|
2682
|
111
|
10
|
2026-05-07T11:35:28.253102+00:00
|
/Users/lukas/.screenpipe/data/data/2026-05-07/1778 /Users/lukas/.screenpipe/data/data/2026-05-07/1778153728253_m1.jpg...
|
PhpStorm
|
faVsco.js – Hubspot/Client.php
|
True
|
NULL
|
monitor_1
|
NULL
|
NULL
|
NULL
|
NULL
|
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
Execute
Explain Plan
Browse Query History
View Parameters
Open Query Execution Settings…
In-Editor Results
Tx: Auto
Cancel Running Statements
Playground
jiminny
Sync Changes
Hide This Notification
Code changed:
Hide
6
1
6
Previous Highlighted Error
Next Highlighted Error
# [PASSWORD_DOTS] HS [PASSWORD_DOTS]
select * from teams where id = 2; # 2
select * from features; # 2
select * from team_features where team_id = 2; # 2
select * from crm_configurations where id = 2; # 2
select * from users where team_id = 2; #
select * from playbooks where team_id = 2; # event 38
select * from playbook_categories where playbook_id = 38; #
SELECT * FROM activities WHERE crm_configuration_id = 2 and crm_provider_id is not null order by id desc;
[URL_WITH_CREDENTIALS] CrmFieldOption array{id:string, label:string, value?:string}
*/
class Client extends BaseClient implements HubspotClientInterface
{
public const string MIN_API_VERSION = '2';
public const string BASE_URL = '[URL_WITH_CREDENTIALS] T
* @param callable(): T $apiCall
* @return T
*
* @throws RateLimitException
*/
private function executeRequest(callable $apiCall)
{
if (! $this->rateLimiter->canMakeRequest($this->config)) {
$retryAfter = $this->rateLimiter->requestAvailableIn($this->config);
$this->log->warning('[Hubspot] Rate limit exceeded, deferring request', [
'team_id' => $this->config->team_id,
'config_id' => $this->config->getId(),
'retry_after' => $retryAfter,
]);
throw new RateLimitException(
'Hubspot rate limit reached for configuration ' . $this->config->getId(),
$retryAfter,
);
}
$this->rateLimiter->incrementRequestCount($this->config);
try {
return $apiCall();
} catch (Throwable $e) {
if ($this->isHubspotRateLimit($e)) {
$retryAfter = $this->parseRetryAfter($e);
$this->log->warning('[Hubspot] Received 429 from API', [
'team_id' => $this->config->team_id,
'config_id' => $this->config->getId(),
'retry_after' => $retryAfter,
'reason' => $e->getMessage(),
]);
throw new RateLimitException('Hubspot returned 429', $retryAfter, $e);
}
throw $e;
}
}
private function isHubspotRateLimit(Throwable $e): bool
{
return method_exists($e, 'getCode') && (int) $e->getCode() === 429;
}
private function parseRetryAfter(Throwable $e): int
{
if (method_exists($e, 'getResponseHeaders')) {
$headers = $e->getResponseHeaders() ?: [];
$value = $headers['Retry-After'] ?? $headers['retry-after'] ?? null;
if (is_array($value)) {
$value = $value[0] ?? null;
}
if (is_numeric($value)) {
return (int) $value;
}
}
return 10;
}
public function getMinimumApiVersion(): string
{
return self::MIN_API_VERSION;
}
public function getInstance(): Factory
{
return new Factory([
'key' => $this->accessToken,
'oauth2' => true,
'base_url' => $this->baseUrl,
]);
}
public function getNewInstance(): Discovery
{
return \HubSpot\Factory::createWithAccessToken($this->accessToken);
}
/**
* Secondly and daily limits for Hubspot API
*
* Product Tier: Free & Starter | Professional & Enterprise | API add-on (any tier)
* Burst: 100/10 seconds | 150/10 seconds | 200/10 seconds
* Daily: 250,000 | 500,000 | 1,000,000
*
* Official documentation states: The search endpoints are rate limited to five requests per second.
* Since with 5 RPS were still hitting secondly rate limits we lowered it to 4
*/
public function getPaginatedData(array $payload, string $type, int $offset = 0): array
{
$total = 0;
$lastId = null;
$rows = [];
foreach ($this->getPaginatedDataGenerator($payload, $type, $offset, $total, $lastId) as $row) {
$rows[] = $row;
}
return ['results' => $rows, 'total' => $total, 'last_record' => $lastId];
}
/**
* @throws HubspotException
* @throws SocialAccountTokenInvalidException
* @throws BadRequest
*/
public function getPaginatedDataGenerator(
array $payload,
string $type,
int $offset = 0,
int &$total = 0,
?string &$lastRecordId = null
): \Generator {
return $this->paginationService->getPaginatedDataGenerator(
$this,
$payload,
$type,
$offset,
$total,
$lastRecordId
);
}
/**
* @throws DealApiException
* @throws CrmException
*/
public function getOpportunityById(string $crmId, array $fields): array
{
try {
$deal = $this->executeRequest(fn () => $this->getNewInstance()->crm()->deals()->basicApi()->getById(
$crmId,
implode(',', $fields),
'companies,contacts'
));
} catch (DealApiException $e) {
$this->log->info('[Hubspot] Failed to fetch opportunity', [
'crm_id' => $crmId,
'reason' => $e->getMessage(),
]);
throw $e;
}
if (! $deal instanceof DealWithAssociations) {
throw new CrmException('Deal not found');
}
return [
'id' => $deal->getId(),
'properties' => $deal->getProperties(),
'associations' => $deal->getAssociations(),
];
}
/**
* Generic batch read method for HubSpot objects
*
* @param string $objectType The object type ('deals', 'companies', 'contacts')
* @param array<string> $crmIds Array of HubSpot object IDs (max 100)
* @param array<string> $fields Array of property names to fetch
*
* @return array<string, array> Array keyed by CRM ID with object data
*/
private function batchReadObjects(string $objectType, array $crmIds, array $fields): array
{
if (empty($crmIds)) {
return [];
}
$this->validateBatchSize($objectType, $crmIds);
$this->ensureValidToken();
try {
$batchConfig = $this->createBatchConfiguration($objectType);
$batchReadRequest = $this->prepareBatchRequest($batchConfig, $crmIds, $fields);
$response = $batchConfig['api']->read($batchReadRequest);
$this->validateApiResponse($response, $objectType);
$results = $this->processApiResults($response);
$this->logBatchResults($objectType, $crmIds, $results);
return $results;
} catch (\Throwable $e) {
$this->handleBatchError($e, $objectType, $crmIds);
}
}
private function validateBatchSize(string $objectType, array $crmIds): void
{
if (count($crmIds) > 100) {
throw new \InvalidArgumentException("Batch size cannot exceed 100 {$objectType}");
}
}
private function createBatchConfiguration(string $objectType): array
{
$configurations = [
'deals' => [
'batchReadRequest' => new \HubSpot\Client\Crm\Deals\Model\BatchReadInputSimplePublicObjectId(),
'inputClass' => \HubSpot\Client\Crm\Deals\Model\SimplePublicObjectId::class,
'api' => $this->getNewInstance()->crm()->deals()->batchApi(),
],
'companies' => [
'batchReadRequest' => new \HubSpot\Client\Crm\Companies\Model\BatchReadInputSimplePublicObjectId(),
'inputClass' => \HubSpot\Client\Crm\Companies\Model\SimplePublicObjectId::class,
'api' => $this->getNewInstance()->crm()->companies()->batchApi(),
],
'contacts' => [
'batchReadRequest' => new \HubSpot\Client\Crm\Contacts\Model\BatchReadInputSimplePublicObjectId(),
'inputClass' => \HubSpot\Client\Crm\Contacts\Model\SimplePublicObjectId::class,
'api' => $this->getNewInstance()->crm()->contacts()->batchApi(),
],
];
if (! isset($configurations[$objectType])) {
throw new \InvalidArgumentException("Unsupported object type: {$objectType}");
}
return $configurations[$objectType];
}
private function prepareBatchRequest(array $batchConfig, array $crmIds, array $fields): object
{
$batchReadRequest = $batchConfig['batchReadRequest'];
$inputClass = $batchConfig['inputClass'];
$inputs = array_map(function ($crmId) use ($inputClass) {
$input = new $inputClass();
$input->setId($crmId);
return $input;
}, $crmIds);
$batchReadRequest->setInputs($inputs);
$batchReadRequest->setProperties($fields);
return $batchReadRequest;
}
private function validateApiResponse($response, string $objectType): void
{
if (! $response) {
throw new CrmException("HubSpot API returned null response for {$objectType} batch read");
}
}
private function processApiResults($response): array
{
$results = [];
$responseResults = $response->getResults();
if ($responseResults) {
foreach ($responseResults as $object) {
if ($object && $object->getId()) {
$results[$object->getId()] = [
'id' => $object->getId(),
'properties' => $object->getProperties() ?: [],
];
}
}
}
return $results;
}
private function logBatchResults(string $objectType, array $crmIds, array $results): void
{
$this->log->info("[HubSpot] Batch fetched {$objectType}", [
'requested_count' => count($crmIds),
'returned_count' => count($results),
'crm_ids' => $crmIds,
]);
}
private function handleBatchError(\Throwable $e, string $objectType, array $crmIds): void
{
$errorMessage = $e->getMessage() ?: 'Unknown error';
$errorTrace = $e->getTraceAsString() ?: 'No trace available';
$this->log->error("[HubSpot] Failed to batch fetch {$objectType}", [
'crm_ids' => $crmIds,
'error' => $errorMessage,
'trace' => $errorTrace,
]);
throw new CrmException("Failed to batch fetch {$objectType}: " . $errorMessage);
}
/**
* Batch read multiple opportunities by their CRM IDs
*
* @param array<string> $crmIds Array of HubSpot deal IDs (max 100)
* @param array<string> $fields Array of property names to fetch
*
* @return array<string, array> Array keyed by CRM ID with opportunity data
*/
public function getOpportunitiesByIds(array $crmIds, array $fields): array
{
return $this->batchReadObjects('deals', $crmIds, $fields);
}
/**
* Batch read multiple companies by their CRM IDs
*
* @param array<string> $crmIds Array of HubSpot company IDs (max 100)
* @param array<string> $fields Array of property names to fetch
*
* @return array<string, array> Array keyed by CRM ID with company data
*/
public function getCompaniesByIds(array $crmIds, array $fields): array
{
return $this->batchReadObjects('companies', $crmIds, $fields);
}
/**
* Batch read multiple contacts by their CRM IDs
*
* @param array<string> $crmIds Array of HubSpot contact IDs (max 100)
* @param array<string> $fields Array of property names to fetch
*
* @return array<string, array> Array keyed by CRM ID with contact data
*/
public function getContactsByIds(array $crmIds, array $fields): array
{
return $this->batchReadObjects('contacts', $crmIds, $fields);
}
/**
* @throws CompanyApiException
* @throws CrmException
*/
public function getAccountById(string $crmId, array $fields): array
{
try {
$company = $this->getNewInstance()->crm()->companies()->basicApi()->getById(
$crmId,
implode(',', $fields),
);
} catch (CompanyApiException $e) {
$this->log->info('[Hubspot] Failed to fetch account', [
'crm_id' => $crmId,
'reason' => $e->getMessage(),
]);
throw $e;
}
if (! $company instanceof CompaniesWithAssociations) {
throw new CrmException('Account not found');
}
return [
'id' => $company->getId(),
'properties' => $company->getProperties(),
];
}
/**
* @throws ContactApiException
* @throws CrmException
*/
public function getContactById(string $crmId, array $fields): array
{
try {
$contact = $this->getNewInstance()->crm()->contacts()->basicApi()->getById(
$crmId,
implode(',', $fields)
);
} catch (ContactApiException $e) {
$this->log->info('[Hubspot] Failed to fetch contact', [
'crm_id' => $crmId,
'reason' => $e->getMessage(),
]);
throw $e;
}
if (! $contact instanceof ContactsWithAssociations) {
throw new CrmException('Contact not found');
}
return [
'id' => $contact->getId(),
'properties' => $contact->getProperties(),
];
}
/**
* This is email search request that Hubspot offers as GET (more generous quota)
*/
public function getContactByEmail(string $email, array $fields = []): array
{
try {
$contact = $this->getNewInstance()->crm()->contacts()->basicApi()->getById(
$email,
implode(',', $fields),
null,
false,
'email'
);
return [
'id' => $contact->getId(),
'properties' => $contact->getProperties(),
];
} catch (ContactApiException $e) {
$this->log->info('[Hubspot] Failed to fetch contact', [
'email' => $email,
'reason' => $e->getMessage(),
]);
return [];
}
}
/**
* @throws CrmException
*/
public function fetchProperty(string $objectType, string $propertyId): Property
{
$result = $this->getNewInstance()->crm()->properties()->coreApi()->getByName($objectType, $propertyId);
if (! $result instanceof Property) {
$this->log->error('[Hubspot] Failed to fetch property', [
'object_type' => $objectType,
'property_id' => $propertyId,
'reason' => $result->getMessage(),
]);
throw new CrmException('Failed to fetch property');
}
return $result;
}
/**
* @return array<CrmFieldOption>
*/
public function fetchPropertyOptions(string $objectType, string $propertyId): array
{
/** @var array<CrmFieldOption> */
return $this->fetchProperty($objectType, $propertyId)->getOptions();
}
/**
* @return array<array{id:string, label:string, deleted:bool}>
*/
public function fetchCallDispositions(): array
{
/** @var Response $response */
$response = $this->getInstance()->engagements()->getCallDispositions();
/**
* @var array<array{
* id:string,
* label:string,
* deleted: bool
* }>
*/
return $response->toArray();
}
/**
* @return array<CrmFieldOption>
*/
public function fetchOpportunityPipelineStages(): array
{
$stages = [];
$apiResponse = $this->getNewInstance()->crm()->pipelines()->pipelinesApi()->getAll('deals');
if ($apiResponse instanceof Error) {
$this->log->error('[Hubspot] Failed to fetch opportunity pipelines', [
'reason' => $apiResponse->getMessage(),
]);
return [];
}
foreach ($apiResponse->getResults() as $pipeline) {
$pipelineStages = array_map(
static function (PipelineStage $stage) {
return [
'id' => $stage->getId(),
'label' => $stage->getLabel(),
];
},
$pipeline->getStages()
);
$stages = array_merge($stages, $pipelineStages);
}
return $stages;
}
public function fetchOpportunityPipelines(): array
{
$pipelines = [];
try {
$apiResponse = $this->makeRequest('/crm/v3/pipelines/deals');
} catch (\Exception $e) {
$this->log->info('[Hubspot] Failed to fetch opportunity pipelines', [
'reason' => $e->getMessage(),
]);
return [];
}
$response = $apiResponse->toArray();
foreach ($response['results'] as $pipeline) {
$pipelines[] = [
'id' => $pipeline['id'],
'label' => $pipeline['label'],
];
}
return $pipelines;
}
/**
* @return array<CrmFieldOption>
*/
public function fetchMeetingOutcomeFieldOptions(Field $field): array
{
return $field->getCrmProviderId() === 'meetingOutcome'
? $this->fetchMeetingOutcomeTypes()
: $this->fetchCallActivityTypes();
}
public function fetchMeetingOutcomeTypes(): array
{
return $this->extractMeetingTypeOptions(
'[URL_WITH_CREDENTIALS] Response $response */
$response = $this->getInstance()
->getClient()
->request('GET', $endpoint);
/**
* @var array<array{
* value: string,
* label: string,
* displayOrder: int
* }> $optionData
*/
$optionData = $response->toArray()['options'] ?? [];
$options = [];
foreach ($optionData as $item) {
$options[] = [
'id' => $item['value'],
'value' => $item['value'],
'label' => $item['label'],
'display_order' => $item['displayOrder'],
];
}
return $options;
}
/**
* @return array<CrmFieldOption>
*/
public function fetchDispositionFieldOptions(): array
{
$options = [];
$dispositions = $this->fetchCallDispositions();
foreach ($dispositions as $disposition) {
if ($disposition['deleted'] !== false) {
continue;
}
$option['value'] = $disposition['id'];
$option['id'] = $disposition['id'];
$option['label'] = $disposition['label'];
$options[] = $option;
}
return $options;
}
/**
* @return array<CrmFieldOption>
*/
public function fetchOpportunityFieldOptions(Field $field): array
{
if ($field->isStageField()) {
return $this->fetchOpportunityPipelineStages();
}
if ($field->isPipelineField()) {
return $this->fetchOpportunityPipelines();
}
return $this->fetchPropertyOptions('deals', $field->getCrmProviderId());
}
/**
* @throws BadRequest
* @throws HubspotException
*/
public function makeRequest(string $endpoint, $method = 'GET', $payload = [], ?string $queryString = null)
{
$endpoint = self::BASE_URL . $endpoint;
if ($method === 'GET') {
$response = $this->getInstance()->getClient()?->request(
method: $method,
endpoint: $endpoint,
query_string: $queryString
);
} else {
$response = $this->getInstance()->getClient()->request($method, $endpoint, [
'json' => ($payload),
]);
}
$max = $response->getHeaderLine('X-HubSpot-RateLimit-Max'); // "110"
$remaining = $response->getHeaderLine('X-HubSpot-RateLimit-Remaining'); // "109"
$interval = $response->getHeaderLine('X-HubSpot-RateLimit-Interval-Milliseconds'); // "10000"
$body = json_decode((string) $response->getBody(), true);
\Illuminate\Support\Facades\Log::channel('custom_channel')->info('$max ' . PHP_EOL . print_r($max, true));
\Illuminate\Support\Facades\Log::channel('custom_channel')->info('$remaining ' . PHP_EOL . print_r($remaining, true));
\Illuminate\Support\Facades\Log::channel('custom_channel')->info('$remaining ' . PHP_EOL . print_r($remaining, true));
return $response;
}
/**
* @throws BadRequest
* @throws HubspotException
*/
public function createMeeting(array $payload): Response
{
$endpoint = '/crm/v3/objects/meetings';
return $this->makeRequest($endpoint, 'POST', $payload);
}
/**
* @throws BadRequest
* @throws HubspotException
*/
public function updateMeeting(string $meetingId, array $payload): Response
{
$endpoint = '/crm/v3/objects/meetings/' . $meetingId;
return $this->makeRequest($endpoint, 'PATCH', $payload);
}
/**
* @throws \Exception
*/
public function createNote(
string $body,
string $ownerId,
int $timestamp,
string $objectId,
NoteObject $noteObject
): ?string {
try {
$noteInput = new SimplePublicObjectInput([
'properties' => [
'hs_note_body' => $body,
'hubspot_owner_id' => $ownerId,
'hs_timestamp' => $timestamp,
],
]);
// Create note
$note = $this->getNewInstance()->crm()->objects()->basicApi()->create('note', $noteInput);
$this->getNewInstance()->crm()->objects()->associationsApi()->create(
'note',
$note->getId(),
$this->getNoteObject($noteObject),
$objectId,
$this->getNoteAssociationType($noteObject),
);
return $note->getId();
} catch (\Exception $e) {
$this->log->error('[Hubspot] Failed to create note', [
'objectId' => $objectId,
'noteObject' => $noteObject->getObjectType(),
'reason' => $e->getMessage(),
]);
\Sentry::captureException($e);
}
return null;
}
public function updateEngagement(string $objectId, array $engagement, array $metadata): void
{
$this->getInstance()->engagements()->update($objectId, $engagement, $metadata);
}
public function getEngagementData(string $engagementId): array
{
$engagement = $this->getInstance()->engagements()->get($engagementId);
return $engagement->toArray();
}
public function createEngagement(array $engagement, array $associations, array $metadata): Response
{
return $this->getInstance()
->engagements()
->create($engagement, $associations, $metadata);
}
public function isUnauthorizedException(\Exception $e): bool
{
// Check for specific HubSpot API exception types first
if ($e instanceof BadRequest) {
// BadRequest can contain 401 status codes
return $e->getCode() === 401;
}
// Check for HTTP client exceptions with status codes
if ($e instanceof \GuzzleHttp\Exception\RequestException && $e->hasResponse()) {
$response = $e->getResponse();
if ($response !== null) {
return $response->getStatusCode() === 401;
}
}
// Check for Guzzle HTTP exceptions
if ($e instanceof \GuzzleHttp\Exception\ClientException) {
return $e->getCode() === 401;
}
// Fallback to string matching as last resort, but be more specific
$message = strtolower($e->getMessage());
return str_contains($message, '401 unauthorized') ||
str_contains($message, 'http 401') ||
str_contains($message, 'status code 401') ||
(preg_match('/\b401\b/', $message) && str_contains($message, 'unauthorized'));
}
/**
* Validates and refreshes the access token if needed before API requests.
* This ensures long-running processes don't fail due to token expiration.
*
* @throws SocialAccountTokenInvalidException
*/
public function ensureValidToken(): void
{
if ($this->oauthAccount === null) {
return;
}
$newToken = $this->tokenManager->ensureValidToken($this->oauthAccount);
if ($newToken !== null) {
$this->accessToken = $newToken;
}
}
public function getConfig()
{
return $this->config;
}
// returns only active (archived=false)
public function getOwners(): array
{
return $this->getNewInstance()->crm()->owners()->getAll();
}
/**
* @param bool $archived
*
* @return array<Owner>|[]
*/
public function getOwnersArchived(bool $archived = true): array
{
$endpoint = '/crm/v3/owners';
$queryParams = [
'archived' => $archived ? 'true' : 'false',
];
$queryString = http_build_query($queryParams);
$owners = [];
try {
$response = $this->makeRequest(endpoint: $endpoint, queryString: $queryString);
$responseData = $response?->toArray();
foreach ($responseData['results'] as $result) {
try {
$owners[] = Owner::create($result);
} catch (Throwable $e) {
$this->log->error('[HubSpot] Failed to process owner data', [
'result' => $result,
'error' => $e->getMessage(),
]);
continue;
}
}
} catch (Throwable $e) {
$this->log->error('HubSpot] Failed to fetch owners', [
'archived' => $archived,
'error' => $e->getMessage(),
]);
return [];
}
return $owners;
}
public function getMeeting(string $engagementId): ObjectWithAssociations
{
return $this->getNewInstance()->crm()->objects()->basicApi()
->getById('meeting', $engagementId, null, 'contact,company,deal');
}
public function deleteEngagement(string $engagementId): void
{
$this->getInstance()->engagements()->delete((int) $engagementId);
}
public function getAssociationsData(array $ids, string $fromObject, string $toObject): array
{
$associationData = [];
$idChunks = array_chunk($ids, self::ASSOCIATIONS_BATCH_SIZE_LIMIT);
foreach ($idChunks as $idChunk) {
try {
$batchInput = new \HubSpot\Client\Crm\Associations\Model\BatchInputPublicObjectId();
$batchInput->setInputs(array_map(function ($id) {
$publicObjectId = new \HubSpot\Client\Crm\Associations\Model\PublicObjectId();
$publicObjectId->setId($id);
return $publicObjectId;
}, $idChunk));
$associatedObjectsData = $this
->getNewInstance()
->crm()
->associations()
->batchApi()
->read($fromObject, $toObject, $batchInput);
if ($associatedObjectsData instanceof \HubSpot\Client\Crm\Associations\Model\BatchResponsePublicAssociationMulti) {
foreach ($associatedObjectsData->getResults() as $association) {
$from = $association->getFrom()->getId();
$toAssociations = $association->getTo();
if (! empty($toAssociations)) {
$associationData[$from] = array_map(function ($item) {
return $item->getId();
}, $toAssociations);
}
}
}
} catch (\Exception $e) {
$this->log->error('[Hubspot] Failed to fetch associations', [
'from_object' => $fromObject,
'to_object' => $toObject,
'reason' => $e->getMessage(),
]);
}
}
return $associationData;
}
/**
* @throws \Exception
*/
private function getNoteAssociationType(NoteObject $noteObject): string
{
return match($noteObject) {
NoteObject::Opportunity => 'note_to_deal',
NoteObject::Lead, NoteObject::Contact => 'note_to_contact', // or 'note_to_lead' if your portal supports it
NoteObject::Account => 'note_to_company',
NoteObject::Call, NoteObject::Event => throw new \Exception('Not supported'),
};
}
/**
* @throws \Exception
*/
private function getNoteObject(NoteObject $noteObject): string
{
return match($noteObject) {
NoteObject::Opportunity => 'deal',
NoteObject::Lead, NoteObject::Contact => 'contact',
NoteObject::Account => 'company',
NoteObject::Call, NoteObject::Event => throw new \Exception('Not supported'),
};
}
public function addAssociations(string $objectType, string $associationType, array $payload): Response
{
$endpoint = "/crm/v4/associations/$objectType/$associationType/batch/create";
return $this->makeRequest($endpoint, 'POST', $payload);
}
public function removeAssociations(string $objectType, string $associationType, array $payload): Response
{
$endpoint = "/crm/v4/associations/$objectType/$associationType/batch/archive";
return $this->makeRequest($endpoint, 'POST', $payload);
}
}
Project
Project
New File or Directory…
Expand Selected
Collapse All
Options
Hide...
|
[{"role":"AXButton","text" [{"role":"AXButton","text":"Project: faVsco.js, menu","depth":5,"on_screen":true,"help_text":"~/jiminny/app","role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"master, menu","depth":5,"on_screen":true,"help_text":"Git Branch: master","role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Start Listening for PHP Debug Connections","depth":5,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"AskJiminnyReportActivityServiceTest","depth":6,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Run 'AskJiminnyReportActivityServiceTest'","depth":6,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Debug 'AskJiminnyReportActivityServiceTest'","depth":6,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"More Actions","depth":6,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"JetBrains AI","depth":5,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Search Everywhere","depth":5,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"IDE and Project Settings","depth":5,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Execute","depth":4,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Explain Plan","depth":4,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Browse Query History","depth":4,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"View Parameters","depth":4,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Open Query Execution Settings…","depth":4,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"In-Editor Results","depth":4,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Tx: Auto","depth":4,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Cancel Running Statements","depth":4,"on_screen":true,"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Playground","depth":4,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"jiminny","depth":4,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Sync Changes","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.018055556,"height":0.026666667},"on_screen":false,"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Hide This Notification","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.018055556,"height":0.026666667},"on_screen":false,"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Code changed:","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.088194445,"height":0.027777778},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Hide","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.018055556,"height":0.026666667},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"6","depth":4,"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"1","depth":4,"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"6","depth":4,"on_screen":true,"role_description":"text"},{"role":"AXButton","text":"Previous Highlighted Error","depth":4,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Next Highlighted Error","depth":4,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXTextArea","text":"# **************************** HS **************************************\n\nselect * from teams where id = 2; # 2\nselect * from features; # 2\nselect * from team_features where team_id = 2; # 2\nselect * from crm_configurations where id = 2; # 2\nselect * from users where team_id = 2; #\nselect * from playbooks where team_id = 2; # event 38\nselect * from playbook_categories where playbook_id = 38; #\n\nSELECT * FROM activities WHERE crm_configuration_id = 2 and crm_provider_id is not null order by id desc;\nhttps://app.hubspot.com/contacts/4392066/deal/16964514951/?engagement=96069102624\n https://app.staging.jiminny.com/playback/d5df34dc-bd66-4ff5-a7b3-8d3be30322a0\n\nSELECT * FROM activities WHERE uuid_to_bin('04fdcd0d-818f-4c53-92dc-6f18bc753ffd') = uuid;\n# 609126 softphone tr. 11241\n\nSELECT * FROM activities WHERE uuid_to_bin('6521bfcd-5a30-46e5-9f74-5440fd48befd') = uuid;\n# 608874 conference tr. 11226 crmId: 103422236596\n\nselect * from ai_prompts where transcription_id IN (11241, 11226);\nselect * from activity_summary_logs where activity_id = 608874;\n\nselect * from sidekick_settings;\nselect * from default_activity_types;\n\nselect * from crm_field_data where activity_id = 1223;\n\nselect * from crm_layouts where crm_configuration_id = 2;\nSELECT * FROM crm_layout_entities WHERE crm_layout_id IN (554);\nselect * from crm_fields where crm_configuration_id = 11 and object_type = 'event';\nSELECT * FROM crm_field_values WHERE crm_field_id IN (1455,1450);\n\nSELECT * FROM crm_field_data WHERE crm_layout_entity_id = 971;\nSELECT * FROM crm_field_data WHERE crm_layout_entity_id IN (6494,6495,6496,6497,6498,6499);\n\nSELECT\n CONCAT(u.id, CASE WHEN u.id = t.owner_id THEN ' (owner)' ELSE '' END) AS user_id,\n u.email,\n sa.*,\n t.owner_id FROM social_accounts sa\nJOIN users u\n on u.id = sa.sociable_id\nJOIN teams t on t.id = u.team_id\nWHERE u.team_id = 2 and sa.provider = 'hubspot';\n\nselect * from social_accounts where id = 1499;\n\nselect * from opportunities where team_id = 2\nand crm_provider_id IN ('51317301383');\n\nselect * from contacts where id = 85;\n\nselect * from opportunities where team_id = 2 order by id desc;\nselect * from opportunities where team_id = 2 and crm_provider_id = '51317301383'; # 5112\nselect * from opportunities where team_id = 2 and crm_provider_id = '55976759904'; # 5112\nselect * from opportunity_contacts where opportunity_id = 5117;\nselect * from crm_field_data where object_id = 1365;\nSELECT * FROM crm_fields WHERE id IN (1405, 1407, 1972, 2128);\n\nselect * from features;\nselect * from team_features where team_id IN (1);\nselect * from team_features where feature_id IN (36);\n\nSHOW CREATE TABLE opportunity_contacts;\nSELECT * FROM opportunity_contacts WHERE crm_provider_id = '111751';\n\n# $slug = 'HUBSPOT_WEBHOOK_SYNC';\n# $team = Jiminny\\Models\\Team::find(2);\n# $feature = Feature::query()->where('slug', $slug)->first();\n# TeamFeature::query()->create(['feature_id' => $feature->getId(),'team_id' => $team->getId()]);\n\n# hubspot_webhook_metrics\n\nselect * from opportunities where team_id = 2 and crm_provider_id IN ('374720564','14527423589','49908861993','50435771779'); # 1365\nSELECT * FROM opportunity_contacts WHERE opportunity_id = '414';\nSELECT * FROM opportunity_contacts WHERE crm_provider_id = '131501';\nselect * from contacts where id in (414, 464);\n\nselect * from activities where crm_configuration_id = 2;\n\nselect settings from crm_configurations where id = 11;\n\nselect * from teams; # 1, 2\nselect * from users;\nselect * from crm_configurations where id = 39;\nselect * from team_features where team_id = 2;\nselect * from features;\n# SELECT * FROM opportunities WHERE crm_configuration_id = 2\n# order by id desc;\n# and crm_provider_id = '49908861993';\n\n\nselect * from activity_providers where id IN (443, 202, 203, 227);\n\nselect * from activity_imports where id = 795889;\n\nselect c.id, c.provider, c.settings, t.* from teams t join crm_configurations c on t.id = c.team_id\nwhere c.provider = 'hubspot';\n\nselect * from crm_configurations crm JOIN teams t on crm.team_id = t.id\nwhere provider = 'hubspot';\nSELECT * FROM teams WHERE id = 31;\nSELECT * FROM users WHERE id = 257;\nSELECT * FROM opportunities WHERE team_id = 2;\n\nselect * from opportunity_contacts where opportunity_id = 5124;\nselect * from contacts where id IN (3850,3853,3851,4073,4140,4155,4480,4530,4623,5986,513,687,1806,1523,3613)\n\nselect * from activities where crm_configuration_id = 13;\n\nSELECT * FROM activities WHERE uuid_to_bin('826619ce-ec8e-4e59-8467-a01f5f6ad71e') = uuid; # 418141\n\n\nselect id, team_id, crm_provider_id from crm_configurations where provider = 'hubspot' and crm_provider_id IS NOT NULL;\nSELECT * FROM accounts WHERE team_id = 2 and crm_provider_id = '1212213464' order by id desc;\nSELECT * FROM contacts WHERE team_id = 2 and account_id = 5189 order by id desc;\nSELECT * FROM contacts WHERE team_id = 2 order by id desc;\nselect * from opportunity_contacts where contact_id = 6223;\nSELECT * FROM opportunities WHERE team_id = 2 and account_id = 5189 order by id desc;\n\nselect * from crm_profiles where crm_configuration_id = 2;\n\nselect * from activities where account_id = 46;","depth":4,"on_screen":true,"value":"# **************************** HS **************************************\n\nselect * from teams where id = 2; # 2\nselect * from features; # 2\nselect * from team_features where team_id = 2; # 2\nselect * from crm_configurations where id = 2; # 2\nselect * from users where team_id = 2; #\nselect * from playbooks where team_id = 2; # event 38\nselect * from playbook_categories where playbook_id = 38; #\n\nSELECT * FROM activities WHERE crm_configuration_id = 2 and crm_provider_id is not null order by id desc;\nhttps://app.hubspot.com/contacts/4392066/deal/16964514951/?engagement=96069102624\n https://app.staging.jiminny.com/playback/d5df34dc-bd66-4ff5-a7b3-8d3be30322a0\n\nSELECT * FROM activities WHERE uuid_to_bin('04fdcd0d-818f-4c53-92dc-6f18bc753ffd') = uuid;\n# 609126 softphone tr. 11241\n\nSELECT * FROM activities WHERE uuid_to_bin('6521bfcd-5a30-46e5-9f74-5440fd48befd') = uuid;\n# 608874 conference tr. 11226 crmId: 103422236596\n\nselect * from ai_prompts where transcription_id IN (11241, 11226);\nselect * from activity_summary_logs where activity_id = 608874;\n\nselect * from sidekick_settings;\nselect * from default_activity_types;\n\nselect * from crm_field_data where activity_id = 1223;\n\nselect * from crm_layouts where crm_configuration_id = 2;\nSELECT * FROM crm_layout_entities WHERE crm_layout_id IN (554);\nselect * from crm_fields where crm_configuration_id = 11 and object_type = 'event';\nSELECT * FROM crm_field_values WHERE crm_field_id IN (1455,1450);\n\nSELECT * FROM crm_field_data WHERE crm_layout_entity_id = 971;\nSELECT * FROM crm_field_data WHERE crm_layout_entity_id IN (6494,6495,6496,6497,6498,6499);\n\nSELECT\n CONCAT(u.id, CASE WHEN u.id = t.owner_id THEN ' (owner)' ELSE '' END) AS user_id,\n u.email,\n sa.*,\n t.owner_id FROM social_accounts sa\nJOIN users u\n on u.id = sa.sociable_id\nJOIN teams t on t.id = u.team_id\nWHERE u.team_id = 2 and sa.provider = 'hubspot';\n\nselect * from social_accounts where id = 1499;\n\nselect * from opportunities where team_id = 2\nand crm_provider_id IN ('51317301383');\n\nselect * from contacts where id = 85;\n\nselect * from opportunities where team_id = 2 order by id desc;\nselect * from opportunities where team_id = 2 and crm_provider_id = '51317301383'; # 5112\nselect * from opportunities where team_id = 2 and crm_provider_id = '55976759904'; # 5112\nselect * from opportunity_contacts where opportunity_id = 5117;\nselect * from crm_field_data where object_id = 1365;\nSELECT * FROM crm_fields WHERE id IN (1405, 1407, 1972, 2128);\n\nselect * from features;\nselect * from team_features where team_id IN (1);\nselect * from team_features where feature_id IN (36);\n\nSHOW CREATE TABLE opportunity_contacts;\nSELECT * FROM opportunity_contacts WHERE crm_provider_id = '111751';\n\n# $slug = 'HUBSPOT_WEBHOOK_SYNC';\n# $team = Jiminny\\Models\\Team::find(2);\n# $feature = Feature::query()->where('slug', $slug)->first();\n# TeamFeature::query()->create(['feature_id' => $feature->getId(),'team_id' => $team->getId()]);\n\n# hubspot_webhook_metrics\n\nselect * from opportunities where team_id = 2 and crm_provider_id IN ('374720564','14527423589','49908861993','50435771779'); # 1365\nSELECT * FROM opportunity_contacts WHERE opportunity_id = '414';\nSELECT * FROM opportunity_contacts WHERE crm_provider_id = '131501';\nselect * from contacts where id in (414, 464);\n\nselect * from activities where crm_configuration_id = 2;\n\nselect settings from crm_configurations where id = 11;\n\nselect * from teams; # 1, 2\nselect * from users;\nselect * from crm_configurations where id = 39;\nselect * from team_features where team_id = 2;\nselect * from features;\n# SELECT * FROM opportunities WHERE crm_configuration_id = 2\n# order by id desc;\n# and crm_provider_id = '49908861993';\n\n\nselect * from activity_providers where id IN (443, 202, 203, 227);\n\nselect * from activity_imports where id = 795889;\n\nselect c.id, c.provider, c.settings, t.* from teams t join crm_configurations c on t.id = c.team_id\nwhere c.provider = 'hubspot';\n\nselect * from crm_configurations crm JOIN teams t on crm.team_id = t.id\nwhere provider = 'hubspot';\nSELECT * FROM teams WHERE id = 31;\nSELECT * FROM users WHERE id = 257;\nSELECT * FROM opportunities WHERE team_id = 2;\n\nselect * from opportunity_contacts where opportunity_id = 5124;\nselect * from contacts where id IN (3850,3853,3851,4073,4140,4155,4480,4530,4623,5986,513,687,1806,1523,3613)\n\nselect * from activities where crm_configuration_id = 13;\n\nSELECT * FROM activities WHERE uuid_to_bin('826619ce-ec8e-4e59-8467-a01f5f6ad71e') = uuid; # 418141\n\n\nselect id, team_id, crm_provider_id from crm_configurations where provider = 'hubspot' and crm_provider_id IS NOT NULL;\nSELECT * FROM accounts WHERE team_id = 2 and crm_provider_id = '1212213464' order by id desc;\nSELECT * FROM contacts WHERE team_id = 2 and account_id = 5189 order by id desc;\nSELECT * FROM contacts WHERE team_id = 2 order by id desc;\nselect * from opportunity_contacts where contact_id = 6223;\nSELECT * FROM opportunities WHERE team_id = 2 and account_id = 5189 order by id desc;\n\nselect * from crm_profiles where crm_configuration_id = 2;\n\nselect * from activities where account_id = 46;","role_description":"text entry area","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Sync Changes","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.018055556,"height":0.026666667},"on_screen":false,"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Hide This Notification","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.018055556,"height":0.026666667},"on_screen":false,"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Code changed:","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.088194445,"height":0.027777778},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Hide","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.018055556,"height":0.026666667},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"2","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.016666668,"height":0.02111111},"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"60","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.021527778,"height":0.02111111},"on_screen":false,"role_description":"text"},{"role":"AXButton","text":"Previous Highlighted Error","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.015277778,"height":0.025555555},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Next Highlighted Error","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.014583333,"height":0.025555555},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXTextArea","text":"<?php\n\ndeclare(strict_types=1);\n\nnamespace Jiminny\\Services\\Crm\\Hubspot;\n\nuse HubSpot\\Client\\Crm\\Deals\\ApiException as DealApiException;\nuse HubSpot\\Client\\Crm\\Contacts\\ApiException as ContactApiException;\nuse HubSpot\\Client\\Crm\\Companies\\ApiException as CompanyApiException;\nuse HubSpot\\Client\\Crm\\Contacts\\Model\\SimplePublicObjectWithAssociations as ContactsWithAssociations;\nuse HubSpot\\Client\\Crm\\Companies\\Model\\SimplePublicObjectWithAssociations as CompaniesWithAssociations;\nuse HubSpot\\Client\\Crm\\Deals\\Model\\SimplePublicObjectWithAssociations as DealWithAssociations;\nuse HubSpot\\Client\\Crm\\Objects\\Model\\SimplePublicObjectInput;\nuse HubSpot\\Client\\Crm\\Objects\\Model\\SimplePublicObjectWithAssociations as ObjectWithAssociations;\nuse HubSpot\\Client\\Crm\\Pipelines\\Model\\Error;\nuse HubSpot\\Client\\Crm\\Pipelines\\Model\\PipelineStage;\nuse HubSpot\\Client\\Crm\\Properties\\Model\\Property;\nuse HubSpot\\Discovery\\Discovery;\nuse Jiminny\\Component\\Utility\\Service\\ProviderRateLimiter;\nuse Jiminny\\Exceptions\\CrmException;\nuse Jiminny\\Exceptions\\RateLimitException;\nuse Jiminny\\Exceptions\\SocialAccountTokenInvalidException;\nuse Jiminny\\Jobs\\Crm\\NoteObject;\nuse Jiminny\\Models\\Crm\\Field;\nuse Jiminny\\Services\\Crm\\BaseClient;\nuse Jiminny\\Services\\Crm\\Hubspot\\DTO\\Response\\Owner;\nuse Jiminny\\Services\\SocialAccountService;\nuse SevenShores\\Hubspot\\Exceptions\\BadRequest;\nuse SevenShores\\Hubspot\\Exceptions\\HubspotException;\nuse SevenShores\\Hubspot\\Factory;\nuse SevenShores\\Hubspot\\Http\\Response;\nuse Jiminny\\Services\\Crm\\Hubspot\\Pagination\\HubspotPaginationService;\nuse Throwable;\n\n/**\n * @phpstan-type CrmFieldOption array{id:string, label:string, value?:string}\n */\nclass Client extends BaseClient implements HubspotClientInterface\n{\n public const string MIN_API_VERSION = '2';\n\n public const string BASE_URL = 'https://api.hubapi.com';\n\n public const int ASSOCIATIONS_BATCH_SIZE_LIMIT = 1000;\n\n private HubspotPaginationService $paginationService;\n private HubspotTokenManager $tokenManager;\n private ProviderRateLimiter $rateLimiter;\n\n public function __construct(\n SocialAccountService $socialAccountService,\n HubspotPaginationService $paginationService,\n HubspotTokenManager $tokenManager,\n ProviderRateLimiter $rateLimiter,\n ) {\n parent::__construct($socialAccountService);\n $this->paginationService = $paginationService;\n $this->tokenManager = $tokenManager;\n $this->rateLimiter = $rateLimiter;\n\n $this->setBaseUrl(self::BASE_URL);\n $this->setVersion(self::MIN_API_VERSION);\n }\n\n /**\n * Single entry point for every HubSpot API call. Enforces the per-portal\n * rate limit configured in the rate_limits table (morphed to the current\n * Configuration) and reacts to a real 429 from HubSpot by translating it\n * into a RateLimitException carrying Retry-After.\n *\n * Wrap any outbound HubSpot call (SDK or raw HTTP) like:\n *\n * $this->executeRequest(fn () => $this->getNewInstance()->crm()->...);\n *\n * @template T\n * @param callable(): T $apiCall\n * @return T\n *\n * @throws RateLimitException\n */\n private function executeRequest(callable $apiCall)\n {\n if (! $this->rateLimiter->canMakeRequest($this->config)) {\n $retryAfter = $this->rateLimiter->requestAvailableIn($this->config);\n\n $this->log->warning('[Hubspot] Rate limit exceeded, deferring request', [\n 'team_id' => $this->config->team_id,\n 'config_id' => $this->config->getId(),\n 'retry_after' => $retryAfter,\n ]);\n\n throw new RateLimitException(\n 'Hubspot rate limit reached for configuration ' . $this->config->getId(),\n $retryAfter,\n );\n }\n\n $this->rateLimiter->incrementRequestCount($this->config);\n\n try {\n return $apiCall();\n } catch (Throwable $e) {\n if ($this->isHubspotRateLimit($e)) {\n $retryAfter = $this->parseRetryAfter($e);\n\n $this->log->warning('[Hubspot] Received 429 from API', [\n 'team_id' => $this->config->team_id,\n 'config_id' => $this->config->getId(),\n 'retry_after' => $retryAfter,\n 'reason' => $e->getMessage(),\n ]);\n\n throw new RateLimitException('Hubspot returned 429', $retryAfter, $e);\n }\n\n throw $e;\n }\n }\n\n private function isHubspotRateLimit(Throwable $e): bool\n {\n return method_exists($e, 'getCode') && (int) $e->getCode() === 429;\n }\n\n private function parseRetryAfter(Throwable $e): int\n {\n if (method_exists($e, 'getResponseHeaders')) {\n $headers = $e->getResponseHeaders() ?: [];\n $value = $headers['Retry-After'] ?? $headers['retry-after'] ?? null;\n if (is_array($value)) {\n $value = $value[0] ?? null;\n }\n if (is_numeric($value)) {\n return (int) $value;\n }\n }\n\n return 10;\n }\n\n public function getMinimumApiVersion(): string\n {\n return self::MIN_API_VERSION;\n }\n\n public function getInstance(): Factory\n {\n return new Factory([\n 'key' => $this->accessToken,\n 'oauth2' => true,\n 'base_url' => $this->baseUrl,\n ]);\n }\n\n public function getNewInstance(): Discovery\n {\n return \\HubSpot\\Factory::createWithAccessToken($this->accessToken);\n }\n\n /**\n * Secondly and daily limits for Hubspot API\n *\n * Product Tier: Free & Starter | Professional & Enterprise | API add-on (any tier)\n * Burst: 100/10 seconds | 150/10 seconds | 200/10 seconds\n * Daily: 250,000 | 500,000 | 1,000,000\n *\n * Official documentation states: The search endpoints are rate limited to five requests per second.\n * Since with 5 RPS were still hitting secondly rate limits we lowered it to 4\n */\n public function getPaginatedData(array $payload, string $type, int $offset = 0): array\n {\n $total = 0;\n $lastId = null;\n $rows = [];\n foreach ($this->getPaginatedDataGenerator($payload, $type, $offset, $total, $lastId) as $row) {\n $rows[] = $row;\n }\n\n return ['results' => $rows, 'total' => $total, 'last_record' => $lastId];\n }\n\n /**\n * @throws HubspotException\n * @throws SocialAccountTokenInvalidException\n * @throws BadRequest\n */\n public function getPaginatedDataGenerator(\n array $payload,\n string $type,\n int $offset = 0,\n int &$total = 0,\n ?string &$lastRecordId = null\n ): \\Generator {\n return $this->paginationService->getPaginatedDataGenerator(\n $this,\n $payload,\n $type,\n $offset,\n $total,\n $lastRecordId\n );\n }\n\n /**\n * @throws DealApiException\n * @throws CrmException\n */\n public function getOpportunityById(string $crmId, array $fields): array\n {\n try {\n $deal = $this->executeRequest(fn () => $this->getNewInstance()->crm()->deals()->basicApi()->getById(\n $crmId,\n implode(',', $fields),\n 'companies,contacts'\n ));\n } catch (DealApiException $e) {\n $this->log->info('[Hubspot] Failed to fetch opportunity', [\n 'crm_id' => $crmId,\n 'reason' => $e->getMessage(),\n ]);\n\n throw $e;\n }\n\n if (! $deal instanceof DealWithAssociations) {\n throw new CrmException('Deal not found');\n }\n\n return [\n 'id' => $deal->getId(),\n 'properties' => $deal->getProperties(),\n 'associations' => $deal->getAssociations(),\n ];\n }\n\n /**\n * Generic batch read method for HubSpot objects\n *\n * @param string $objectType The object type ('deals', 'companies', 'contacts')\n * @param array<string> $crmIds Array of HubSpot object IDs (max 100)\n * @param array<string> $fields Array of property names to fetch\n *\n * @return array<string, array> Array keyed by CRM ID with object data\n */\n private function batchReadObjects(string $objectType, array $crmIds, array $fields): array\n {\n if (empty($crmIds)) {\n return [];\n }\n\n $this->validateBatchSize($objectType, $crmIds);\n $this->ensureValidToken();\n\n try {\n $batchConfig = $this->createBatchConfiguration($objectType);\n $batchReadRequest = $this->prepareBatchRequest($batchConfig, $crmIds, $fields);\n $response = $batchConfig['api']->read($batchReadRequest);\n\n $this->validateApiResponse($response, $objectType);\n\n $results = $this->processApiResults($response);\n $this->logBatchResults($objectType, $crmIds, $results);\n\n return $results;\n } catch (\\Throwable $e) {\n $this->handleBatchError($e, $objectType, $crmIds);\n }\n }\n\n private function validateBatchSize(string $objectType, array $crmIds): void\n {\n if (count($crmIds) > 100) {\n throw new \\InvalidArgumentException(\"Batch size cannot exceed 100 {$objectType}\");\n }\n }\n\n private function createBatchConfiguration(string $objectType): array\n {\n $configurations = [\n 'deals' => [\n 'batchReadRequest' => new \\HubSpot\\Client\\Crm\\Deals\\Model\\BatchReadInputSimplePublicObjectId(),\n 'inputClass' => \\HubSpot\\Client\\Crm\\Deals\\Model\\SimplePublicObjectId::class,\n 'api' => $this->getNewInstance()->crm()->deals()->batchApi(),\n ],\n 'companies' => [\n 'batchReadRequest' => new \\HubSpot\\Client\\Crm\\Companies\\Model\\BatchReadInputSimplePublicObjectId(),\n 'inputClass' => \\HubSpot\\Client\\Crm\\Companies\\Model\\SimplePublicObjectId::class,\n 'api' => $this->getNewInstance()->crm()->companies()->batchApi(),\n ],\n 'contacts' => [\n 'batchReadRequest' => new \\HubSpot\\Client\\Crm\\Contacts\\Model\\BatchReadInputSimplePublicObjectId(),\n 'inputClass' => \\HubSpot\\Client\\Crm\\Contacts\\Model\\SimplePublicObjectId::class,\n 'api' => $this->getNewInstance()->crm()->contacts()->batchApi(),\n ],\n ];\n\n if (! isset($configurations[$objectType])) {\n throw new \\InvalidArgumentException(\"Unsupported object type: {$objectType}\");\n }\n\n return $configurations[$objectType];\n }\n\n private function prepareBatchRequest(array $batchConfig, array $crmIds, array $fields): object\n {\n $batchReadRequest = $batchConfig['batchReadRequest'];\n $inputClass = $batchConfig['inputClass'];\n\n $inputs = array_map(function ($crmId) use ($inputClass) {\n $input = new $inputClass();\n $input->setId($crmId);\n\n return $input;\n }, $crmIds);\n\n $batchReadRequest->setInputs($inputs);\n $batchReadRequest->setProperties($fields);\n\n return $batchReadRequest;\n }\n\n private function validateApiResponse($response, string $objectType): void\n {\n if (! $response) {\n throw new CrmException(\"HubSpot API returned null response for {$objectType} batch read\");\n }\n }\n\n private function processApiResults($response): array\n {\n $results = [];\n $responseResults = $response->getResults();\n\n if ($responseResults) {\n foreach ($responseResults as $object) {\n if ($object && $object->getId()) {\n $results[$object->getId()] = [\n 'id' => $object->getId(),\n 'properties' => $object->getProperties() ?: [],\n ];\n }\n }\n }\n\n return $results;\n }\n\n private function logBatchResults(string $objectType, array $crmIds, array $results): void\n {\n $this->log->info(\"[HubSpot] Batch fetched {$objectType}\", [\n 'requested_count' => count($crmIds),\n 'returned_count' => count($results),\n 'crm_ids' => $crmIds,\n ]);\n }\n\n private function handleBatchError(\\Throwable $e, string $objectType, array $crmIds): void\n {\n $errorMessage = $e->getMessage() ?: 'Unknown error';\n $errorTrace = $e->getTraceAsString() ?: 'No trace available';\n\n $this->log->error(\"[HubSpot] Failed to batch fetch {$objectType}\", [\n 'crm_ids' => $crmIds,\n 'error' => $errorMessage,\n 'trace' => $errorTrace,\n ]);\n\n throw new CrmException(\"Failed to batch fetch {$objectType}: \" . $errorMessage);\n }\n\n /**\n * Batch read multiple opportunities by their CRM IDs\n *\n * @param array<string> $crmIds Array of HubSpot deal IDs (max 100)\n * @param array<string> $fields Array of property names to fetch\n *\n * @return array<string, array> Array keyed by CRM ID with opportunity data\n */\n public function getOpportunitiesByIds(array $crmIds, array $fields): array\n {\n return $this->batchReadObjects('deals', $crmIds, $fields);\n }\n\n /**\n * Batch read multiple companies by their CRM IDs\n *\n * @param array<string> $crmIds Array of HubSpot company IDs (max 100)\n * @param array<string> $fields Array of property names to fetch\n *\n * @return array<string, array> Array keyed by CRM ID with company data\n */\n public function getCompaniesByIds(array $crmIds, array $fields): array\n {\n return $this->batchReadObjects('companies', $crmIds, $fields);\n }\n\n /**\n * Batch read multiple contacts by their CRM IDs\n *\n * @param array<string> $crmIds Array of HubSpot contact IDs (max 100)\n * @param array<string> $fields Array of property names to fetch\n *\n * @return array<string, array> Array keyed by CRM ID with contact data\n */\n public function getContactsByIds(array $crmIds, array $fields): array\n {\n return $this->batchReadObjects('contacts', $crmIds, $fields);\n }\n\n /**\n * @throws CompanyApiException\n * @throws CrmException\n */\n public function getAccountById(string $crmId, array $fields): array\n {\n try {\n $company = $this->getNewInstance()->crm()->companies()->basicApi()->getById(\n $crmId,\n implode(',', $fields),\n );\n } catch (CompanyApiException $e) {\n $this->log->info('[Hubspot] Failed to fetch account', [\n 'crm_id' => $crmId,\n 'reason' => $e->getMessage(),\n ]);\n\n throw $e;\n }\n\n if (! $company instanceof CompaniesWithAssociations) {\n throw new CrmException('Account not found');\n }\n\n return [\n 'id' => $company->getId(),\n 'properties' => $company->getProperties(),\n ];\n }\n\n /**\n * @throws ContactApiException\n * @throws CrmException\n */\n public function getContactById(string $crmId, array $fields): array\n {\n try {\n $contact = $this->getNewInstance()->crm()->contacts()->basicApi()->getById(\n $crmId,\n implode(',', $fields)\n );\n } catch (ContactApiException $e) {\n $this->log->info('[Hubspot] Failed to fetch contact', [\n 'crm_id' => $crmId,\n 'reason' => $e->getMessage(),\n ]);\n\n throw $e;\n }\n\n if (! $contact instanceof ContactsWithAssociations) {\n throw new CrmException('Contact not found');\n }\n\n return [\n 'id' => $contact->getId(),\n 'properties' => $contact->getProperties(),\n ];\n }\n\n /**\n * This is email search request that Hubspot offers as GET (more generous quota)\n */\n public function getContactByEmail(string $email, array $fields = []): array\n {\n try {\n $contact = $this->getNewInstance()->crm()->contacts()->basicApi()->getById(\n $email,\n implode(',', $fields),\n null,\n false,\n 'email'\n );\n\n return [\n 'id' => $contact->getId(),\n 'properties' => $contact->getProperties(),\n ];\n } catch (ContactApiException $e) {\n $this->log->info('[Hubspot] Failed to fetch contact', [\n 'email' => $email,\n 'reason' => $e->getMessage(),\n ]);\n\n return [];\n }\n }\n\n /**\n * @throws CrmException\n */\n public function fetchProperty(string $objectType, string $propertyId): Property\n {\n $result = $this->getNewInstance()->crm()->properties()->coreApi()->getByName($objectType, $propertyId);\n\n if (! $result instanceof Property) {\n $this->log->error('[Hubspot] Failed to fetch property', [\n 'object_type' => $objectType,\n 'property_id' => $propertyId,\n 'reason' => $result->getMessage(),\n ]);\n\n throw new CrmException('Failed to fetch property');\n }\n\n return $result;\n }\n\n /**\n * @return array<CrmFieldOption>\n */\n public function fetchPropertyOptions(string $objectType, string $propertyId): array\n {\n /** @var array<CrmFieldOption> */\n return $this->fetchProperty($objectType, $propertyId)->getOptions();\n }\n\n /**\n * @return array<array{id:string, label:string, deleted:bool}>\n */\n public function fetchCallDispositions(): array\n {\n /** @var Response $response */\n $response = $this->getInstance()->engagements()->getCallDispositions();\n\n /**\n * @var array<array{\n * id:string,\n * label:string,\n * deleted: bool\n * }>\n */\n return $response->toArray();\n }\n\n /**\n * @return array<CrmFieldOption>\n */\n public function fetchOpportunityPipelineStages(): array\n {\n $stages = [];\n $apiResponse = $this->getNewInstance()->crm()->pipelines()->pipelinesApi()->getAll('deals');\n\n if ($apiResponse instanceof Error) {\n $this->log->error('[Hubspot] Failed to fetch opportunity pipelines', [\n 'reason' => $apiResponse->getMessage(),\n ]);\n\n return [];\n }\n\n foreach ($apiResponse->getResults() as $pipeline) {\n $pipelineStages = array_map(\n static function (PipelineStage $stage) {\n return [\n 'id' => $stage->getId(),\n 'label' => $stage->getLabel(),\n ];\n },\n $pipeline->getStages()\n );\n\n $stages = array_merge($stages, $pipelineStages);\n }\n\n return $stages;\n }\n\n public function fetchOpportunityPipelines(): array\n {\n $pipelines = [];\n\n try {\n $apiResponse = $this->makeRequest('/crm/v3/pipelines/deals');\n } catch (\\Exception $e) {\n $this->log->info('[Hubspot] Failed to fetch opportunity pipelines', [\n 'reason' => $e->getMessage(),\n ]);\n\n return [];\n }\n\n $response = $apiResponse->toArray();\n\n foreach ($response['results'] as $pipeline) {\n $pipelines[] = [\n 'id' => $pipeline['id'],\n 'label' => $pipeline['label'],\n ];\n }\n\n return $pipelines;\n }\n\n /**\n * @return array<CrmFieldOption>\n */\n public function fetchMeetingOutcomeFieldOptions(Field $field): array\n {\n return $field->getCrmProviderId() === 'meetingOutcome'\n ? $this->fetchMeetingOutcomeTypes()\n : $this->fetchCallActivityTypes();\n }\n\n public function fetchMeetingOutcomeTypes(): array\n {\n return $this->extractMeetingTypeOptions(\n 'https://api.hubapi.com/crm/v3/properties/meeting/hs_meeting_outcome'\n );\n }\n\n public function fetchCallActivityTypes(): array\n {\n return $this->extractMeetingTypeOptions(\n 'https://api.hubapi.com/crm/v3/properties/call/hs_activity_type'\n );\n }\n\n private function extractMeetingTypeOptions(string $endpoint): array\n {\n /** @var Response $response */\n $response = $this->getInstance()\n ->getClient()\n ->request('GET', $endpoint);\n\n /**\n * @var array<array{\n * value: string,\n * label: string,\n * displayOrder: int\n * }> $optionData\n */\n $optionData = $response->toArray()['options'] ?? [];\n\n $options = [];\n foreach ($optionData as $item) {\n $options[] = [\n 'id' => $item['value'],\n 'value' => $item['value'],\n 'label' => $item['label'],\n 'display_order' => $item['displayOrder'],\n ];\n }\n\n return $options;\n }\n\n /**\n * @return array<CrmFieldOption>\n */\n public function fetchDispositionFieldOptions(): array\n {\n $options = [];\n\n $dispositions = $this->fetchCallDispositions();\n\n foreach ($dispositions as $disposition) {\n if ($disposition['deleted'] !== false) {\n continue;\n }\n\n $option['value'] = $disposition['id'];\n $option['id'] = $disposition['id'];\n $option['label'] = $disposition['label'];\n\n $options[] = $option;\n }\n\n return $options;\n }\n\n /**\n * @return array<CrmFieldOption>\n */\n public function fetchOpportunityFieldOptions(Field $field): array\n {\n if ($field->isStageField()) {\n return $this->fetchOpportunityPipelineStages();\n }\n\n if ($field->isPipelineField()) {\n return $this->fetchOpportunityPipelines();\n }\n\n return $this->fetchPropertyOptions('deals', $field->getCrmProviderId());\n }\n\n /**\n * @throws BadRequest\n * @throws HubspotException\n */\n public function makeRequest(string $endpoint, $method = 'GET', $payload = [], ?string $queryString = null)\n {\n $endpoint = self::BASE_URL . $endpoint;\n\n if ($method === 'GET') {\n $response = $this->getInstance()->getClient()?->request(\n method: $method,\n endpoint: $endpoint,\n query_string: $queryString\n );\n } else {\n $response = $this->getInstance()->getClient()->request($method, $endpoint, [\n 'json' => ($payload),\n ]);\n }\n \n $max = $response->getHeaderLine('X-HubSpot-RateLimit-Max'); // \"110\"\n $remaining = $response->getHeaderLine('X-HubSpot-RateLimit-Remaining'); // \"109\"\n $interval = $response->getHeaderLine('X-HubSpot-RateLimit-Interval-Milliseconds'); // \"10000\"\n $body = json_decode((string) $response->getBody(), true);\n \n \\Illuminate\\Support\\Facades\\Log::channel('custom_channel')->info('$max ' . PHP_EOL . print_r($max, true));\n \\Illuminate\\Support\\Facades\\Log::channel('custom_channel')->info('$remaining ' . PHP_EOL . print_r($remaining, true));\n \\Illuminate\\Support\\Facades\\Log::channel('custom_channel')->info('$remaining ' . PHP_EOL . print_r($remaining, true));\n \n return $response;\n }\n\n /**\n * @throws BadRequest\n * @throws HubspotException\n */\n public function createMeeting(array $payload): Response\n {\n $endpoint = '/crm/v3/objects/meetings';\n\n return $this->makeRequest($endpoint, 'POST', $payload);\n }\n\n /**\n * @throws BadRequest\n * @throws HubspotException\n */\n public function updateMeeting(string $meetingId, array $payload): Response\n {\n $endpoint = '/crm/v3/objects/meetings/' . $meetingId;\n\n return $this->makeRequest($endpoint, 'PATCH', $payload);\n }\n\n /**\n * @throws \\Exception\n */\n public function createNote(\n string $body,\n string $ownerId,\n int $timestamp,\n string $objectId,\n NoteObject $noteObject\n ): ?string {\n try {\n $noteInput = new SimplePublicObjectInput([\n 'properties' => [\n 'hs_note_body' => $body,\n 'hubspot_owner_id' => $ownerId,\n 'hs_timestamp' => $timestamp,\n ],\n ]);\n\n // Create note\n $note = $this->getNewInstance()->crm()->objects()->basicApi()->create('note', $noteInput);\n\n $this->getNewInstance()->crm()->objects()->associationsApi()->create(\n 'note',\n $note->getId(),\n $this->getNoteObject($noteObject),\n $objectId,\n $this->getNoteAssociationType($noteObject),\n );\n\n return $note->getId();\n } catch (\\Exception $e) {\n $this->log->error('[Hubspot] Failed to create note', [\n 'objectId' => $objectId,\n 'noteObject' => $noteObject->getObjectType(),\n 'reason' => $e->getMessage(),\n ]);\n\n \\Sentry::captureException($e);\n }\n\n return null;\n }\n\n public function updateEngagement(string $objectId, array $engagement, array $metadata): void\n {\n $this->getInstance()->engagements()->update($objectId, $engagement, $metadata);\n }\n\n public function getEngagementData(string $engagementId): array\n {\n $engagement = $this->getInstance()->engagements()->get($engagementId);\n\n return $engagement->toArray();\n }\n\n public function createEngagement(array $engagement, array $associations, array $metadata): Response\n {\n return $this->getInstance()\n ->engagements()\n ->create($engagement, $associations, $metadata);\n }\n\n public function isUnauthorizedException(\\Exception $e): bool\n {\n // Check for specific HubSpot API exception types first\n if ($e instanceof BadRequest) {\n // BadRequest can contain 401 status codes\n return $e->getCode() === 401;\n }\n\n // Check for HTTP client exceptions with status codes\n if ($e instanceof \\GuzzleHttp\\Exception\\RequestException && $e->hasResponse()) {\n $response = $e->getResponse();\n if ($response !== null) {\n return $response->getStatusCode() === 401;\n }\n }\n\n // Check for Guzzle HTTP exceptions\n if ($e instanceof \\GuzzleHttp\\Exception\\ClientException) {\n return $e->getCode() === 401;\n }\n\n // Fallback to string matching as last resort, but be more specific\n $message = strtolower($e->getMessage());\n\n return str_contains($message, '401 unauthorized') ||\n str_contains($message, 'http 401') ||\n str_contains($message, 'status code 401') ||\n (preg_match('/\\b401\\b/', $message) && str_contains($message, 'unauthorized'));\n }\n\n /**\n * Validates and refreshes the access token if needed before API requests.\n * This ensures long-running processes don't fail due to token expiration.\n *\n * @throws SocialAccountTokenInvalidException\n */\n public function ensureValidToken(): void\n {\n if ($this->oauthAccount === null) {\n return;\n }\n\n $newToken = $this->tokenManager->ensureValidToken($this->oauthAccount);\n if ($newToken !== null) {\n $this->accessToken = $newToken;\n }\n }\n\n public function getConfig()\n {\n return $this->config;\n }\n\n // returns only active (archived=false)\n public function getOwners(): array\n {\n return $this->getNewInstance()->crm()->owners()->getAll();\n }\n\n /**\n * @param bool $archived\n *\n * @return array<Owner>|[]\n */\n public function getOwnersArchived(bool $archived = true): array\n {\n $endpoint = '/crm/v3/owners';\n $queryParams = [\n 'archived' => $archived ? 'true' : 'false',\n ];\n $queryString = http_build_query($queryParams);\n\n $owners = [];\n\n try {\n $response = $this->makeRequest(endpoint: $endpoint, queryString: $queryString);\n $responseData = $response?->toArray();\n\n foreach ($responseData['results'] as $result) {\n try {\n $owners[] = Owner::create($result);\n } catch (Throwable $e) {\n $this->log->error('[HubSpot] Failed to process owner data', [\n 'result' => $result,\n 'error' => $e->getMessage(),\n ]);\n\n continue;\n }\n }\n } catch (Throwable $e) {\n $this->log->error('HubSpot] Failed to fetch owners', [\n 'archived' => $archived,\n 'error' => $e->getMessage(),\n ]);\n\n return [];\n }\n\n return $owners;\n }\n\n public function getMeeting(string $engagementId): ObjectWithAssociations\n {\n return $this->getNewInstance()->crm()->objects()->basicApi()\n ->getById('meeting', $engagementId, null, 'contact,company,deal');\n }\n\n public function deleteEngagement(string $engagementId): void\n {\n $this->getInstance()->engagements()->delete((int) $engagementId);\n }\n\n public function getAssociationsData(array $ids, string $fromObject, string $toObject): array\n {\n $associationData = [];\n $idChunks = array_chunk($ids, self::ASSOCIATIONS_BATCH_SIZE_LIMIT);\n\n foreach ($idChunks as $idChunk) {\n try {\n $batchInput = new \\HubSpot\\Client\\Crm\\Associations\\Model\\BatchInputPublicObjectId();\n $batchInput->setInputs(array_map(function ($id) {\n $publicObjectId = new \\HubSpot\\Client\\Crm\\Associations\\Model\\PublicObjectId();\n $publicObjectId->setId($id);\n\n return $publicObjectId;\n }, $idChunk));\n\n $associatedObjectsData = $this\n ->getNewInstance()\n ->crm()\n ->associations()\n ->batchApi()\n ->read($fromObject, $toObject, $batchInput);\n\n if ($associatedObjectsData instanceof \\HubSpot\\Client\\Crm\\Associations\\Model\\BatchResponsePublicAssociationMulti) {\n foreach ($associatedObjectsData->getResults() as $association) {\n $from = $association->getFrom()->getId();\n $toAssociations = $association->getTo();\n\n if (! empty($toAssociations)) {\n $associationData[$from] = array_map(function ($item) {\n return $item->getId();\n }, $toAssociations);\n }\n }\n }\n } catch (\\Exception $e) {\n $this->log->error('[Hubspot] Failed to fetch associations', [\n 'from_object' => $fromObject,\n 'to_object' => $toObject,\n 'reason' => $e->getMessage(),\n ]);\n }\n }\n\n return $associationData;\n }\n\n /**\n * @throws \\Exception\n */\n private function getNoteAssociationType(NoteObject $noteObject): string\n {\n return match($noteObject) {\n NoteObject::Opportunity => 'note_to_deal',\n NoteObject::Lead, NoteObject::Contact => 'note_to_contact', // or 'note_to_lead' if your portal supports it\n NoteObject::Account => 'note_to_company',\n NoteObject::Call, NoteObject::Event => throw new \\Exception('Not supported'),\n };\n }\n\n /**\n * @throws \\Exception\n */\n private function getNoteObject(NoteObject $noteObject): string\n {\n return match($noteObject) {\n NoteObject::Opportunity => 'deal',\n NoteObject::Lead, NoteObject::Contact => 'contact',\n NoteObject::Account => 'company',\n NoteObject::Call, NoteObject::Event => throw new \\Exception('Not supported'),\n };\n }\n\n public function addAssociations(string $objectType, string $associationType, array $payload): Response\n {\n $endpoint = \"/crm/v4/associations/$objectType/$associationType/batch/create\";\n\n return $this->makeRequest($endpoint, 'POST', $payload);\n }\n\n public function removeAssociations(string $objectType, string $associationType, array $payload): Response\n {\n $endpoint = \"/crm/v4/associations/$objectType/$associationType/batch/archive\";\n\n return $this->makeRequest($endpoint, 'POST', $payload);\n }\n}","depth":4,"on_screen":true,"value":"<?php\n\ndeclare(strict_types=1);\n\nnamespace Jiminny\\Services\\Crm\\Hubspot;\n\nuse HubSpot\\Client\\Crm\\Deals\\ApiException as DealApiException;\nuse HubSpot\\Client\\Crm\\Contacts\\ApiException as ContactApiException;\nuse HubSpot\\Client\\Crm\\Companies\\ApiException as CompanyApiException;\nuse HubSpot\\Client\\Crm\\Contacts\\Model\\SimplePublicObjectWithAssociations as ContactsWithAssociations;\nuse HubSpot\\Client\\Crm\\Companies\\Model\\SimplePublicObjectWithAssociations as CompaniesWithAssociations;\nuse HubSpot\\Client\\Crm\\Deals\\Model\\SimplePublicObjectWithAssociations as DealWithAssociations;\nuse HubSpot\\Client\\Crm\\Objects\\Model\\SimplePublicObjectInput;\nuse HubSpot\\Client\\Crm\\Objects\\Model\\SimplePublicObjectWithAssociations as ObjectWithAssociations;\nuse HubSpot\\Client\\Crm\\Pipelines\\Model\\Error;\nuse HubSpot\\Client\\Crm\\Pipelines\\Model\\PipelineStage;\nuse HubSpot\\Client\\Crm\\Properties\\Model\\Property;\nuse HubSpot\\Discovery\\Discovery;\nuse Jiminny\\Component\\Utility\\Service\\ProviderRateLimiter;\nuse Jiminny\\Exceptions\\CrmException;\nuse Jiminny\\Exceptions\\RateLimitException;\nuse Jiminny\\Exceptions\\SocialAccountTokenInvalidException;\nuse Jiminny\\Jobs\\Crm\\NoteObject;\nuse Jiminny\\Models\\Crm\\Field;\nuse Jiminny\\Services\\Crm\\BaseClient;\nuse Jiminny\\Services\\Crm\\Hubspot\\DTO\\Response\\Owner;\nuse Jiminny\\Services\\SocialAccountService;\nuse SevenShores\\Hubspot\\Exceptions\\BadRequest;\nuse SevenShores\\Hubspot\\Exceptions\\HubspotException;\nuse SevenShores\\Hubspot\\Factory;\nuse SevenShores\\Hubspot\\Http\\Response;\nuse Jiminny\\Services\\Crm\\Hubspot\\Pagination\\HubspotPaginationService;\nuse Throwable;\n\n/**\n * @phpstan-type CrmFieldOption array{id:string, label:string, value?:string}\n */\nclass Client extends BaseClient implements HubspotClientInterface\n{\n public const string MIN_API_VERSION = '2';\n\n public const string BASE_URL = 'https://api.hubapi.com';\n\n public const int ASSOCIATIONS_BATCH_SIZE_LIMIT = 1000;\n\n private HubspotPaginationService $paginationService;\n private HubspotTokenManager $tokenManager;\n private ProviderRateLimiter $rateLimiter;\n\n public function __construct(\n SocialAccountService $socialAccountService,\n HubspotPaginationService $paginationService,\n HubspotTokenManager $tokenManager,\n ProviderRateLimiter $rateLimiter,\n ) {\n parent::__construct($socialAccountService);\n $this->paginationService = $paginationService;\n $this->tokenManager = $tokenManager;\n $this->rateLimiter = $rateLimiter;\n\n $this->setBaseUrl(self::BASE_URL);\n $this->setVersion(self::MIN_API_VERSION);\n }\n\n /**\n * Single entry point for every HubSpot API call. Enforces the per-portal\n * rate limit configured in the rate_limits table (morphed to the current\n * Configuration) and reacts to a real 429 from HubSpot by translating it\n * into a RateLimitException carrying Retry-After.\n *\n * Wrap any outbound HubSpot call (SDK or raw HTTP) like:\n *\n * $this->executeRequest(fn () => $this->getNewInstance()->crm()->...);\n *\n * @template T\n * @param callable(): T $apiCall\n * @return T\n *\n * @throws RateLimitException\n */\n private function executeRequest(callable $apiCall)\n {\n if (! $this->rateLimiter->canMakeRequest($this->config)) {\n $retryAfter = $this->rateLimiter->requestAvailableIn($this->config);\n\n $this->log->warning('[Hubspot] Rate limit exceeded, deferring request', [\n 'team_id' => $this->config->team_id,\n 'config_id' => $this->config->getId(),\n 'retry_after' => $retryAfter,\n ]);\n\n throw new RateLimitException(\n 'Hubspot rate limit reached for configuration ' . $this->config->getId(),\n $retryAfter,\n );\n }\n\n $this->rateLimiter->incrementRequestCount($this->config);\n\n try {\n return $apiCall();\n } catch (Throwable $e) {\n if ($this->isHubspotRateLimit($e)) {\n $retryAfter = $this->parseRetryAfter($e);\n\n $this->log->warning('[Hubspot] Received 429 from API', [\n 'team_id' => $this->config->team_id,\n 'config_id' => $this->config->getId(),\n 'retry_after' => $retryAfter,\n 'reason' => $e->getMessage(),\n ]);\n\n throw new RateLimitException('Hubspot returned 429', $retryAfter, $e);\n }\n\n throw $e;\n }\n }\n\n private function isHubspotRateLimit(Throwable $e): bool\n {\n return method_exists($e, 'getCode') && (int) $e->getCode() === 429;\n }\n\n private function parseRetryAfter(Throwable $e): int\n {\n if (method_exists($e, 'getResponseHeaders')) {\n $headers = $e->getResponseHeaders() ?: [];\n $value = $headers['Retry-After'] ?? $headers['retry-after'] ?? null;\n if (is_array($value)) {\n $value = $value[0] ?? null;\n }\n if (is_numeric($value)) {\n return (int) $value;\n }\n }\n\n return 10;\n }\n\n public function getMinimumApiVersion(): string\n {\n return self::MIN_API_VERSION;\n }\n\n public function getInstance(): Factory\n {\n return new Factory([\n 'key' => $this->accessToken,\n 'oauth2' => true,\n 'base_url' => $this->baseUrl,\n ]);\n }\n\n public function getNewInstance(): Discovery\n {\n return \\HubSpot\\Factory::createWithAccessToken($this->accessToken);\n }\n\n /**\n * Secondly and daily limits for Hubspot API\n *\n * Product Tier: Free & Starter | Professional & Enterprise | API add-on (any tier)\n * Burst: 100/10 seconds | 150/10 seconds | 200/10 seconds\n * Daily: 250,000 | 500,000 | 1,000,000\n *\n * Official documentation states: The search endpoints are rate limited to five requests per second.\n * Since with 5 RPS were still hitting secondly rate limits we lowered it to 4\n */\n public function getPaginatedData(array $payload, string $type, int $offset = 0): array\n {\n $total = 0;\n $lastId = null;\n $rows = [];\n foreach ($this->getPaginatedDataGenerator($payload, $type, $offset, $total, $lastId) as $row) {\n $rows[] = $row;\n }\n\n return ['results' => $rows, 'total' => $total, 'last_record' => $lastId];\n }\n\n /**\n * @throws HubspotException\n * @throws SocialAccountTokenInvalidException\n * @throws BadRequest\n */\n public function getPaginatedDataGenerator(\n array $payload,\n string $type,\n int $offset = 0,\n int &$total = 0,\n ?string &$lastRecordId = null\n ): \\Generator {\n return $this->paginationService->getPaginatedDataGenerator(\n $this,\n $payload,\n $type,\n $offset,\n $total,\n $lastRecordId\n );\n }\n\n /**\n * @throws DealApiException\n * @throws CrmException\n */\n public function getOpportunityById(string $crmId, array $fields): array\n {\n try {\n $deal = $this->executeRequest(fn () => $this->getNewInstance()->crm()->deals()->basicApi()->getById(\n $crmId,\n implode(',', $fields),\n 'companies,contacts'\n ));\n } catch (DealApiException $e) {\n $this->log->info('[Hubspot] Failed to fetch opportunity', [\n 'crm_id' => $crmId,\n 'reason' => $e->getMessage(),\n ]);\n\n throw $e;\n }\n\n if (! $deal instanceof DealWithAssociations) {\n throw new CrmException('Deal not found');\n }\n\n return [\n 'id' => $deal->getId(),\n 'properties' => $deal->getProperties(),\n 'associations' => $deal->getAssociations(),\n ];\n }\n\n /**\n * Generic batch read method for HubSpot objects\n *\n * @param string $objectType The object type ('deals', 'companies', 'contacts')\n * @param array<string> $crmIds Array of HubSpot object IDs (max 100)\n * @param array<string> $fields Array of property names to fetch\n *\n * @return array<string, array> Array keyed by CRM ID with object data\n */\n private function batchReadObjects(string $objectType, array $crmIds, array $fields): array\n {\n if (empty($crmIds)) {\n return [];\n }\n\n $this->validateBatchSize($objectType, $crmIds);\n $this->ensureValidToken();\n\n try {\n $batchConfig = $this->createBatchConfiguration($objectType);\n $batchReadRequest = $this->prepareBatchRequest($batchConfig, $crmIds, $fields);\n $response = $batchConfig['api']->read($batchReadRequest);\n\n $this->validateApiResponse($response, $objectType);\n\n $results = $this->processApiResults($response);\n $this->logBatchResults($objectType, $crmIds, $results);\n\n return $results;\n } catch (\\Throwable $e) {\n $this->handleBatchError($e, $objectType, $crmIds);\n }\n }\n\n private function validateBatchSize(string $objectType, array $crmIds): void\n {\n if (count($crmIds) > 100) {\n throw new \\InvalidArgumentException(\"Batch size cannot exceed 100 {$objectType}\");\n }\n }\n\n private function createBatchConfiguration(string $objectType): array\n {\n $configurations = [\n 'deals' => [\n 'batchReadRequest' => new \\HubSpot\\Client\\Crm\\Deals\\Model\\BatchReadInputSimplePublicObjectId(),\n 'inputClass' => \\HubSpot\\Client\\Crm\\Deals\\Model\\SimplePublicObjectId::class,\n 'api' => $this->getNewInstance()->crm()->deals()->batchApi(),\n ],\n 'companies' => [\n 'batchReadRequest' => new \\HubSpot\\Client\\Crm\\Companies\\Model\\BatchReadInputSimplePublicObjectId(),\n 'inputClass' => \\HubSpot\\Client\\Crm\\Companies\\Model\\SimplePublicObjectId::class,\n 'api' => $this->getNewInstance()->crm()->companies()->batchApi(),\n ],\n 'contacts' => [\n 'batchReadRequest' => new \\HubSpot\\Client\\Crm\\Contacts\\Model\\BatchReadInputSimplePublicObjectId(),\n 'inputClass' => \\HubSpot\\Client\\Crm\\Contacts\\Model\\SimplePublicObjectId::class,\n 'api' => $this->getNewInstance()->crm()->contacts()->batchApi(),\n ],\n ];\n\n if (! isset($configurations[$objectType])) {\n throw new \\InvalidArgumentException(\"Unsupported object type: {$objectType}\");\n }\n\n return $configurations[$objectType];\n }\n\n private function prepareBatchRequest(array $batchConfig, array $crmIds, array $fields): object\n {\n $batchReadRequest = $batchConfig['batchReadRequest'];\n $inputClass = $batchConfig['inputClass'];\n\n $inputs = array_map(function ($crmId) use ($inputClass) {\n $input = new $inputClass();\n $input->setId($crmId);\n\n return $input;\n }, $crmIds);\n\n $batchReadRequest->setInputs($inputs);\n $batchReadRequest->setProperties($fields);\n\n return $batchReadRequest;\n }\n\n private function validateApiResponse($response, string $objectType): void\n {\n if (! $response) {\n throw new CrmException(\"HubSpot API returned null response for {$objectType} batch read\");\n }\n }\n\n private function processApiResults($response): array\n {\n $results = [];\n $responseResults = $response->getResults();\n\n if ($responseResults) {\n foreach ($responseResults as $object) {\n if ($object && $object->getId()) {\n $results[$object->getId()] = [\n 'id' => $object->getId(),\n 'properties' => $object->getProperties() ?: [],\n ];\n }\n }\n }\n\n return $results;\n }\n\n private function logBatchResults(string $objectType, array $crmIds, array $results): void\n {\n $this->log->info(\"[HubSpot] Batch fetched {$objectType}\", [\n 'requested_count' => count($crmIds),\n 'returned_count' => count($results),\n 'crm_ids' => $crmIds,\n ]);\n }\n\n private function handleBatchError(\\Throwable $e, string $objectType, array $crmIds): void\n {\n $errorMessage = $e->getMessage() ?: 'Unknown error';\n $errorTrace = $e->getTraceAsString() ?: 'No trace available';\n\n $this->log->error(\"[HubSpot] Failed to batch fetch {$objectType}\", [\n 'crm_ids' => $crmIds,\n 'error' => $errorMessage,\n 'trace' => $errorTrace,\n ]);\n\n throw new CrmException(\"Failed to batch fetch {$objectType}: \" . $errorMessage);\n }\n\n /**\n * Batch read multiple opportunities by their CRM IDs\n *\n * @param array<string> $crmIds Array of HubSpot deal IDs (max 100)\n * @param array<string> $fields Array of property names to fetch\n *\n * @return array<string, array> Array keyed by CRM ID with opportunity data\n */\n public function getOpportunitiesByIds(array $crmIds, array $fields): array\n {\n return $this->batchReadObjects('deals', $crmIds, $fields);\n }\n\n /**\n * Batch read multiple companies by their CRM IDs\n *\n * @param array<string> $crmIds Array of HubSpot company IDs (max 100)\n * @param array<string> $fields Array of property names to fetch\n *\n * @return array<string, array> Array keyed by CRM ID with company data\n */\n public function getCompaniesByIds(array $crmIds, array $fields): array\n {\n return $this->batchReadObjects('companies', $crmIds, $fields);\n }\n\n /**\n * Batch read multiple contacts by their CRM IDs\n *\n * @param array<string> $crmIds Array of HubSpot contact IDs (max 100)\n * @param array<string> $fields Array of property names to fetch\n *\n * @return array<string, array> Array keyed by CRM ID with contact data\n */\n public function getContactsByIds(array $crmIds, array $fields): array\n {\n return $this->batchReadObjects('contacts', $crmIds, $fields);\n }\n\n /**\n * @throws CompanyApiException\n * @throws CrmException\n */\n public function getAccountById(string $crmId, array $fields): array\n {\n try {\n $company = $this->getNewInstance()->crm()->companies()->basicApi()->getById(\n $crmId,\n implode(',', $fields),\n );\n } catch (CompanyApiException $e) {\n $this->log->info('[Hubspot] Failed to fetch account', [\n 'crm_id' => $crmId,\n 'reason' => $e->getMessage(),\n ]);\n\n throw $e;\n }\n\n if (! $company instanceof CompaniesWithAssociations) {\n throw new CrmException('Account not found');\n }\n\n return [\n 'id' => $company->getId(),\n 'properties' => $company->getProperties(),\n ];\n }\n\n /**\n * @throws ContactApiException\n * @throws CrmException\n */\n public function getContactById(string $crmId, array $fields): array\n {\n try {\n $contact = $this->getNewInstance()->crm()->contacts()->basicApi()->getById(\n $crmId,\n implode(',', $fields)\n );\n } catch (ContactApiException $e) {\n $this->log->info('[Hubspot] Failed to fetch contact', [\n 'crm_id' => $crmId,\n 'reason' => $e->getMessage(),\n ]);\n\n throw $e;\n }\n\n if (! $contact instanceof ContactsWithAssociations) {\n throw new CrmException('Contact not found');\n }\n\n return [\n 'id' => $contact->getId(),\n 'properties' => $contact->getProperties(),\n ];\n }\n\n /**\n * This is email search request that Hubspot offers as GET (more generous quota)\n */\n public function getContactByEmail(string $email, array $fields = []): array\n {\n try {\n $contact = $this->getNewInstance()->crm()->contacts()->basicApi()->getById(\n $email,\n implode(',', $fields),\n null,\n false,\n 'email'\n );\n\n return [\n 'id' => $contact->getId(),\n 'properties' => $contact->getProperties(),\n ];\n } catch (ContactApiException $e) {\n $this->log->info('[Hubspot] Failed to fetch contact', [\n 'email' => $email,\n 'reason' => $e->getMessage(),\n ]);\n\n return [];\n }\n }\n\n /**\n * @throws CrmException\n */\n public function fetchProperty(string $objectType, string $propertyId): Property\n {\n $result = $this->getNewInstance()->crm()->properties()->coreApi()->getByName($objectType, $propertyId);\n\n if (! $result instanceof Property) {\n $this->log->error('[Hubspot] Failed to fetch property', [\n 'object_type' => $objectType,\n 'property_id' => $propertyId,\n 'reason' => $result->getMessage(),\n ]);\n\n throw new CrmException('Failed to fetch property');\n }\n\n return $result;\n }\n\n /**\n * @return array<CrmFieldOption>\n */\n public function fetchPropertyOptions(string $objectType, string $propertyId): array\n {\n /** @var array<CrmFieldOption> */\n return $this->fetchProperty($objectType, $propertyId)->getOptions();\n }\n\n /**\n * @return array<array{id:string, label:string, deleted:bool}>\n */\n public function fetchCallDispositions(): array\n {\n /** @var Response $response */\n $response = $this->getInstance()->engagements()->getCallDispositions();\n\n /**\n * @var array<array{\n * id:string,\n * label:string,\n * deleted: bool\n * }>\n */\n return $response->toArray();\n }\n\n /**\n * @return array<CrmFieldOption>\n */\n public function fetchOpportunityPipelineStages(): array\n {\n $stages = [];\n $apiResponse = $this->getNewInstance()->crm()->pipelines()->pipelinesApi()->getAll('deals');\n\n if ($apiResponse instanceof Error) {\n $this->log->error('[Hubspot] Failed to fetch opportunity pipelines', [\n 'reason' => $apiResponse->getMessage(),\n ]);\n\n return [];\n }\n\n foreach ($apiResponse->getResults() as $pipeline) {\n $pipelineStages = array_map(\n static function (PipelineStage $stage) {\n return [\n 'id' => $stage->getId(),\n 'label' => $stage->getLabel(),\n ];\n },\n $pipeline->getStages()\n );\n\n $stages = array_merge($stages, $pipelineStages);\n }\n\n return $stages;\n }\n\n public function fetchOpportunityPipelines(): array\n {\n $pipelines = [];\n\n try {\n $apiResponse = $this->makeRequest('/crm/v3/pipelines/deals');\n } catch (\\Exception $e) {\n $this->log->info('[Hubspot] Failed to fetch opportunity pipelines', [\n 'reason' => $e->getMessage(),\n ]);\n\n return [];\n }\n\n $response = $apiResponse->toArray();\n\n foreach ($response['results'] as $pipeline) {\n $pipelines[] = [\n 'id' => $pipeline['id'],\n 'label' => $pipeline['label'],\n ];\n }\n\n return $pipelines;\n }\n\n /**\n * @return array<CrmFieldOption>\n */\n public function fetchMeetingOutcomeFieldOptions(Field $field): array\n {\n return $field->getCrmProviderId() === 'meetingOutcome'\n ? $this->fetchMeetingOutcomeTypes()\n : $this->fetchCallActivityTypes();\n }\n\n public function fetchMeetingOutcomeTypes(): array\n {\n return $this->extractMeetingTypeOptions(\n 'https://api.hubapi.com/crm/v3/properties/meeting/hs_meeting_outcome'\n );\n }\n\n public function fetchCallActivityTypes(): array\n {\n return $this->extractMeetingTypeOptions(\n 'https://api.hubapi.com/crm/v3/properties/call/hs_activity_type'\n );\n }\n\n private function extractMeetingTypeOptions(string $endpoint): array\n {\n /** @var Response $response */\n $response = $this->getInstance()\n ->getClient()\n ->request('GET', $endpoint);\n\n /**\n * @var array<array{\n * value: string,\n * label: string,\n * displayOrder: int\n * }> $optionData\n */\n $optionData = $response->toArray()['options'] ?? [];\n\n $options = [];\n foreach ($optionData as $item) {\n $options[] = [\n 'id' => $item['value'],\n 'value' => $item['value'],\n 'label' => $item['label'],\n 'display_order' => $item['displayOrder'],\n ];\n }\n\n return $options;\n }\n\n /**\n * @return array<CrmFieldOption>\n */\n public function fetchDispositionFieldOptions(): array\n {\n $options = [];\n\n $dispositions = $this->fetchCallDispositions();\n\n foreach ($dispositions as $disposition) {\n if ($disposition['deleted'] !== false) {\n continue;\n }\n\n $option['value'] = $disposition['id'];\n $option['id'] = $disposition['id'];\n $option['label'] = $disposition['label'];\n\n $options[] = $option;\n }\n\n return $options;\n }\n\n /**\n * @return array<CrmFieldOption>\n */\n public function fetchOpportunityFieldOptions(Field $field): array\n {\n if ($field->isStageField()) {\n return $this->fetchOpportunityPipelineStages();\n }\n\n if ($field->isPipelineField()) {\n return $this->fetchOpportunityPipelines();\n }\n\n return $this->fetchPropertyOptions('deals', $field->getCrmProviderId());\n }\n\n /**\n * @throws BadRequest\n * @throws HubspotException\n */\n public function makeRequest(string $endpoint, $method = 'GET', $payload = [], ?string $queryString = null)\n {\n $endpoint = self::BASE_URL . $endpoint;\n\n if ($method === 'GET') {\n $response = $this->getInstance()->getClient()?->request(\n method: $method,\n endpoint: $endpoint,\n query_string: $queryString\n );\n } else {\n $response = $this->getInstance()->getClient()->request($method, $endpoint, [\n 'json' => ($payload),\n ]);\n }\n \n $max = $response->getHeaderLine('X-HubSpot-RateLimit-Max'); // \"110\"\n $remaining = $response->getHeaderLine('X-HubSpot-RateLimit-Remaining'); // \"109\"\n $interval = $response->getHeaderLine('X-HubSpot-RateLimit-Interval-Milliseconds'); // \"10000\"\n $body = json_decode((string) $response->getBody(), true);\n \n \\Illuminate\\Support\\Facades\\Log::channel('custom_channel')->info('$max ' . PHP_EOL . print_r($max, true));\n \\Illuminate\\Support\\Facades\\Log::channel('custom_channel')->info('$remaining ' . PHP_EOL . print_r($remaining, true));\n \\Illuminate\\Support\\Facades\\Log::channel('custom_channel')->info('$remaining ' . PHP_EOL . print_r($remaining, true));\n \n return $response;\n }\n\n /**\n * @throws BadRequest\n * @throws HubspotException\n */\n public function createMeeting(array $payload): Response\n {\n $endpoint = '/crm/v3/objects/meetings';\n\n return $this->makeRequest($endpoint, 'POST', $payload);\n }\n\n /**\n * @throws BadRequest\n * @throws HubspotException\n */\n public function updateMeeting(string $meetingId, array $payload): Response\n {\n $endpoint = '/crm/v3/objects/meetings/' . $meetingId;\n\n return $this->makeRequest($endpoint, 'PATCH', $payload);\n }\n\n /**\n * @throws \\Exception\n */\n public function createNote(\n string $body,\n string $ownerId,\n int $timestamp,\n string $objectId,\n NoteObject $noteObject\n ): ?string {\n try {\n $noteInput = new SimplePublicObjectInput([\n 'properties' => [\n 'hs_note_body' => $body,\n 'hubspot_owner_id' => $ownerId,\n 'hs_timestamp' => $timestamp,\n ],\n ]);\n\n // Create note\n $note = $this->getNewInstance()->crm()->objects()->basicApi()->create('note', $noteInput);\n\n $this->getNewInstance()->crm()->objects()->associationsApi()->create(\n 'note',\n $note->getId(),\n $this->getNoteObject($noteObject),\n $objectId,\n $this->getNoteAssociationType($noteObject),\n );\n\n return $note->getId();\n } catch (\\Exception $e) {\n $this->log->error('[Hubspot] Failed to create note', [\n 'objectId' => $objectId,\n 'noteObject' => $noteObject->getObjectType(),\n 'reason' => $e->getMessage(),\n ]);\n\n \\Sentry::captureException($e);\n }\n\n return null;\n }\n\n public function updateEngagement(string $objectId, array $engagement, array $metadata): void\n {\n $this->getInstance()->engagements()->update($objectId, $engagement, $metadata);\n }\n\n public function getEngagementData(string $engagementId): array\n {\n $engagement = $this->getInstance()->engagements()->get($engagementId);\n\n return $engagement->toArray();\n }\n\n public function createEngagement(array $engagement, array $associations, array $metadata): Response\n {\n return $this->getInstance()\n ->engagements()\n ->create($engagement, $associations, $metadata);\n }\n\n public function isUnauthorizedException(\\Exception $e): bool\n {\n // Check for specific HubSpot API exception types first\n if ($e instanceof BadRequest) {\n // BadRequest can contain 401 status codes\n return $e->getCode() === 401;\n }\n\n // Check for HTTP client exceptions with status codes\n if ($e instanceof \\GuzzleHttp\\Exception\\RequestException && $e->hasResponse()) {\n $response = $e->getResponse();\n if ($response !== null) {\n return $response->getStatusCode() === 401;\n }\n }\n\n // Check for Guzzle HTTP exceptions\n if ($e instanceof \\GuzzleHttp\\Exception\\ClientException) {\n return $e->getCode() === 401;\n }\n\n // Fallback to string matching as last resort, but be more specific\n $message = strtolower($e->getMessage());\n\n return str_contains($message, '401 unauthorized') ||\n str_contains($message, 'http 401') ||\n str_contains($message, 'status code 401') ||\n (preg_match('/\\b401\\b/', $message) && str_contains($message, 'unauthorized'));\n }\n\n /**\n * Validates and refreshes the access token if needed before API requests.\n * This ensures long-running processes don't fail due to token expiration.\n *\n * @throws SocialAccountTokenInvalidException\n */\n public function ensureValidToken(): void\n {\n if ($this->oauthAccount === null) {\n return;\n }\n\n $newToken = $this->tokenManager->ensureValidToken($this->oauthAccount);\n if ($newToken !== null) {\n $this->accessToken = $newToken;\n }\n }\n\n public function getConfig()\n {\n return $this->config;\n }\n\n // returns only active (archived=false)\n public function getOwners(): array\n {\n return $this->getNewInstance()->crm()->owners()->getAll();\n }\n\n /**\n * @param bool $archived\n *\n * @return array<Owner>|[]\n */\n public function getOwnersArchived(bool $archived = true): array\n {\n $endpoint = '/crm/v3/owners';\n $queryParams = [\n 'archived' => $archived ? 'true' : 'false',\n ];\n $queryString = http_build_query($queryParams);\n\n $owners = [];\n\n try {\n $response = $this->makeRequest(endpoint: $endpoint, queryString: $queryString);\n $responseData = $response?->toArray();\n\n foreach ($responseData['results'] as $result) {\n try {\n $owners[] = Owner::create($result);\n } catch (Throwable $e) {\n $this->log->error('[HubSpot] Failed to process owner data', [\n 'result' => $result,\n 'error' => $e->getMessage(),\n ]);\n\n continue;\n }\n }\n } catch (Throwable $e) {\n $this->log->error('HubSpot] Failed to fetch owners', [\n 'archived' => $archived,\n 'error' => $e->getMessage(),\n ]);\n\n return [];\n }\n\n return $owners;\n }\n\n public function getMeeting(string $engagementId): ObjectWithAssociations\n {\n return $this->getNewInstance()->crm()->objects()->basicApi()\n ->getById('meeting', $engagementId, null, 'contact,company,deal');\n }\n\n public function deleteEngagement(string $engagementId): void\n {\n $this->getInstance()->engagements()->delete((int) $engagementId);\n }\n\n public function getAssociationsData(array $ids, string $fromObject, string $toObject): array\n {\n $associationData = [];\n $idChunks = array_chunk($ids, self::ASSOCIATIONS_BATCH_SIZE_LIMIT);\n\n foreach ($idChunks as $idChunk) {\n try {\n $batchInput = new \\HubSpot\\Client\\Crm\\Associations\\Model\\BatchInputPublicObjectId();\n $batchInput->setInputs(array_map(function ($id) {\n $publicObjectId = new \\HubSpot\\Client\\Crm\\Associations\\Model\\PublicObjectId();\n $publicObjectId->setId($id);\n\n return $publicObjectId;\n }, $idChunk));\n\n $associatedObjectsData = $this\n ->getNewInstance()\n ->crm()\n ->associations()\n ->batchApi()\n ->read($fromObject, $toObject, $batchInput);\n\n if ($associatedObjectsData instanceof \\HubSpot\\Client\\Crm\\Associations\\Model\\BatchResponsePublicAssociationMulti) {\n foreach ($associatedObjectsData->getResults() as $association) {\n $from = $association->getFrom()->getId();\n $toAssociations = $association->getTo();\n\n if (! empty($toAssociations)) {\n $associationData[$from] = array_map(function ($item) {\n return $item->getId();\n }, $toAssociations);\n }\n }\n }\n } catch (\\Exception $e) {\n $this->log->error('[Hubspot] Failed to fetch associations', [\n 'from_object' => $fromObject,\n 'to_object' => $toObject,\n 'reason' => $e->getMessage(),\n ]);\n }\n }\n\n return $associationData;\n }\n\n /**\n * @throws \\Exception\n */\n private function getNoteAssociationType(NoteObject $noteObject): string\n {\n return match($noteObject) {\n NoteObject::Opportunity => 'note_to_deal',\n NoteObject::Lead, NoteObject::Contact => 'note_to_contact', // or 'note_to_lead' if your portal supports it\n NoteObject::Account => 'note_to_company',\n NoteObject::Call, NoteObject::Event => throw new \\Exception('Not supported'),\n };\n }\n\n /**\n * @throws \\Exception\n */\n private function getNoteObject(NoteObject $noteObject): string\n {\n return match($noteObject) {\n NoteObject::Opportunity => 'deal',\n NoteObject::Lead, NoteObject::Contact => 'contact',\n NoteObject::Account => 'company',\n NoteObject::Call, NoteObject::Event => throw new \\Exception('Not supported'),\n };\n }\n\n public function addAssociations(string $objectType, string $associationType, array $payload): Response\n {\n $endpoint = \"/crm/v4/associations/$objectType/$associationType/batch/create\";\n\n return $this->makeRequest($endpoint, 'POST', $payload);\n }\n\n public function removeAssociations(string $objectType, string $associationType, array $payload): Response\n {\n $endpoint = \"/crm/v4/associations/$objectType/$associationType/batch/archive\";\n\n return $this->makeRequest($endpoint, 'POST', $payload);\n }\n}","role_description":"text entry area","is_enabled":true,"is_focused":true,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"Project","depth":3,"on_screen":false,"role_description":"text"},{"role":"AXButton","text":"Project","depth":3,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"New File or Directory…","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.018055556,"height":0.026666667},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Expand Selected","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.018055556,"height":0.026666667},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Collapse All","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.018055556,"height":0.026666667},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Options","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.018055556,"height":0.026666667},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Hide","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.018055556,"height":0.026666667},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false}]...
|
8137451649170895720
|
6684458809013180516
|
click
|
accessibility
|
NULL
|
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
Execute
Explain Plan
Browse Query History
View Parameters
Open Query Execution Settings…
In-Editor Results
Tx: Auto
Cancel Running Statements
Playground
jiminny
Sync Changes
Hide This Notification
Code changed:
Hide
6
1
6
Previous Highlighted Error
Next Highlighted Error
# [PASSWORD_DOTS] HS [PASSWORD_DOTS]
select * from teams where id = 2; # 2
select * from features; # 2
select * from team_features where team_id = 2; # 2
select * from crm_configurations where id = 2; # 2
select * from users where team_id = 2; #
select * from playbooks where team_id = 2; # event 38
select * from playbook_categories where playbook_id = 38; #
SELECT * FROM activities WHERE crm_configuration_id = 2 and crm_provider_id is not null order by id desc;
[URL_WITH_CREDENTIALS] CrmFieldOption array{id:string, label:string, value?:string}
*/
class Client extends BaseClient implements HubspotClientInterface
{
public const string MIN_API_VERSION = '2';
public const string BASE_URL = '[URL_WITH_CREDENTIALS] T
* @param callable(): T $apiCall
* @return T
*
* @throws RateLimitException
*/
private function executeRequest(callable $apiCall)
{
if (! $this->rateLimiter->canMakeRequest($this->config)) {
$retryAfter = $this->rateLimiter->requestAvailableIn($this->config);
$this->log->warning('[Hubspot] Rate limit exceeded, deferring request', [
'team_id' => $this->config->team_id,
'config_id' => $this->config->getId(),
'retry_after' => $retryAfter,
]);
throw new RateLimitException(
'Hubspot rate limit reached for configuration ' . $this->config->getId(),
$retryAfter,
);
}
$this->rateLimiter->incrementRequestCount($this->config);
try {
return $apiCall();
} catch (Throwable $e) {
if ($this->isHubspotRateLimit($e)) {
$retryAfter = $this->parseRetryAfter($e);
$this->log->warning('[Hubspot] Received 429 from API', [
'team_id' => $this->config->team_id,
'config_id' => $this->config->getId(),
'retry_after' => $retryAfter,
'reason' => $e->getMessage(),
]);
throw new RateLimitException('Hubspot returned 429', $retryAfter, $e);
}
throw $e;
}
}
private function isHubspotRateLimit(Throwable $e): bool
{
return method_exists($e, 'getCode') && (int) $e->getCode() === 429;
}
private function parseRetryAfter(Throwable $e): int
{
if (method_exists($e, 'getResponseHeaders')) {
$headers = $e->getResponseHeaders() ?: [];
$value = $headers['Retry-After'] ?? $headers['retry-after'] ?? null;
if (is_array($value)) {
$value = $value[0] ?? null;
}
if (is_numeric($value)) {
return (int) $value;
}
}
return 10;
}
public function getMinimumApiVersion(): string
{
return self::MIN_API_VERSION;
}
public function getInstance(): Factory
{
return new Factory([
'key' => $this->accessToken,
'oauth2' => true,
'base_url' => $this->baseUrl,
]);
}
public function getNewInstance(): Discovery
{
return \HubSpot\Factory::createWithAccessToken($this->accessToken);
}
/**
* Secondly and daily limits for Hubspot API
*
* Product Tier: Free & Starter | Professional & Enterprise | API add-on (any tier)
* Burst: 100/10 seconds | 150/10 seconds | 200/10 seconds
* Daily: 250,000 | 500,000 | 1,000,000
*
* Official documentation states: The search endpoints are rate limited to five requests per second.
* Since with 5 RPS were still hitting secondly rate limits we lowered it to 4
*/
public function getPaginatedData(array $payload, string $type, int $offset = 0): array
{
$total = 0;
$lastId = null;
$rows = [];
foreach ($this->getPaginatedDataGenerator($payload, $type, $offset, $total, $lastId) as $row) {
$rows[] = $row;
}
return ['results' => $rows, 'total' => $total, 'last_record' => $lastId];
}
/**
* @throws HubspotException
* @throws SocialAccountTokenInvalidException
* @throws BadRequest
*/
public function getPaginatedDataGenerator(
array $payload,
string $type,
int $offset = 0,
int &$total = 0,
?string &$lastRecordId = null
): \Generator {
return $this->paginationService->getPaginatedDataGenerator(
$this,
$payload,
$type,
$offset,
$total,
$lastRecordId
);
}
/**
* @throws DealApiException
* @throws CrmException
*/
public function getOpportunityById(string $crmId, array $fields): array
{
try {
$deal = $this->executeRequest(fn () => $this->getNewInstance()->crm()->deals()->basicApi()->getById(
$crmId,
implode(',', $fields),
'companies,contacts'
));
} catch (DealApiException $e) {
$this->log->info('[Hubspot] Failed to fetch opportunity', [
'crm_id' => $crmId,
'reason' => $e->getMessage(),
]);
throw $e;
}
if (! $deal instanceof DealWithAssociations) {
throw new CrmException('Deal not found');
}
return [
'id' => $deal->getId(),
'properties' => $deal->getProperties(),
'associations' => $deal->getAssociations(),
];
}
/**
* Generic batch read method for HubSpot objects
*
* @param string $objectType The object type ('deals', 'companies', 'contacts')
* @param array<string> $crmIds Array of HubSpot object IDs (max 100)
* @param array<string> $fields Array of property names to fetch
*
* @return array<string, array> Array keyed by CRM ID with object data
*/
private function batchReadObjects(string $objectType, array $crmIds, array $fields): array
{
if (empty($crmIds)) {
return [];
}
$this->validateBatchSize($objectType, $crmIds);
$this->ensureValidToken();
try {
$batchConfig = $this->createBatchConfiguration($objectType);
$batchReadRequest = $this->prepareBatchRequest($batchConfig, $crmIds, $fields);
$response = $batchConfig['api']->read($batchReadRequest);
$this->validateApiResponse($response, $objectType);
$results = $this->processApiResults($response);
$this->logBatchResults($objectType, $crmIds, $results);
return $results;
} catch (\Throwable $e) {
$this->handleBatchError($e, $objectType, $crmIds);
}
}
private function validateBatchSize(string $objectType, array $crmIds): void
{
if (count($crmIds) > 100) {
throw new \InvalidArgumentException("Batch size cannot exceed 100 {$objectType}");
}
}
private function createBatchConfiguration(string $objectType): array
{
$configurations = [
'deals' => [
'batchReadRequest' => new \HubSpot\Client\Crm\Deals\Model\BatchReadInputSimplePublicObjectId(),
'inputClass' => \HubSpot\Client\Crm\Deals\Model\SimplePublicObjectId::class,
'api' => $this->getNewInstance()->crm()->deals()->batchApi(),
],
'companies' => [
'batchReadRequest' => new \HubSpot\Client\Crm\Companies\Model\BatchReadInputSimplePublicObjectId(),
'inputClass' => \HubSpot\Client\Crm\Companies\Model\SimplePublicObjectId::class,
'api' => $this->getNewInstance()->crm()->companies()->batchApi(),
],
'contacts' => [
'batchReadRequest' => new \HubSpot\Client\Crm\Contacts\Model\BatchReadInputSimplePublicObjectId(),
'inputClass' => \HubSpot\Client\Crm\Contacts\Model\SimplePublicObjectId::class,
'api' => $this->getNewInstance()->crm()->contacts()->batchApi(),
],
];
if (! isset($configurations[$objectType])) {
throw new \InvalidArgumentException("Unsupported object type: {$objectType}");
}
return $configurations[$objectType];
}
private function prepareBatchRequest(array $batchConfig, array $crmIds, array $fields): object
{
$batchReadRequest = $batchConfig['batchReadRequest'];
$inputClass = $batchConfig['inputClass'];
$inputs = array_map(function ($crmId) use ($inputClass) {
$input = new $inputClass();
$input->setId($crmId);
return $input;
}, $crmIds);
$batchReadRequest->setInputs($inputs);
$batchReadRequest->setProperties($fields);
return $batchReadRequest;
}
private function validateApiResponse($response, string $objectType): void
{
if (! $response) {
throw new CrmException("HubSpot API returned null response for {$objectType} batch read");
}
}
private function processApiResults($response): array
{
$results = [];
$responseResults = $response->getResults();
if ($responseResults) {
foreach ($responseResults as $object) {
if ($object && $object->getId()) {
$results[$object->getId()] = [
'id' => $object->getId(),
'properties' => $object->getProperties() ?: [],
];
}
}
}
return $results;
}
private function logBatchResults(string $objectType, array $crmIds, array $results): void
{
$this->log->info("[HubSpot] Batch fetched {$objectType}", [
'requested_count' => count($crmIds),
'returned_count' => count($results),
'crm_ids' => $crmIds,
]);
}
private function handleBatchError(\Throwable $e, string $objectType, array $crmIds): void
{
$errorMessage = $e->getMessage() ?: 'Unknown error';
$errorTrace = $e->getTraceAsString() ?: 'No trace available';
$this->log->error("[HubSpot] Failed to batch fetch {$objectType}", [
'crm_ids' => $crmIds,
'error' => $errorMessage,
'trace' => $errorTrace,
]);
throw new CrmException("Failed to batch fetch {$objectType}: " . $errorMessage);
}
/**
* Batch read multiple opportunities by their CRM IDs
*
* @param array<string> $crmIds Array of HubSpot deal IDs (max 100)
* @param array<string> $fields Array of property names to fetch
*
* @return array<string, array> Array keyed by CRM ID with opportunity data
*/
public function getOpportunitiesByIds(array $crmIds, array $fields): array
{
return $this->batchReadObjects('deals', $crmIds, $fields);
}
/**
* Batch read multiple companies by their CRM IDs
*
* @param array<string> $crmIds Array of HubSpot company IDs (max 100)
* @param array<string> $fields Array of property names to fetch
*
* @return array<string, array> Array keyed by CRM ID with company data
*/
public function getCompaniesByIds(array $crmIds, array $fields): array
{
return $this->batchReadObjects('companies', $crmIds, $fields);
}
/**
* Batch read multiple contacts by their CRM IDs
*
* @param array<string> $crmIds Array of HubSpot contact IDs (max 100)
* @param array<string> $fields Array of property names to fetch
*
* @return array<string, array> Array keyed by CRM ID with contact data
*/
public function getContactsByIds(array $crmIds, array $fields): array
{
return $this->batchReadObjects('contacts', $crmIds, $fields);
}
/**
* @throws CompanyApiException
* @throws CrmException
*/
public function getAccountById(string $crmId, array $fields): array
{
try {
$company = $this->getNewInstance()->crm()->companies()->basicApi()->getById(
$crmId,
implode(',', $fields),
);
} catch (CompanyApiException $e) {
$this->log->info('[Hubspot] Failed to fetch account', [
'crm_id' => $crmId,
'reason' => $e->getMessage(),
]);
throw $e;
}
if (! $company instanceof CompaniesWithAssociations) {
throw new CrmException('Account not found');
}
return [
'id' => $company->getId(),
'properties' => $company->getProperties(),
];
}
/**
* @throws ContactApiException
* @throws CrmException
*/
public function getContactById(string $crmId, array $fields): array
{
try {
$contact = $this->getNewInstance()->crm()->contacts()->basicApi()->getById(
$crmId,
implode(',', $fields)
);
} catch (ContactApiException $e) {
$this->log->info('[Hubspot] Failed to fetch contact', [
'crm_id' => $crmId,
'reason' => $e->getMessage(),
]);
throw $e;
}
if (! $contact instanceof ContactsWithAssociations) {
throw new CrmException('Contact not found');
}
return [
'id' => $contact->getId(),
'properties' => $contact->getProperties(),
];
}
/**
* This is email search request that Hubspot offers as GET (more generous quota)
*/
public function getContactByEmail(string $email, array $fields = []): array
{
try {
$contact = $this->getNewInstance()->crm()->contacts()->basicApi()->getById(
$email,
implode(',', $fields),
null,
false,
'email'
);
return [
'id' => $contact->getId(),
'properties' => $contact->getProperties(),
];
} catch (ContactApiException $e) {
$this->log->info('[Hubspot] Failed to fetch contact', [
'email' => $email,
'reason' => $e->getMessage(),
]);
return [];
}
}
/**
* @throws CrmException
*/
public function fetchProperty(string $objectType, string $propertyId): Property
{
$result = $this->getNewInstance()->crm()->properties()->coreApi()->getByName($objectType, $propertyId);
if (! $result instanceof Property) {
$this->log->error('[Hubspot] Failed to fetch property', [
'object_type' => $objectType,
'property_id' => $propertyId,
'reason' => $result->getMessage(),
]);
throw new CrmException('Failed to fetch property');
}
return $result;
}
/**
* @return array<CrmFieldOption>
*/
public function fetchPropertyOptions(string $objectType, string $propertyId): array
{
/** @var array<CrmFieldOption> */
return $this->fetchProperty($objectType, $propertyId)->getOptions();
}
/**
* @return array<array{id:string, label:string, deleted:bool}>
*/
public function fetchCallDispositions(): array
{
/** @var Response $response */
$response = $this->getInstance()->engagements()->getCallDispositions();
/**
* @var array<array{
* id:string,
* label:string,
* deleted: bool
* }>
*/
return $response->toArray();
}
/**
* @return array<CrmFieldOption>
*/
public function fetchOpportunityPipelineStages(): array
{
$stages = [];
$apiResponse = $this->getNewInstance()->crm()->pipelines()->pipelinesApi()->getAll('deals');
if ($apiResponse instanceof Error) {
$this->log->error('[Hubspot] Failed to fetch opportunity pipelines', [
'reason' => $apiResponse->getMessage(),
]);
return [];
}
foreach ($apiResponse->getResults() as $pipeline) {
$pipelineStages = array_map(
static function (PipelineStage $stage) {
return [
'id' => $stage->getId(),
'label' => $stage->getLabel(),
];
},
$pipeline->getStages()
);
$stages = array_merge($stages, $pipelineStages);
}
return $stages;
}
public function fetchOpportunityPipelines(): array
{
$pipelines = [];
try {
$apiResponse = $this->makeRequest('/crm/v3/pipelines/deals');
} catch (\Exception $e) {
$this->log->info('[Hubspot] Failed to fetch opportunity pipelines', [
'reason' => $e->getMessage(),
]);
return [];
}
$response = $apiResponse->toArray();
foreach ($response['results'] as $pipeline) {
$pipelines[] = [
'id' => $pipeline['id'],
'label' => $pipeline['label'],
];
}
return $pipelines;
}
/**
* @return array<CrmFieldOption>
*/
public function fetchMeetingOutcomeFieldOptions(Field $field): array
{
return $field->getCrmProviderId() === 'meetingOutcome'
? $this->fetchMeetingOutcomeTypes()
: $this->fetchCallActivityTypes();
}
public function fetchMeetingOutcomeTypes(): array
{
return $this->extractMeetingTypeOptions(
'[URL_WITH_CREDENTIALS] Response $response */
$response = $this->getInstance()
->getClient()
->request('GET', $endpoint);
/**
* @var array<array{
* value: string,
* label: string,
* displayOrder: int
* }> $optionData
*/
$optionData = $response->toArray()['options'] ?? [];
$options = [];
foreach ($optionData as $item) {
$options[] = [
'id' => $item['value'],
'value' => $item['value'],
'label' => $item['label'],
'display_order' => $item['displayOrder'],
];
}
return $options;
}
/**
* @return array<CrmFieldOption>
*/
public function fetchDispositionFieldOptions(): array
{
$options = [];
$dispositions = $this->fetchCallDispositions();
foreach ($dispositions as $disposition) {
if ($disposition['deleted'] !== false) {
continue;
}
$option['value'] = $disposition['id'];
$option['id'] = $disposition['id'];
$option['label'] = $disposition['label'];
$options[] = $option;
}
return $options;
}
/**
* @return array<CrmFieldOption>
*/
public function fetchOpportunityFieldOptions(Field $field): array
{
if ($field->isStageField()) {
return $this->fetchOpportunityPipelineStages();
}
if ($field->isPipelineField()) {
return $this->fetchOpportunityPipelines();
}
return $this->fetchPropertyOptions('deals', $field->getCrmProviderId());
}
/**
* @throws BadRequest
* @throws HubspotException
*/
public function makeRequest(string $endpoint, $method = 'GET', $payload = [], ?string $queryString = null)
{
$endpoint = self::BASE_URL . $endpoint;
if ($method === 'GET') {
$response = $this->getInstance()->getClient()?->request(
method: $method,
endpoint: $endpoint,
query_string: $queryString
);
} else {
$response = $this->getInstance()->getClient()->request($method, $endpoint, [
'json' => ($payload),
]);
}
$max = $response->getHeaderLine('X-HubSpot-RateLimit-Max'); // "110"
$remaining = $response->getHeaderLine('X-HubSpot-RateLimit-Remaining'); // "109"
$interval = $response->getHeaderLine('X-HubSpot-RateLimit-Interval-Milliseconds'); // "10000"
$body = json_decode((string) $response->getBody(), true);
\Illuminate\Support\Facades\Log::channel('custom_channel')->info('$max ' . PHP_EOL . print_r($max, true));
\Illuminate\Support\Facades\Log::channel('custom_channel')->info('$remaining ' . PHP_EOL . print_r($remaining, true));
\Illuminate\Support\Facades\Log::channel('custom_channel')->info('$remaining ' . PHP_EOL . print_r($remaining, true));
return $response;
}
/**
* @throws BadRequest
* @throws HubspotException
*/
public function createMeeting(array $payload): Response
{
$endpoint = '/crm/v3/objects/meetings';
return $this->makeRequest($endpoint, 'POST', $payload);
}
/**
* @throws BadRequest
* @throws HubspotException
*/
public function updateMeeting(string $meetingId, array $payload): Response
{
$endpoint = '/crm/v3/objects/meetings/' . $meetingId;
return $this->makeRequest($endpoint, 'PATCH', $payload);
}
/**
* @throws \Exception
*/
public function createNote(
string $body,
string $ownerId,
int $timestamp,
string $objectId,
NoteObject $noteObject
): ?string {
try {
$noteInput = new SimplePublicObjectInput([
'properties' => [
'hs_note_body' => $body,
'hubspot_owner_id' => $ownerId,
'hs_timestamp' => $timestamp,
],
]);
// Create note
$note = $this->getNewInstance()->crm()->objects()->basicApi()->create('note', $noteInput);
$this->getNewInstance()->crm()->objects()->associationsApi()->create(
'note',
$note->getId(),
$this->getNoteObject($noteObject),
$objectId,
$this->getNoteAssociationType($noteObject),
);
return $note->getId();
} catch (\Exception $e) {
$this->log->error('[Hubspot] Failed to create note', [
'objectId' => $objectId,
'noteObject' => $noteObject->getObjectType(),
'reason' => $e->getMessage(),
]);
\Sentry::captureException($e);
}
return null;
}
public function updateEngagement(string $objectId, array $engagement, array $metadata): void
{
$this->getInstance()->engagements()->update($objectId, $engagement, $metadata);
}
public function getEngagementData(string $engagementId): array
{
$engagement = $this->getInstance()->engagements()->get($engagementId);
return $engagement->toArray();
}
public function createEngagement(array $engagement, array $associations, array $metadata): Response
{
return $this->getInstance()
->engagements()
->create($engagement, $associations, $metadata);
}
public function isUnauthorizedException(\Exception $e): bool
{
// Check for specific HubSpot API exception types first
if ($e instanceof BadRequest) {
// BadRequest can contain 401 status codes
return $e->getCode() === 401;
}
// Check for HTTP client exceptions with status codes
if ($e instanceof \GuzzleHttp\Exception\RequestException && $e->hasResponse()) {
$response = $e->getResponse();
if ($response !== null) {
return $response->getStatusCode() === 401;
}
}
// Check for Guzzle HTTP exceptions
if ($e instanceof \GuzzleHttp\Exception\ClientException) {
return $e->getCode() === 401;
}
// Fallback to string matching as last resort, but be more specific
$message = strtolower($e->getMessage());
return str_contains($message, '401 unauthorized') ||
str_contains($message, 'http 401') ||
str_contains($message, 'status code 401') ||
(preg_match('/\b401\b/', $message) && str_contains($message, 'unauthorized'));
}
/**
* Validates and refreshes the access token if needed before API requests.
* This ensures long-running processes don't fail due to token expiration.
*
* @throws SocialAccountTokenInvalidException
*/
public function ensureValidToken(): void
{
if ($this->oauthAccount === null) {
return;
}
$newToken = $this->tokenManager->ensureValidToken($this->oauthAccount);
if ($newToken !== null) {
$this->accessToken = $newToken;
}
}
public function getConfig()
{
return $this->config;
}
// returns only active (archived=false)
public function getOwners(): array
{
return $this->getNewInstance()->crm()->owners()->getAll();
}
/**
* @param bool $archived
*
* @return array<Owner>|[]
*/
public function getOwnersArchived(bool $archived = true): array
{
$endpoint = '/crm/v3/owners';
$queryParams = [
'archived' => $archived ? 'true' : 'false',
];
$queryString = http_build_query($queryParams);
$owners = [];
try {
$response = $this->makeRequest(endpoint: $endpoint, queryString: $queryString);
$responseData = $response?->toArray();
foreach ($responseData['results'] as $result) {
try {
$owners[] = Owner::create($result);
} catch (Throwable $e) {
$this->log->error('[HubSpot] Failed to process owner data', [
'result' => $result,
'error' => $e->getMessage(),
]);
continue;
}
}
} catch (Throwable $e) {
$this->log->error('HubSpot] Failed to fetch owners', [
'archived' => $archived,
'error' => $e->getMessage(),
]);
return [];
}
return $owners;
}
public function getMeeting(string $engagementId): ObjectWithAssociations
{
return $this->getNewInstance()->crm()->objects()->basicApi()
->getById('meeting', $engagementId, null, 'contact,company,deal');
}
public function deleteEngagement(string $engagementId): void
{
$this->getInstance()->engagements()->delete((int) $engagementId);
}
public function getAssociationsData(array $ids, string $fromObject, string $toObject): array
{
$associationData = [];
$idChunks = array_chunk($ids, self::ASSOCIATIONS_BATCH_SIZE_LIMIT);
foreach ($idChunks as $idChunk) {
try {
$batchInput = new \HubSpot\Client\Crm\Associations\Model\BatchInputPublicObjectId();
$batchInput->setInputs(array_map(function ($id) {
$publicObjectId = new \HubSpot\Client\Crm\Associations\Model\PublicObjectId();
$publicObjectId->setId($id);
return $publicObjectId;
}, $idChunk));
$associatedObjectsData = $this
->getNewInstance()
->crm()
->associations()
->batchApi()
->read($fromObject, $toObject, $batchInput);
if ($associatedObjectsData instanceof \HubSpot\Client\Crm\Associations\Model\BatchResponsePublicAssociationMulti) {
foreach ($associatedObjectsData->getResults() as $association) {
$from = $association->getFrom()->getId();
$toAssociations = $association->getTo();
if (! empty($toAssociations)) {
$associationData[$from] = array_map(function ($item) {
return $item->getId();
}, $toAssociations);
}
}
}
} catch (\Exception $e) {
$this->log->error('[Hubspot] Failed to fetch associations', [
'from_object' => $fromObject,
'to_object' => $toObject,
'reason' => $e->getMessage(),
]);
}
}
return $associationData;
}
/**
* @throws \Exception
*/
private function getNoteAssociationType(NoteObject $noteObject): string
{
return match($noteObject) {
NoteObject::Opportunity => 'note_to_deal',
NoteObject::Lead, NoteObject::Contact => 'note_to_contact', // or 'note_to_lead' if your portal supports it
NoteObject::Account => 'note_to_company',
NoteObject::Call, NoteObject::Event => throw new \Exception('Not supported'),
};
}
/**
* @throws \Exception
*/
private function getNoteObject(NoteObject $noteObject): string
{
return match($noteObject) {
NoteObject::Opportunity => 'deal',
NoteObject::Lead, NoteObject::Contact => 'contact',
NoteObject::Account => 'company',
NoteObject::Call, NoteObject::Event => throw new \Exception('Not supported'),
};
}
public function addAssociations(string $objectType, string $associationType, array $payload): Response
{
$endpoint = "/crm/v4/associations/$objectType/$associationType/batch/create";
return $this->makeRequest($endpoint, 'POST', $payload);
}
public function removeAssociations(string $objectType, string $associationType, array $payload): Response
{
$endpoint = "/crm/v4/associations/$objectType/$associationType/batch/archive";
return $this->makeRequest($endpoint, 'POST', $payload);
}
}
Project
Project
New File or Directory…
Expand Selected
Collapse All
Options
Hide...
|
2679
|
NULL
|
NULL
|
NULL
|
|
2683
|
111
|
11
|
2026-05-07T11:35:31.181888+00:00
|
/Users/lukas/.screenpipe/data/data/2026-05-07/1778 /Users/lukas/.screenpipe/data/data/2026-05-07/1778153731181_m1.jpg...
|
PhpStorm
|
faVsco.js – Hubspot/Client.php
|
True
|
NULL
|
monitor_1
|
NULL
|
NULL
|
NULL
|
NULL
|
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
Execute
Explain Plan
Browse Query History
View Parameters
Open Query Execution Settings…
In-Editor Results
Tx: Auto
Cancel Running Statements
Playground
jiminny
Sync Changes
Hide This Notification
Code changed:
Hide
6
1
6
Previous Highlighted Error
Next Highlighted Error
# [PASSWORD_DOTS] HS [PASSWORD_DOTS]
select * from teams where id = 2; # 2
select * from features; # 2
select * from team_features where team_id = 2; # 2
select * from crm_configurations where id = 2; # 2
select * from users where team_id = 2; #
select * from playbooks where team_id = 2; # event 38
select * from playbook_categories where playbook_id = 38; #
SELECT * FROM activities WHERE crm_configuration_id = 2 and crm_provider_id is not null order by id desc;
[URL_WITH_CREDENTIALS] CrmFieldOption array{id:string, label:string, value?:string}
*/
class Client extends BaseClient implements HubspotClientInterface
{
public const string MIN_API_VERSION = '2';
public const string BASE_URL = '[URL_WITH_CREDENTIALS] T
* @param callable(): T $apiCall
* @return T
*
* @throws RateLimitException
*/
private function executeRequest(callable $apiCall)
{
if (! $this->rateLimiter->canMakeRequest($this->config)) {
$retryAfter = $this->rateLimiter->requestAvailableIn($this->config);
$this->log->warning('[Hubspot] Rate limit exceeded, deferring request', [
'team_id' => $this->config->team_id,
'config_id' => $this->config->getId(),
'retry_after' => $retryAfter,
]);
throw new RateLimitException(
'Hubspot rate limit reached for configuration ' . $this->config->getId(),
$retryAfter,
);
}
$this->rateLimiter->incrementRequestCount($this->config);
try {
return $apiCall();
} catch (Throwable $e) {
if ($this->isHubspotRateLimit($e)) {
$retryAfter = $this->parseRetryAfter($e);
$this->log->warning('[Hubspot] Received 429 from API', [
'team_id' => $this->config->team_id,
'config_id' => $this->config->getId(),
'retry_after' => $retryAfter,
'reason' => $e->getMessage(),
]);
throw new RateLimitException('Hubspot returned 429', $retryAfter, $e);
}
throw $e;
}
}
private function isHubspotRateLimit(Throwable $e): bool
{
return method_exists($e, 'getCode') && (int) $e->getCode() === 429;
}
private function parseRetryAfter(Throwable $e): int
{
if (method_exists($e, 'getResponseHeaders')) {
$headers = $e->getResponseHeaders() ?: [];
$value = $headers['Retry-After'] ?? $headers['retry-after'] ?? null;
if (is_array($value)) {
$value = $value[0] ?? null;
}
if (is_numeric($value)) {
return (int) $value;
}
}
return 10;
}
public function getMinimumApiVersion(): string
{
return self::MIN_API_VERSION;
}
public function getInstance(): Factory
{
return new Factory([
'key' => $this->accessToken,
'oauth2' => true,
'base_url' => $this->baseUrl,
]);
}
public function getNewInstance(): Discovery
{
return \HubSpot\Factory::createWithAccessToken($this->accessToken);
}
/**
* Secondly and daily limits for Hubspot API
*
* Product Tier: Free & Starter | Professional & Enterprise | API add-on (any tier)
* Burst: 100/10 seconds | 150/10 seconds | 200/10 seconds
* Daily: 250,000 | 500,000 | 1,000,000
*
* Official documentation states: The search endpoints are rate limited to five requests per second.
* Since with 5 RPS were still hitting secondly rate limits we lowered it to 4
*/
public function getPaginatedData(array $payload, string $type, int $offset = 0): array
{
$total = 0;
$lastId = null;
$rows = [];
foreach ($this->getPaginatedDataGenerator($payload, $type, $offset, $total, $lastId) as $row) {
$rows[] = $row;
}
return ['results' => $rows, 'total' => $total, 'last_record' => $lastId];
}
/**
* @throws HubspotException
* @throws SocialAccountTokenInvalidException
* @throws BadRequest
*/
public function getPaginatedDataGenerator(
array $payload,
string $type,
int $offset = 0,
int &$total = 0,
?string &$lastRecordId = null
): \Generator {
return $this->paginationService->getPaginatedDataGenerator(
$this,
$payload,
$type,
$offset,
$total,
$lastRecordId
);
}
/**
* @throws DealApiException
* @throws CrmException
*/
public function getOpportunityById(string $crmId, array $fields): array
{
try {
$deal = $this->executeRequest(fn () => $this->getNewInstance()->crm()->deals()->basicApi()->getById(
$crmId,
implode(',', $fields),
'companies,contacts'
));
} catch (DealApiException $e) {
$this->log->info('[Hubspot] Failed to fetch opportunity', [
'crm_id' => $crmId,
'reason' => $e->getMessage(),
]);
throw $e;
}
if (! $deal instanceof DealWithAssociations) {
throw new CrmException('Deal not found');
}
return [
'id' => $deal->getId(),
'properties' => $deal->getProperties(),
'associations' => $deal->getAssociations(),
];
}
/**
* Generic batch read method for HubSpot objects
*
* @param string $objectType The object type ('deals', 'companies', 'contacts')
* @param array<string> $crmIds Array of HubSpot object IDs (max 100)
* @param array<string> $fields Array of property names to fetch
*
* @return array<string, array> Array keyed by CRM ID with object data
*/
private function batchReadObjects(string $objectType, array $crmIds, array $fields): array
{
if (empty($crmIds)) {
return [];
}
$this->validateBatchSize($objectType, $crmIds);
$this->ensureValidToken();
try {
$batchConfig = $this->createBatchConfiguration($objectType);
$batchReadRequest = $this->prepareBatchRequest($batchConfig, $crmIds, $fields);
$response = $batchConfig['api']->read($batchReadRequest);
$this->validateApiResponse($response, $objectType);
$results = $this->processApiResults($response);
$this->logBatchResults($objectType, $crmIds, $results);
return $results;
} catch (\Throwable $e) {
$this->handleBatchError($e, $objectType, $crmIds);
}
}
private function validateBatchSize(string $objectType, array $crmIds): void
{
if (count($crmIds) > 100) {
throw new \InvalidArgumentException("Batch size cannot exceed 100 {$objectType}");
}
}
private function createBatchConfiguration(string $objectType): array
{
$configurations = [
'deals' => [
'batchReadRequest' => new \HubSpot\Client\Crm\Deals\Model\BatchReadInputSimplePublicObjectId(),
'inputClass' => \HubSpot\Client\Crm\Deals\Model\SimplePublicObjectId::class,
'api' => $this->getNewInstance()->crm()->deals()->batchApi(),
],
'companies' => [
'batchReadRequest' => new \HubSpot\Client\Crm\Companies\Model\BatchReadInputSimplePublicObjectId(),
'inputClass' => \HubSpot\Client\Crm\Companies\Model\SimplePublicObjectId::class,
'api' => $this->getNewInstance()->crm()->companies()->batchApi(),
],
'contacts' => [
'batchReadRequest' => new \HubSpot\Client\Crm\Contacts\Model\BatchReadInputSimplePublicObjectId(),
'inputClass' => \HubSpot\Client\Crm\Contacts\Model\SimplePublicObjectId::class,
'api' => $this->getNewInstance()->crm()->contacts()->batchApi(),
],
];
if (! isset($configurations[$objectType])) {
throw new \InvalidArgumentException("Unsupported object type: {$objectType}");
}
return $configurations[$objectType];
}
private function prepareBatchRequest(array $batchConfig, array $crmIds, array $fields): object
{
$batchReadRequest = $batchConfig['batchReadRequest'];
$inputClass = $batchConfig['inputClass'];
$inputs = array_map(function ($crmId) use ($inputClass) {
$input = new $inputClass();
$input->setId($crmId);
return $input;
}, $crmIds);
$batchReadRequest->setInputs($inputs);
$batchReadRequest->setProperties($fields);
return $batchReadRequest;
}
private function validateApiResponse($response, string $objectType): void
{
if (! $response) {
throw new CrmException("HubSpot API returned null response for {$objectType} batch read");
}
}
private function processApiResults($response): array
{
$results = [];
$responseResults = $response->getResults();
if ($responseResults) {
foreach ($responseResults as $object) {
if ($object && $object->getId()) {
$results[$object->getId()] = [
'id' => $object->getId(),
'properties' => $object->getProperties() ?: [],
];
}
}
}
return $results;
}
private function logBatchResults(string $objectType, array $crmIds, array $results): void
{
$this->log->info("[HubSpot] Batch fetched {$objectType}", [
'requested_count' => count($crmIds),
'returned_count' => count($results),
'crm_ids' => $crmIds,
]);
}
private function handleBatchError(\Throwable $e, string $objectType, array $crmIds): void
{
$errorMessage = $e->getMessage() ?: 'Unknown error';
$errorTrace = $e->getTraceAsString() ?: 'No trace available';
$this->log->error("[HubSpot] Failed to batch fetch {$objectType}", [
'crm_ids' => $crmIds,
'error' => $errorMessage,
'trace' => $errorTrace,
]);
throw new CrmException("Failed to batch fetch {$objectType}: " . $errorMessage);
}
/**
* Batch read multiple opportunities by their CRM IDs
*
* @param array<string> $crmIds Array of HubSpot deal IDs (max 100)
* @param array<string> $fields Array of property names to fetch
*
* @return array<string, array> Array keyed by CRM ID with opportunity data
*/
public function getOpportunitiesByIds(array $crmIds, array $fields): array
{
return $this->batchReadObjects('deals', $crmIds, $fields);
}
/**
* Batch read multiple companies by their CRM IDs
*
* @param array<string> $crmIds Array of HubSpot company IDs (max 100)
* @param array<string> $fields Array of property names to fetch
*
* @return array<string, array> Array keyed by CRM ID with company data
*/
public function getCompaniesByIds(array $crmIds, array $fields): array
{
return $this->batchReadObjects('companies', $crmIds, $fields);
}
/**
* Batch read multiple contacts by their CRM IDs
*
* @param array<string> $crmIds Array of HubSpot contact IDs (max 100)
* @param array<string> $fields Array of property names to fetch
*
* @return array<string, array> Array keyed by CRM ID with contact data
*/
public function getContactsByIds(array $crmIds, array $fields): array
{
return $this->batchReadObjects('contacts', $crmIds, $fields);
}
/**
* @throws CompanyApiException
* @throws CrmException
*/
public function getAccountById(string $crmId, array $fields): array
{
try {
$company = $this->getNewInstance()->crm()->companies()->basicApi()->getById(
$crmId,
implode(',', $fields),
);
} catch (CompanyApiException $e) {
$this->log->info('[Hubspot] Failed to fetch account', [
'crm_id' => $crmId,
'reason' => $e->getMessage(),
]);
throw $e;
}
if (! $company instanceof CompaniesWithAssociations) {
throw new CrmException('Account not found');
}
return [
'id' => $company->getId(),
'properties' => $company->getProperties(),
];
}
/**
* @throws ContactApiException
* @throws CrmException
*/
public function getContactById(string $crmId, array $fields): array
{
try {
$contact = $this->getNewInstance()->crm()->contacts()->basicApi()->getById(
$crmId,
implode(',', $fields)
);
} catch (ContactApiException $e) {
$this->log->info('[Hubspot] Failed to fetch contact', [
'crm_id' => $crmId,
'reason' => $e->getMessage(),
]);
throw $e;
}
if (! $contact instanceof ContactsWithAssociations) {
throw new CrmException('Contact not found');
}
return [
'id' => $contact->getId(),
'properties' => $contact->getProperties(),
];
}
/**
* This is email search request that Hubspot offers as GET (more generous quota)
*/
public function getContactByEmail(string $email, array $fields = []): array
{
try {
$contact = $this->getNewInstance()->crm()->contacts()->basicApi()->getById(
$email,
implode(',', $fields),
null,
false,
'email'
);
return [
'id' => $contact->getId(),
'properties' => $contact->getProperties(),
];
} catch (ContactApiException $e) {
$this->log->info('[Hubspot] Failed to fetch contact', [
'email' => $email,
'reason' => $e->getMessage(),
]);
return [];
}
}
/**
* @throws CrmException
*/
public function fetchProperty(string $objectType, string $propertyId): Property
{
$result = $this->getNewInstance()->crm()->properties()->coreApi()->getByName($objectType, $propertyId);
if (! $result instanceof Property) {
$this->log->error('[Hubspot] Failed to fetch property', [
'object_type' => $objectType,
'property_id' => $propertyId,
'reason' => $result->getMessage(),
]);
throw new CrmException('Failed to fetch property');
}
return $result;
}
/**
* @return array<CrmFieldOption>
*/
public function fetchPropertyOptions(string $objectType, string $propertyId): array
{
/** @var array<CrmFieldOption> */
return $this->fetchProperty($objectType, $propertyId)->getOptions();
}
/**
* @return array<array{id:string, label:string, deleted:bool}>
*/
public function fetchCallDispositions(): array
{
/** @var Response $response */
$response = $this->getInstance()->engagements()->getCallDispositions();
/**
* @var array<array{
* id:string,
* label:string,
* deleted: bool
* }>
*/
return $response->toArray();
}
/**
* @return array<CrmFieldOption>
*/
public function fetchOpportunityPipelineStages(): array
{
$stages = [];
$apiResponse = $this->getNewInstance()->crm()->pipelines()->pipelinesApi()->getAll('deals');
if ($apiResponse instanceof Error) {
$this->log->error('[Hubspot] Failed to fetch opportunity pipelines', [
'reason' => $apiResponse->getMessage(),
]);
return [];
}
foreach ($apiResponse->getResults() as $pipeline) {
$pipelineStages = array_map(
static function (PipelineStage $stage) {
return [
'id' => $stage->getId(),
'label' => $stage->getLabel(),
];
},
$pipeline->getStages()
);
$stages = array_merge($stages, $pipelineStages);
}
return $stages;
}
public function fetchOpportunityPipelines(): array
{
$pipelines = [];
try {
$apiResponse = $this->makeRequest('/crm/v3/pipelines/deals');
} catch (\Exception $e) {
$this->log->info('[Hubspot] Failed to fetch opportunity pipelines', [
'reason' => $e->getMessage(),
]);
return [];
}
$response = $apiResponse->toArray();
foreach ($response['results'] as $pipeline) {
$pipelines[] = [
'id' => $pipeline['id'],
'label' => $pipeline['label'],
];
}
return $pipelines;
}
/**
* @return array<CrmFieldOption>
*/
public function fetchMeetingOutcomeFieldOptions(Field $field): array
{
return $field->getCrmProviderId() === 'meetingOutcome'
? $this->fetchMeetingOutcomeTypes()
: $this->fetchCallActivityTypes();
}
public function fetchMeetingOutcomeTypes(): array
{
return $this->extractMeetingTypeOptions(
'[URL_WITH_CREDENTIALS] Response $response */
$response = $this->getInstance()
->getClient()
->request('GET', $endpoint);
/**
* @var array<array{
* value: string,
* label: string,
* displayOrder: int
* }> $optionData
*/
$optionData = $response->toArray()['options'] ?? [];
$options = [];
foreach ($optionData as $item) {
$options[] = [
'id' => $item['value'],
'value' => $item['value'],
'label' => $item['label'],
'display_order' => $item['displayOrder'],
];
}
return $options;
}
/**
* @return array<CrmFieldOption>
*/
public function fetchDispositionFieldOptions(): array
{
$options = [];
$dispositions = $this->fetchCallDispositions();
foreach ($dispositions as $disposition) {
if ($disposition['deleted'] !== false) {
continue;
}
$option['value'] = $disposition['id'];
$option['id'] = $disposition['id'];
$option['label'] = $disposition['label'];
$options[] = $option;
}
return $options;
}
/**
* @return array<CrmFieldOption>
*/
public function fetchOpportunityFieldOptions(Field $field): array
{
if ($field->isStageField()) {
return $this->fetchOpportunityPipelineStages();
}
if ($field->isPipelineField()) {
return $this->fetchOpportunityPipelines();
}
return $this->fetchPropertyOptions('deals', $field->getCrmProviderId());
}
/**
* @throws BadRequest
* @throws HubspotException
*/
public function makeRequest(string $endpoint, $method = 'GET', $payload = [], ?string $queryString = null)
{
$endpoint = self::BASE_URL . $endpoint;
if ($method === 'GET') {
$response = $this->getInstance()->getClient()?->request(
method: $method,
endpoint: $endpoint,
query_string: $queryString
);
} else {
$response = $this->getInstance()->getClient()->request($method, $endpoint, [
'json' => ($payload),
]);
}
$max = $response->getHeaderLine('X-HubSpot-RateLimit-Max'); // "110"
$remaining = $response->getHeaderLine('X-HubSpot-RateLimit-Remaining'); // "109"
$interval = $response->getHeaderLine('X-HubSpot-RateLimit-Interval-Milliseconds'); // "10000"
$body = json_decode((string) $response->getBody(), true);
\Illuminate\Support\Facades\Log::channel('custom_channel')->info('$max ' . PHP_EOL . print_r($max, true));
\Illuminate\Support\Facades\Log::channel('custom_channel')->info('$remaining ' . PHP_EOL . print_r($remaining, true));
\Illuminate\Support\Facades\Log::channel('custom_channel')->info('$interval ' . PHP_EOL . print_r($interval, true));
return $response;
}
/**
* @throws BadRequest
* @throws HubspotException
*/
public function createMeeting(array $payload): Response
{
$endpoint = '/crm/v3/objects/meetings';
return $this->makeRequest($endpoint, 'POST', $payload);
}
/**
* @throws BadRequest
* @throws HubspotException
*/
public function updateMeeting(string $meetingId, array $payload): Response
{
$endpoint = '/crm/v3/objects/meetings/' . $meetingId;
return $this->makeRequest($endpoint, 'PATCH', $payload);
}
/**
* @throws \Exception
*/
public function createNote(
string $body,
string $ownerId,
int $timestamp,
string $objectId,
NoteObject $noteObject
): ?string {
try {
$noteInput = new SimplePublicObjectInput([
'properties' => [
'hs_note_body' => $body,
'hubspot_owner_id' => $ownerId,
'hs_timestamp' => $timestamp,
],
]);
// Create note
$note = $this->getNewInstance()->crm()->objects()->basicApi()->create('note', $noteInput);
$this->getNewInstance()->crm()->objects()->associationsApi()->create(
'note',
$note->getId(),
$this->getNoteObject($noteObject),
$objectId,
$this->getNoteAssociationType($noteObject),
);
return $note->getId();
} catch (\Exception $e) {
$this->log->error('[Hubspot] Failed to create note', [
'objectId' => $objectId,
'noteObject' => $noteObject->getObjectType(),
'reason' => $e->getMessage(),
]);
\Sentry::captureException($e);
}
return null;
}
public function updateEngagement(string $objectId, array $engagement, array $metadata): void
{
$this->getInstance()->engagements()->update($objectId, $engagement, $metadata);
}
public function getEngagementData(string $engagementId): array
{
$engagement = $this->getInstance()->engagements()->get($engagementId);
return $engagement->toArray();
}
public function createEngagement(array $engagement, array $associations, array $metadata): Response
{
return $this->getInstance()
->engagements()
->create($engagement, $associations, $metadata);
}
public function isUnauthorizedException(\Exception $e): bool
{
// Check for specific HubSpot API exception types first
if ($e instanceof BadRequest) {
// BadRequest can contain 401 status codes
return $e->getCode() === 401;
}
// Check for HTTP client exceptions with status codes
if ($e instanceof \GuzzleHttp\Exception\RequestException && $e->hasResponse()) {
$response = $e->getResponse();
if ($response !== null) {
return $response->getStatusCode() === 401;
}
}
// Check for Guzzle HTTP exceptions
if ($e instanceof \GuzzleHttp\Exception\ClientException) {
return $e->getCode() === 401;
}
// Fallback to string matching as last resort, but be more specific
$message = strtolower($e->getMessage());
return str_contains($message, '401 unauthorized') ||
str_contains($message, 'http 401') ||
str_contains($message, 'status code 401') ||
(preg_match('/\b401\b/', $message) && str_contains($message, 'unauthorized'));
}
/**
* Validates and refreshes the access token if needed before API requests.
* This ensures long-running processes don't fail due to token expiration.
*
* @throws SocialAccountTokenInvalidException
*/
public function ensureValidToken(): void
{
if ($this->oauthAccount === null) {
return;
}
$newToken = $this->tokenManager->ensureValidToken($this->oauthAccount);
if ($newToken !== null) {
$this->accessToken = $newToken;
}
}
public function getConfig()
{
return $this->config;
}
// returns only active (archived=false)
public function getOwners(): array
{
return $this->getNewInstance()->crm()->owners()->getAll();
}
/**
* @param bool $archived
*
* @return array<Owner>|[]
*/
public function getOwnersArchived(bool $archived = true): array
{
$endpoint = '/crm/v3/owners';
$queryParams = [
'archived' => $archived ? 'true' : 'false',
];
$queryString = http_build_query($queryParams);
$owners = [];
try {
$response = $this->makeRequest(endpoint: $endpoint, queryString: $queryString);
$responseData = $response?->toArray();
foreach ($responseData['results'] as $result) {
try {
$owners[] = Owner::create($result);
} catch (Throwable $e) {
$this->log->error('[HubSpot] Failed to process owner data', [
'result' => $result,
'error' => $e->getMessage(),
]);
continue;
}
}
} catch (Throwable $e) {
$this->log->error('HubSpot] Failed to fetch owners', [
'archived' => $archived,
'error' => $e->getMessage(),
]);
return [];
}
return $owners;
}
public function getMeeting(string $engagementId): ObjectWithAssociations
{
return $this->getNewInstance()->crm()->objects()->basicApi()
->getById('meeting', $engagementId, null, 'contact,company,deal');
}
public function deleteEngagement(string $engagementId): void
{
$this->getInstance()->engagements()->delete((int) $engagementId);
}
public function getAssociationsData(array $ids, string $fromObject, string $toObject): array
{
$associationData = [];
$idChunks = array_chunk($ids, self::ASSOCIATIONS_BATCH_SIZE_LIMIT);
foreach ($idChunks as $idChunk) {
try {
$batchInput = new \HubSpot\Client\Crm\Associations\Model\BatchInputPublicObjectId();
$batchInput->setInputs(array_map(function ($id) {
$publicObjectId = new \HubSpot\Client\Crm\Associations\Model\PublicObjectId();
$publicObjectId->setId($id);
return $publicObjectId;
}, $idChunk));
$associatedObjectsData = $this
->getNewInstance()
->crm()
->associations()
->batchApi()
->read($fromObject, $toObject, $batchInput);
if ($associatedObjectsData instanceof \HubSpot\Client\Crm\Associations\Model\BatchResponsePublicAssociationMulti) {
foreach ($associatedObjectsData->getResults() as $association) {
$from = $association->getFrom()->getId();
$toAssociations = $association->getTo();
if (! empty($toAssociations)) {
$associationData[$from] = array_map(function ($item) {
return $item->getId();
}, $toAssociations);
}
}
}
} catch (\Exception $e) {
$this->log->error('[Hubspot] Failed to fetch associations', [
'from_object' => $fromObject,
'to_object' => $toObject,
'reason' => $e->getMessage(),
]);
}
}
return $associationData;
}
/**
* @throws \Exception
*/
private function getNoteAssociationType(NoteObject $noteObject): string
{
return match($noteObject) {
NoteObject::Opportunity => 'note_to_deal',
NoteObject::Lead, NoteObject::Contact => 'note_to_contact', // or 'note_to_lead' if your portal supports it
NoteObject::Account => 'note_to_company',
NoteObject::Call, NoteObject::Event => throw new \Exception('Not supported'),
};
}
/**
* @throws \Exception
*/
private function getNoteObject(NoteObject $noteObject): string
{
return match($noteObject) {
NoteObject::Opportunity => 'deal',
NoteObject::Lead, NoteObject::Contact => 'contact',
NoteObject::Account => 'company',
NoteObject::Call, NoteObject::Event => throw new \Exception('Not supported'),
};
}
public function addAssociations(string $objectType, string $associationType, array $payload): Response
{
$endpoint = "/crm/v4/associations/$objectType/$associationType/batch/create";
return $this->makeRequest($endpoint, 'POST', $payload);
}
public function removeAssociations(string $objectType, string $associationType, array $payload): Response
{
$endpoint = "/crm/v4/associations/$objectType/$associationType/batch/archive";
return $this->makeRequest($endpoint, 'POST', $payload);
}
}
Project
Project
New File or Directory…
Expand Selected
Collapse All
Options
Hide...
|
[{"role":"AXButton","text" [{"role":"AXButton","text":"Project: faVsco.js, menu","depth":5,"on_screen":true,"help_text":"~/jiminny/app","role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"master, menu","depth":5,"on_screen":true,"help_text":"Git Branch: master","role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Start Listening for PHP Debug Connections","depth":5,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"AskJiminnyReportActivityServiceTest","depth":6,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Run 'AskJiminnyReportActivityServiceTest'","depth":6,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Debug 'AskJiminnyReportActivityServiceTest'","depth":6,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"More Actions","depth":6,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"JetBrains AI","depth":5,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Search Everywhere","depth":5,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"IDE and Project Settings","depth":5,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Execute","depth":4,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Explain Plan","depth":4,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Browse Query History","depth":4,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"View Parameters","depth":4,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Open Query Execution Settings…","depth":4,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"In-Editor Results","depth":4,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Tx: Auto","depth":4,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Cancel Running Statements","depth":4,"on_screen":true,"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Playground","depth":4,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"jiminny","depth":4,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Sync Changes","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.018055556,"height":0.026666667},"on_screen":false,"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Hide This Notification","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.018055556,"height":0.026666667},"on_screen":false,"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Code changed:","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.088194445,"height":0.027777778},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Hide","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.018055556,"height":0.026666667},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"6","depth":4,"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"1","depth":4,"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"6","depth":4,"on_screen":true,"role_description":"text"},{"role":"AXButton","text":"Previous Highlighted Error","depth":4,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Next Highlighted Error","depth":4,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXTextArea","text":"# **************************** HS **************************************\n\nselect * from teams where id = 2; # 2\nselect * from features; # 2\nselect * from team_features where team_id = 2; # 2\nselect * from crm_configurations where id = 2; # 2\nselect * from users where team_id = 2; #\nselect * from playbooks where team_id = 2; # event 38\nselect * from playbook_categories where playbook_id = 38; #\n\nSELECT * FROM activities WHERE crm_configuration_id = 2 and crm_provider_id is not null order by id desc;\nhttps://app.hubspot.com/contacts/4392066/deal/16964514951/?engagement=96069102624\n https://app.staging.jiminny.com/playback/d5df34dc-bd66-4ff5-a7b3-8d3be30322a0\n\nSELECT * FROM activities WHERE uuid_to_bin('04fdcd0d-818f-4c53-92dc-6f18bc753ffd') = uuid;\n# 609126 softphone tr. 11241\n\nSELECT * FROM activities WHERE uuid_to_bin('6521bfcd-5a30-46e5-9f74-5440fd48befd') = uuid;\n# 608874 conference tr. 11226 crmId: 103422236596\n\nselect * from ai_prompts where transcription_id IN (11241, 11226);\nselect * from activity_summary_logs where activity_id = 608874;\n\nselect * from sidekick_settings;\nselect * from default_activity_types;\n\nselect * from crm_field_data where activity_id = 1223;\n\nselect * from crm_layouts where crm_configuration_id = 2;\nSELECT * FROM crm_layout_entities WHERE crm_layout_id IN (554);\nselect * from crm_fields where crm_configuration_id = 11 and object_type = 'event';\nSELECT * FROM crm_field_values WHERE crm_field_id IN (1455,1450);\n\nSELECT * FROM crm_field_data WHERE crm_layout_entity_id = 971;\nSELECT * FROM crm_field_data WHERE crm_layout_entity_id IN (6494,6495,6496,6497,6498,6499);\n\nSELECT\n CONCAT(u.id, CASE WHEN u.id = t.owner_id THEN ' (owner)' ELSE '' END) AS user_id,\n u.email,\n sa.*,\n t.owner_id FROM social_accounts sa\nJOIN users u\n on u.id = sa.sociable_id\nJOIN teams t on t.id = u.team_id\nWHERE u.team_id = 2 and sa.provider = 'hubspot';\n\nselect * from social_accounts where id = 1499;\n\nselect * from opportunities where team_id = 2\nand crm_provider_id IN ('51317301383');\n\nselect * from contacts where id = 85;\n\nselect * from opportunities where team_id = 2 order by id desc;\nselect * from opportunities where team_id = 2 and crm_provider_id = '51317301383'; # 5112\nselect * from opportunities where team_id = 2 and crm_provider_id = '55976759904'; # 5112\nselect * from opportunity_contacts where opportunity_id = 5117;\nselect * from crm_field_data where object_id = 1365;\nSELECT * FROM crm_fields WHERE id IN (1405, 1407, 1972, 2128);\n\nselect * from features;\nselect * from team_features where team_id IN (1);\nselect * from team_features where feature_id IN (36);\n\nSHOW CREATE TABLE opportunity_contacts;\nSELECT * FROM opportunity_contacts WHERE crm_provider_id = '111751';\n\n# $slug = 'HUBSPOT_WEBHOOK_SYNC';\n# $team = Jiminny\\Models\\Team::find(2);\n# $feature = Feature::query()->where('slug', $slug)->first();\n# TeamFeature::query()->create(['feature_id' => $feature->getId(),'team_id' => $team->getId()]);\n\n# hubspot_webhook_metrics\n\nselect * from opportunities where team_id = 2 and crm_provider_id IN ('374720564','14527423589','49908861993','50435771779'); # 1365\nSELECT * FROM opportunity_contacts WHERE opportunity_id = '414';\nSELECT * FROM opportunity_contacts WHERE crm_provider_id = '131501';\nselect * from contacts where id in (414, 464);\n\nselect * from activities where crm_configuration_id = 2;\n\nselect settings from crm_configurations where id = 11;\n\nselect * from teams; # 1, 2\nselect * from users;\nselect * from crm_configurations where id = 39;\nselect * from team_features where team_id = 2;\nselect * from features;\n# SELECT * FROM opportunities WHERE crm_configuration_id = 2\n# order by id desc;\n# and crm_provider_id = '49908861993';\n\n\nselect * from activity_providers where id IN (443, 202, 203, 227);\n\nselect * from activity_imports where id = 795889;\n\nselect c.id, c.provider, c.settings, t.* from teams t join crm_configurations c on t.id = c.team_id\nwhere c.provider = 'hubspot';\n\nselect * from crm_configurations crm JOIN teams t on crm.team_id = t.id\nwhere provider = 'hubspot';\nSELECT * FROM teams WHERE id = 31;\nSELECT * FROM users WHERE id = 257;\nSELECT * FROM opportunities WHERE team_id = 2;\n\nselect * from opportunity_contacts where opportunity_id = 5124;\nselect * from contacts where id IN (3850,3853,3851,4073,4140,4155,4480,4530,4623,5986,513,687,1806,1523,3613)\n\nselect * from activities where crm_configuration_id = 13;\n\nSELECT * FROM activities WHERE uuid_to_bin('826619ce-ec8e-4e59-8467-a01f5f6ad71e') = uuid; # 418141\n\n\nselect id, team_id, crm_provider_id from crm_configurations where provider = 'hubspot' and crm_provider_id IS NOT NULL;\nSELECT * FROM accounts WHERE team_id = 2 and crm_provider_id = '1212213464' order by id desc;\nSELECT * FROM contacts WHERE team_id = 2 and account_id = 5189 order by id desc;\nSELECT * FROM contacts WHERE team_id = 2 order by id desc;\nselect * from opportunity_contacts where contact_id = 6223;\nSELECT * FROM opportunities WHERE team_id = 2 and account_id = 5189 order by id desc;\n\nselect * from crm_profiles where crm_configuration_id = 2;\n\nselect * from activities where account_id = 46;","depth":4,"on_screen":true,"value":"# **************************** HS **************************************\n\nselect * from teams where id = 2; # 2\nselect * from features; # 2\nselect * from team_features where team_id = 2; # 2\nselect * from crm_configurations where id = 2; # 2\nselect * from users where team_id = 2; #\nselect * from playbooks where team_id = 2; # event 38\nselect * from playbook_categories where playbook_id = 38; #\n\nSELECT * FROM activities WHERE crm_configuration_id = 2 and crm_provider_id is not null order by id desc;\nhttps://app.hubspot.com/contacts/4392066/deal/16964514951/?engagement=96069102624\n https://app.staging.jiminny.com/playback/d5df34dc-bd66-4ff5-a7b3-8d3be30322a0\n\nSELECT * FROM activities WHERE uuid_to_bin('04fdcd0d-818f-4c53-92dc-6f18bc753ffd') = uuid;\n# 609126 softphone tr. 11241\n\nSELECT * FROM activities WHERE uuid_to_bin('6521bfcd-5a30-46e5-9f74-5440fd48befd') = uuid;\n# 608874 conference tr. 11226 crmId: 103422236596\n\nselect * from ai_prompts where transcription_id IN (11241, 11226);\nselect * from activity_summary_logs where activity_id = 608874;\n\nselect * from sidekick_settings;\nselect * from default_activity_types;\n\nselect * from crm_field_data where activity_id = 1223;\n\nselect * from crm_layouts where crm_configuration_id = 2;\nSELECT * FROM crm_layout_entities WHERE crm_layout_id IN (554);\nselect * from crm_fields where crm_configuration_id = 11 and object_type = 'event';\nSELECT * FROM crm_field_values WHERE crm_field_id IN (1455,1450);\n\nSELECT * FROM crm_field_data WHERE crm_layout_entity_id = 971;\nSELECT * FROM crm_field_data WHERE crm_layout_entity_id IN (6494,6495,6496,6497,6498,6499);\n\nSELECT\n CONCAT(u.id, CASE WHEN u.id = t.owner_id THEN ' (owner)' ELSE '' END) AS user_id,\n u.email,\n sa.*,\n t.owner_id FROM social_accounts sa\nJOIN users u\n on u.id = sa.sociable_id\nJOIN teams t on t.id = u.team_id\nWHERE u.team_id = 2 and sa.provider = 'hubspot';\n\nselect * from social_accounts where id = 1499;\n\nselect * from opportunities where team_id = 2\nand crm_provider_id IN ('51317301383');\n\nselect * from contacts where id = 85;\n\nselect * from opportunities where team_id = 2 order by id desc;\nselect * from opportunities where team_id = 2 and crm_provider_id = '51317301383'; # 5112\nselect * from opportunities where team_id = 2 and crm_provider_id = '55976759904'; # 5112\nselect * from opportunity_contacts where opportunity_id = 5117;\nselect * from crm_field_data where object_id = 1365;\nSELECT * FROM crm_fields WHERE id IN (1405, 1407, 1972, 2128);\n\nselect * from features;\nselect * from team_features where team_id IN (1);\nselect * from team_features where feature_id IN (36);\n\nSHOW CREATE TABLE opportunity_contacts;\nSELECT * FROM opportunity_contacts WHERE crm_provider_id = '111751';\n\n# $slug = 'HUBSPOT_WEBHOOK_SYNC';\n# $team = Jiminny\\Models\\Team::find(2);\n# $feature = Feature::query()->where('slug', $slug)->first();\n# TeamFeature::query()->create(['feature_id' => $feature->getId(),'team_id' => $team->getId()]);\n\n# hubspot_webhook_metrics\n\nselect * from opportunities where team_id = 2 and crm_provider_id IN ('374720564','14527423589','49908861993','50435771779'); # 1365\nSELECT * FROM opportunity_contacts WHERE opportunity_id = '414';\nSELECT * FROM opportunity_contacts WHERE crm_provider_id = '131501';\nselect * from contacts where id in (414, 464);\n\nselect * from activities where crm_configuration_id = 2;\n\nselect settings from crm_configurations where id = 11;\n\nselect * from teams; # 1, 2\nselect * from users;\nselect * from crm_configurations where id = 39;\nselect * from team_features where team_id = 2;\nselect * from features;\n# SELECT * FROM opportunities WHERE crm_configuration_id = 2\n# order by id desc;\n# and crm_provider_id = '49908861993';\n\n\nselect * from activity_providers where id IN (443, 202, 203, 227);\n\nselect * from activity_imports where id = 795889;\n\nselect c.id, c.provider, c.settings, t.* from teams t join crm_configurations c on t.id = c.team_id\nwhere c.provider = 'hubspot';\n\nselect * from crm_configurations crm JOIN teams t on crm.team_id = t.id\nwhere provider = 'hubspot';\nSELECT * FROM teams WHERE id = 31;\nSELECT * FROM users WHERE id = 257;\nSELECT * FROM opportunities WHERE team_id = 2;\n\nselect * from opportunity_contacts where opportunity_id = 5124;\nselect * from contacts where id IN (3850,3853,3851,4073,4140,4155,4480,4530,4623,5986,513,687,1806,1523,3613)\n\nselect * from activities where crm_configuration_id = 13;\n\nSELECT * FROM activities WHERE uuid_to_bin('826619ce-ec8e-4e59-8467-a01f5f6ad71e') = uuid; # 418141\n\n\nselect id, team_id, crm_provider_id from crm_configurations where provider = 'hubspot' and crm_provider_id IS NOT NULL;\nSELECT * FROM accounts WHERE team_id = 2 and crm_provider_id = '1212213464' order by id desc;\nSELECT * FROM contacts WHERE team_id = 2 and account_id = 5189 order by id desc;\nSELECT * FROM contacts WHERE team_id = 2 order by id desc;\nselect * from opportunity_contacts where contact_id = 6223;\nSELECT * FROM opportunities WHERE team_id = 2 and account_id = 5189 order by id desc;\n\nselect * from crm_profiles where crm_configuration_id = 2;\n\nselect * from activities where account_id = 46;","role_description":"text entry area","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Sync Changes","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.018055556,"height":0.026666667},"on_screen":false,"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Hide This Notification","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.018055556,"height":0.026666667},"on_screen":false,"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Code changed:","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.088194445,"height":0.027777778},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Hide","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.018055556,"height":0.026666667},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"2","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.016666668,"height":0.02111111},"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"60","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.021527778,"height":0.02111111},"on_screen":false,"role_description":"text"},{"role":"AXButton","text":"Previous Highlighted Error","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.015277778,"height":0.025555555},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Next Highlighted Error","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.014583333,"height":0.025555555},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXTextArea","text":"<?php\n\ndeclare(strict_types=1);\n\nnamespace Jiminny\\Services\\Crm\\Hubspot;\n\nuse HubSpot\\Client\\Crm\\Deals\\ApiException as DealApiException;\nuse HubSpot\\Client\\Crm\\Contacts\\ApiException as ContactApiException;\nuse HubSpot\\Client\\Crm\\Companies\\ApiException as CompanyApiException;\nuse HubSpot\\Client\\Crm\\Contacts\\Model\\SimplePublicObjectWithAssociations as ContactsWithAssociations;\nuse HubSpot\\Client\\Crm\\Companies\\Model\\SimplePublicObjectWithAssociations as CompaniesWithAssociations;\nuse HubSpot\\Client\\Crm\\Deals\\Model\\SimplePublicObjectWithAssociations as DealWithAssociations;\nuse HubSpot\\Client\\Crm\\Objects\\Model\\SimplePublicObjectInput;\nuse HubSpot\\Client\\Crm\\Objects\\Model\\SimplePublicObjectWithAssociations as ObjectWithAssociations;\nuse HubSpot\\Client\\Crm\\Pipelines\\Model\\Error;\nuse HubSpot\\Client\\Crm\\Pipelines\\Model\\PipelineStage;\nuse HubSpot\\Client\\Crm\\Properties\\Model\\Property;\nuse HubSpot\\Discovery\\Discovery;\nuse Jiminny\\Component\\Utility\\Service\\ProviderRateLimiter;\nuse Jiminny\\Exceptions\\CrmException;\nuse Jiminny\\Exceptions\\RateLimitException;\nuse Jiminny\\Exceptions\\SocialAccountTokenInvalidException;\nuse Jiminny\\Jobs\\Crm\\NoteObject;\nuse Jiminny\\Models\\Crm\\Field;\nuse Jiminny\\Services\\Crm\\BaseClient;\nuse Jiminny\\Services\\Crm\\Hubspot\\DTO\\Response\\Owner;\nuse Jiminny\\Services\\SocialAccountService;\nuse SevenShores\\Hubspot\\Exceptions\\BadRequest;\nuse SevenShores\\Hubspot\\Exceptions\\HubspotException;\nuse SevenShores\\Hubspot\\Factory;\nuse SevenShores\\Hubspot\\Http\\Response;\nuse Jiminny\\Services\\Crm\\Hubspot\\Pagination\\HubspotPaginationService;\nuse Throwable;\n\n/**\n * @phpstan-type CrmFieldOption array{id:string, label:string, value?:string}\n */\nclass Client extends BaseClient implements HubspotClientInterface\n{\n public const string MIN_API_VERSION = '2';\n\n public const string BASE_URL = 'https://api.hubapi.com';\n\n public const int ASSOCIATIONS_BATCH_SIZE_LIMIT = 1000;\n\n private HubspotPaginationService $paginationService;\n private HubspotTokenManager $tokenManager;\n private ProviderRateLimiter $rateLimiter;\n\n public function __construct(\n SocialAccountService $socialAccountService,\n HubspotPaginationService $paginationService,\n HubspotTokenManager $tokenManager,\n ProviderRateLimiter $rateLimiter,\n ) {\n parent::__construct($socialAccountService);\n $this->paginationService = $paginationService;\n $this->tokenManager = $tokenManager;\n $this->rateLimiter = $rateLimiter;\n\n $this->setBaseUrl(self::BASE_URL);\n $this->setVersion(self::MIN_API_VERSION);\n }\n\n /**\n * Single entry point for every HubSpot API call. Enforces the per-portal\n * rate limit configured in the rate_limits table (morphed to the current\n * Configuration) and reacts to a real 429 from HubSpot by translating it\n * into a RateLimitException carrying Retry-After.\n *\n * Wrap any outbound HubSpot call (SDK or raw HTTP) like:\n *\n * $this->executeRequest(fn () => $this->getNewInstance()->crm()->...);\n *\n * @template T\n * @param callable(): T $apiCall\n * @return T\n *\n * @throws RateLimitException\n */\n private function executeRequest(callable $apiCall)\n {\n if (! $this->rateLimiter->canMakeRequest($this->config)) {\n $retryAfter = $this->rateLimiter->requestAvailableIn($this->config);\n\n $this->log->warning('[Hubspot] Rate limit exceeded, deferring request', [\n 'team_id' => $this->config->team_id,\n 'config_id' => $this->config->getId(),\n 'retry_after' => $retryAfter,\n ]);\n\n throw new RateLimitException(\n 'Hubspot rate limit reached for configuration ' . $this->config->getId(),\n $retryAfter,\n );\n }\n\n $this->rateLimiter->incrementRequestCount($this->config);\n\n try {\n return $apiCall();\n } catch (Throwable $e) {\n if ($this->isHubspotRateLimit($e)) {\n $retryAfter = $this->parseRetryAfter($e);\n\n $this->log->warning('[Hubspot] Received 429 from API', [\n 'team_id' => $this->config->team_id,\n 'config_id' => $this->config->getId(),\n 'retry_after' => $retryAfter,\n 'reason' => $e->getMessage(),\n ]);\n\n throw new RateLimitException('Hubspot returned 429', $retryAfter, $e);\n }\n\n throw $e;\n }\n }\n\n private function isHubspotRateLimit(Throwable $e): bool\n {\n return method_exists($e, 'getCode') && (int) $e->getCode() === 429;\n }\n\n private function parseRetryAfter(Throwable $e): int\n {\n if (method_exists($e, 'getResponseHeaders')) {\n $headers = $e->getResponseHeaders() ?: [];\n $value = $headers['Retry-After'] ?? $headers['retry-after'] ?? null;\n if (is_array($value)) {\n $value = $value[0] ?? null;\n }\n if (is_numeric($value)) {\n return (int) $value;\n }\n }\n\n return 10;\n }\n\n public function getMinimumApiVersion(): string\n {\n return self::MIN_API_VERSION;\n }\n\n public function getInstance(): Factory\n {\n return new Factory([\n 'key' => $this->accessToken,\n 'oauth2' => true,\n 'base_url' => $this->baseUrl,\n ]);\n }\n\n public function getNewInstance(): Discovery\n {\n return \\HubSpot\\Factory::createWithAccessToken($this->accessToken);\n }\n\n /**\n * Secondly and daily limits for Hubspot API\n *\n * Product Tier: Free & Starter | Professional & Enterprise | API add-on (any tier)\n * Burst: 100/10 seconds | 150/10 seconds | 200/10 seconds\n * Daily: 250,000 | 500,000 | 1,000,000\n *\n * Official documentation states: The search endpoints are rate limited to five requests per second.\n * Since with 5 RPS were still hitting secondly rate limits we lowered it to 4\n */\n public function getPaginatedData(array $payload, string $type, int $offset = 0): array\n {\n $total = 0;\n $lastId = null;\n $rows = [];\n foreach ($this->getPaginatedDataGenerator($payload, $type, $offset, $total, $lastId) as $row) {\n $rows[] = $row;\n }\n\n return ['results' => $rows, 'total' => $total, 'last_record' => $lastId];\n }\n\n /**\n * @throws HubspotException\n * @throws SocialAccountTokenInvalidException\n * @throws BadRequest\n */\n public function getPaginatedDataGenerator(\n array $payload,\n string $type,\n int $offset = 0,\n int &$total = 0,\n ?string &$lastRecordId = null\n ): \\Generator {\n return $this->paginationService->getPaginatedDataGenerator(\n $this,\n $payload,\n $type,\n $offset,\n $total,\n $lastRecordId\n );\n }\n\n /**\n * @throws DealApiException\n * @throws CrmException\n */\n public function getOpportunityById(string $crmId, array $fields): array\n {\n try {\n $deal = $this->executeRequest(fn () => $this->getNewInstance()->crm()->deals()->basicApi()->getById(\n $crmId,\n implode(',', $fields),\n 'companies,contacts'\n ));\n } catch (DealApiException $e) {\n $this->log->info('[Hubspot] Failed to fetch opportunity', [\n 'crm_id' => $crmId,\n 'reason' => $e->getMessage(),\n ]);\n\n throw $e;\n }\n\n if (! $deal instanceof DealWithAssociations) {\n throw new CrmException('Deal not found');\n }\n\n return [\n 'id' => $deal->getId(),\n 'properties' => $deal->getProperties(),\n 'associations' => $deal->getAssociations(),\n ];\n }\n\n /**\n * Generic batch read method for HubSpot objects\n *\n * @param string $objectType The object type ('deals', 'companies', 'contacts')\n * @param array<string> $crmIds Array of HubSpot object IDs (max 100)\n * @param array<string> $fields Array of property names to fetch\n *\n * @return array<string, array> Array keyed by CRM ID with object data\n */\n private function batchReadObjects(string $objectType, array $crmIds, array $fields): array\n {\n if (empty($crmIds)) {\n return [];\n }\n\n $this->validateBatchSize($objectType, $crmIds);\n $this->ensureValidToken();\n\n try {\n $batchConfig = $this->createBatchConfiguration($objectType);\n $batchReadRequest = $this->prepareBatchRequest($batchConfig, $crmIds, $fields);\n $response = $batchConfig['api']->read($batchReadRequest);\n\n $this->validateApiResponse($response, $objectType);\n\n $results = $this->processApiResults($response);\n $this->logBatchResults($objectType, $crmIds, $results);\n\n return $results;\n } catch (\\Throwable $e) {\n $this->handleBatchError($e, $objectType, $crmIds);\n }\n }\n\n private function validateBatchSize(string $objectType, array $crmIds): void\n {\n if (count($crmIds) > 100) {\n throw new \\InvalidArgumentException(\"Batch size cannot exceed 100 {$objectType}\");\n }\n }\n\n private function createBatchConfiguration(string $objectType): array\n {\n $configurations = [\n 'deals' => [\n 'batchReadRequest' => new \\HubSpot\\Client\\Crm\\Deals\\Model\\BatchReadInputSimplePublicObjectId(),\n 'inputClass' => \\HubSpot\\Client\\Crm\\Deals\\Model\\SimplePublicObjectId::class,\n 'api' => $this->getNewInstance()->crm()->deals()->batchApi(),\n ],\n 'companies' => [\n 'batchReadRequest' => new \\HubSpot\\Client\\Crm\\Companies\\Model\\BatchReadInputSimplePublicObjectId(),\n 'inputClass' => \\HubSpot\\Client\\Crm\\Companies\\Model\\SimplePublicObjectId::class,\n 'api' => $this->getNewInstance()->crm()->companies()->batchApi(),\n ],\n 'contacts' => [\n 'batchReadRequest' => new \\HubSpot\\Client\\Crm\\Contacts\\Model\\BatchReadInputSimplePublicObjectId(),\n 'inputClass' => \\HubSpot\\Client\\Crm\\Contacts\\Model\\SimplePublicObjectId::class,\n 'api' => $this->getNewInstance()->crm()->contacts()->batchApi(),\n ],\n ];\n\n if (! isset($configurations[$objectType])) {\n throw new \\InvalidArgumentException(\"Unsupported object type: {$objectType}\");\n }\n\n return $configurations[$objectType];\n }\n\n private function prepareBatchRequest(array $batchConfig, array $crmIds, array $fields): object\n {\n $batchReadRequest = $batchConfig['batchReadRequest'];\n $inputClass = $batchConfig['inputClass'];\n\n $inputs = array_map(function ($crmId) use ($inputClass) {\n $input = new $inputClass();\n $input->setId($crmId);\n\n return $input;\n }, $crmIds);\n\n $batchReadRequest->setInputs($inputs);\n $batchReadRequest->setProperties($fields);\n\n return $batchReadRequest;\n }\n\n private function validateApiResponse($response, string $objectType): void\n {\n if (! $response) {\n throw new CrmException(\"HubSpot API returned null response for {$objectType} batch read\");\n }\n }\n\n private function processApiResults($response): array\n {\n $results = [];\n $responseResults = $response->getResults();\n\n if ($responseResults) {\n foreach ($responseResults as $object) {\n if ($object && $object->getId()) {\n $results[$object->getId()] = [\n 'id' => $object->getId(),\n 'properties' => $object->getProperties() ?: [],\n ];\n }\n }\n }\n\n return $results;\n }\n\n private function logBatchResults(string $objectType, array $crmIds, array $results): void\n {\n $this->log->info(\"[HubSpot] Batch fetched {$objectType}\", [\n 'requested_count' => count($crmIds),\n 'returned_count' => count($results),\n 'crm_ids' => $crmIds,\n ]);\n }\n\n private function handleBatchError(\\Throwable $e, string $objectType, array $crmIds): void\n {\n $errorMessage = $e->getMessage() ?: 'Unknown error';\n $errorTrace = $e->getTraceAsString() ?: 'No trace available';\n\n $this->log->error(\"[HubSpot] Failed to batch fetch {$objectType}\", [\n 'crm_ids' => $crmIds,\n 'error' => $errorMessage,\n 'trace' => $errorTrace,\n ]);\n\n throw new CrmException(\"Failed to batch fetch {$objectType}: \" . $errorMessage);\n }\n\n /**\n * Batch read multiple opportunities by their CRM IDs\n *\n * @param array<string> $crmIds Array of HubSpot deal IDs (max 100)\n * @param array<string> $fields Array of property names to fetch\n *\n * @return array<string, array> Array keyed by CRM ID with opportunity data\n */\n public function getOpportunitiesByIds(array $crmIds, array $fields): array\n {\n return $this->batchReadObjects('deals', $crmIds, $fields);\n }\n\n /**\n * Batch read multiple companies by their CRM IDs\n *\n * @param array<string> $crmIds Array of HubSpot company IDs (max 100)\n * @param array<string> $fields Array of property names to fetch\n *\n * @return array<string, array> Array keyed by CRM ID with company data\n */\n public function getCompaniesByIds(array $crmIds, array $fields): array\n {\n return $this->batchReadObjects('companies', $crmIds, $fields);\n }\n\n /**\n * Batch read multiple contacts by their CRM IDs\n *\n * @param array<string> $crmIds Array of HubSpot contact IDs (max 100)\n * @param array<string> $fields Array of property names to fetch\n *\n * @return array<string, array> Array keyed by CRM ID with contact data\n */\n public function getContactsByIds(array $crmIds, array $fields): array\n {\n return $this->batchReadObjects('contacts', $crmIds, $fields);\n }\n\n /**\n * @throws CompanyApiException\n * @throws CrmException\n */\n public function getAccountById(string $crmId, array $fields): array\n {\n try {\n $company = $this->getNewInstance()->crm()->companies()->basicApi()->getById(\n $crmId,\n implode(',', $fields),\n );\n } catch (CompanyApiException $e) {\n $this->log->info('[Hubspot] Failed to fetch account', [\n 'crm_id' => $crmId,\n 'reason' => $e->getMessage(),\n ]);\n\n throw $e;\n }\n\n if (! $company instanceof CompaniesWithAssociations) {\n throw new CrmException('Account not found');\n }\n\n return [\n 'id' => $company->getId(),\n 'properties' => $company->getProperties(),\n ];\n }\n\n /**\n * @throws ContactApiException\n * @throws CrmException\n */\n public function getContactById(string $crmId, array $fields): array\n {\n try {\n $contact = $this->getNewInstance()->crm()->contacts()->basicApi()->getById(\n $crmId,\n implode(',', $fields)\n );\n } catch (ContactApiException $e) {\n $this->log->info('[Hubspot] Failed to fetch contact', [\n 'crm_id' => $crmId,\n 'reason' => $e->getMessage(),\n ]);\n\n throw $e;\n }\n\n if (! $contact instanceof ContactsWithAssociations) {\n throw new CrmException('Contact not found');\n }\n\n return [\n 'id' => $contact->getId(),\n 'properties' => $contact->getProperties(),\n ];\n }\n\n /**\n * This is email search request that Hubspot offers as GET (more generous quota)\n */\n public function getContactByEmail(string $email, array $fields = []): array\n {\n try {\n $contact = $this->getNewInstance()->crm()->contacts()->basicApi()->getById(\n $email,\n implode(',', $fields),\n null,\n false,\n 'email'\n );\n\n return [\n 'id' => $contact->getId(),\n 'properties' => $contact->getProperties(),\n ];\n } catch (ContactApiException $e) {\n $this->log->info('[Hubspot] Failed to fetch contact', [\n 'email' => $email,\n 'reason' => $e->getMessage(),\n ]);\n\n return [];\n }\n }\n\n /**\n * @throws CrmException\n */\n public function fetchProperty(string $objectType, string $propertyId): Property\n {\n $result = $this->getNewInstance()->crm()->properties()->coreApi()->getByName($objectType, $propertyId);\n\n if (! $result instanceof Property) {\n $this->log->error('[Hubspot] Failed to fetch property', [\n 'object_type' => $objectType,\n 'property_id' => $propertyId,\n 'reason' => $result->getMessage(),\n ]);\n\n throw new CrmException('Failed to fetch property');\n }\n\n return $result;\n }\n\n /**\n * @return array<CrmFieldOption>\n */\n public function fetchPropertyOptions(string $objectType, string $propertyId): array\n {\n /** @var array<CrmFieldOption> */\n return $this->fetchProperty($objectType, $propertyId)->getOptions();\n }\n\n /**\n * @return array<array{id:string, label:string, deleted:bool}>\n */\n public function fetchCallDispositions(): array\n {\n /** @var Response $response */\n $response = $this->getInstance()->engagements()->getCallDispositions();\n\n /**\n * @var array<array{\n * id:string,\n * label:string,\n * deleted: bool\n * }>\n */\n return $response->toArray();\n }\n\n /**\n * @return array<CrmFieldOption>\n */\n public function fetchOpportunityPipelineStages(): array\n {\n $stages = [];\n $apiResponse = $this->getNewInstance()->crm()->pipelines()->pipelinesApi()->getAll('deals');\n\n if ($apiResponse instanceof Error) {\n $this->log->error('[Hubspot] Failed to fetch opportunity pipelines', [\n 'reason' => $apiResponse->getMessage(),\n ]);\n\n return [];\n }\n\n foreach ($apiResponse->getResults() as $pipeline) {\n $pipelineStages = array_map(\n static function (PipelineStage $stage) {\n return [\n 'id' => $stage->getId(),\n 'label' => $stage->getLabel(),\n ];\n },\n $pipeline->getStages()\n );\n\n $stages = array_merge($stages, $pipelineStages);\n }\n\n return $stages;\n }\n\n public function fetchOpportunityPipelines(): array\n {\n $pipelines = [];\n\n try {\n $apiResponse = $this->makeRequest('/crm/v3/pipelines/deals');\n } catch (\\Exception $e) {\n $this->log->info('[Hubspot] Failed to fetch opportunity pipelines', [\n 'reason' => $e->getMessage(),\n ]);\n\n return [];\n }\n\n $response = $apiResponse->toArray();\n\n foreach ($response['results'] as $pipeline) {\n $pipelines[] = [\n 'id' => $pipeline['id'],\n 'label' => $pipeline['label'],\n ];\n }\n\n return $pipelines;\n }\n\n /**\n * @return array<CrmFieldOption>\n */\n public function fetchMeetingOutcomeFieldOptions(Field $field): array\n {\n return $field->getCrmProviderId() === 'meetingOutcome'\n ? $this->fetchMeetingOutcomeTypes()\n : $this->fetchCallActivityTypes();\n }\n\n public function fetchMeetingOutcomeTypes(): array\n {\n return $this->extractMeetingTypeOptions(\n 'https://api.hubapi.com/crm/v3/properties/meeting/hs_meeting_outcome'\n );\n }\n\n public function fetchCallActivityTypes(): array\n {\n return $this->extractMeetingTypeOptions(\n 'https://api.hubapi.com/crm/v3/properties/call/hs_activity_type'\n );\n }\n\n private function extractMeetingTypeOptions(string $endpoint): array\n {\n /** @var Response $response */\n $response = $this->getInstance()\n ->getClient()\n ->request('GET', $endpoint);\n\n /**\n * @var array<array{\n * value: string,\n * label: string,\n * displayOrder: int\n * }> $optionData\n */\n $optionData = $response->toArray()['options'] ?? [];\n\n $options = [];\n foreach ($optionData as $item) {\n $options[] = [\n 'id' => $item['value'],\n 'value' => $item['value'],\n 'label' => $item['label'],\n 'display_order' => $item['displayOrder'],\n ];\n }\n\n return $options;\n }\n\n /**\n * @return array<CrmFieldOption>\n */\n public function fetchDispositionFieldOptions(): array\n {\n $options = [];\n\n $dispositions = $this->fetchCallDispositions();\n\n foreach ($dispositions as $disposition) {\n if ($disposition['deleted'] !== false) {\n continue;\n }\n\n $option['value'] = $disposition['id'];\n $option['id'] = $disposition['id'];\n $option['label'] = $disposition['label'];\n\n $options[] = $option;\n }\n\n return $options;\n }\n\n /**\n * @return array<CrmFieldOption>\n */\n public function fetchOpportunityFieldOptions(Field $field): array\n {\n if ($field->isStageField()) {\n return $this->fetchOpportunityPipelineStages();\n }\n\n if ($field->isPipelineField()) {\n return $this->fetchOpportunityPipelines();\n }\n\n return $this->fetchPropertyOptions('deals', $field->getCrmProviderId());\n }\n\n /**\n * @throws BadRequest\n * @throws HubspotException\n */\n public function makeRequest(string $endpoint, $method = 'GET', $payload = [], ?string $queryString = null)\n {\n $endpoint = self::BASE_URL . $endpoint;\n\n if ($method === 'GET') {\n $response = $this->getInstance()->getClient()?->request(\n method: $method,\n endpoint: $endpoint,\n query_string: $queryString\n );\n } else {\n $response = $this->getInstance()->getClient()->request($method, $endpoint, [\n 'json' => ($payload),\n ]);\n }\n \n $max = $response->getHeaderLine('X-HubSpot-RateLimit-Max'); // \"110\"\n $remaining = $response->getHeaderLine('X-HubSpot-RateLimit-Remaining'); // \"109\"\n $interval = $response->getHeaderLine('X-HubSpot-RateLimit-Interval-Milliseconds'); // \"10000\"\n $body = json_decode((string) $response->getBody(), true);\n \n \\Illuminate\\Support\\Facades\\Log::channel('custom_channel')->info('$max ' . PHP_EOL . print_r($max, true));\n \\Illuminate\\Support\\Facades\\Log::channel('custom_channel')->info('$remaining ' . PHP_EOL . print_r($remaining, true));\n \\Illuminate\\Support\\Facades\\Log::channel('custom_channel')->info('$interval ' . PHP_EOL . print_r($interval, true));\n \n return $response;\n }\n\n /**\n * @throws BadRequest\n * @throws HubspotException\n */\n public function createMeeting(array $payload): Response\n {\n $endpoint = '/crm/v3/objects/meetings';\n\n return $this->makeRequest($endpoint, 'POST', $payload);\n }\n\n /**\n * @throws BadRequest\n * @throws HubspotException\n */\n public function updateMeeting(string $meetingId, array $payload): Response\n {\n $endpoint = '/crm/v3/objects/meetings/' . $meetingId;\n\n return $this->makeRequest($endpoint, 'PATCH', $payload);\n }\n\n /**\n * @throws \\Exception\n */\n public function createNote(\n string $body,\n string $ownerId,\n int $timestamp,\n string $objectId,\n NoteObject $noteObject\n ): ?string {\n try {\n $noteInput = new SimplePublicObjectInput([\n 'properties' => [\n 'hs_note_body' => $body,\n 'hubspot_owner_id' => $ownerId,\n 'hs_timestamp' => $timestamp,\n ],\n ]);\n\n // Create note\n $note = $this->getNewInstance()->crm()->objects()->basicApi()->create('note', $noteInput);\n\n $this->getNewInstance()->crm()->objects()->associationsApi()->create(\n 'note',\n $note->getId(),\n $this->getNoteObject($noteObject),\n $objectId,\n $this->getNoteAssociationType($noteObject),\n );\n\n return $note->getId();\n } catch (\\Exception $e) {\n $this->log->error('[Hubspot] Failed to create note', [\n 'objectId' => $objectId,\n 'noteObject' => $noteObject->getObjectType(),\n 'reason' => $e->getMessage(),\n ]);\n\n \\Sentry::captureException($e);\n }\n\n return null;\n }\n\n public function updateEngagement(string $objectId, array $engagement, array $metadata): void\n {\n $this->getInstance()->engagements()->update($objectId, $engagement, $metadata);\n }\n\n public function getEngagementData(string $engagementId): array\n {\n $engagement = $this->getInstance()->engagements()->get($engagementId);\n\n return $engagement->toArray();\n }\n\n public function createEngagement(array $engagement, array $associations, array $metadata): Response\n {\n return $this->getInstance()\n ->engagements()\n ->create($engagement, $associations, $metadata);\n }\n\n public function isUnauthorizedException(\\Exception $e): bool\n {\n // Check for specific HubSpot API exception types first\n if ($e instanceof BadRequest) {\n // BadRequest can contain 401 status codes\n return $e->getCode() === 401;\n }\n\n // Check for HTTP client exceptions with status codes\n if ($e instanceof \\GuzzleHttp\\Exception\\RequestException && $e->hasResponse()) {\n $response = $e->getResponse();\n if ($response !== null) {\n return $response->getStatusCode() === 401;\n }\n }\n\n // Check for Guzzle HTTP exceptions\n if ($e instanceof \\GuzzleHttp\\Exception\\ClientException) {\n return $e->getCode() === 401;\n }\n\n // Fallback to string matching as last resort, but be more specific\n $message = strtolower($e->getMessage());\n\n return str_contains($message, '401 unauthorized') ||\n str_contains($message, 'http 401') ||\n str_contains($message, 'status code 401') ||\n (preg_match('/\\b401\\b/', $message) && str_contains($message, 'unauthorized'));\n }\n\n /**\n * Validates and refreshes the access token if needed before API requests.\n * This ensures long-running processes don't fail due to token expiration.\n *\n * @throws SocialAccountTokenInvalidException\n */\n public function ensureValidToken(): void\n {\n if ($this->oauthAccount === null) {\n return;\n }\n\n $newToken = $this->tokenManager->ensureValidToken($this->oauthAccount);\n if ($newToken !== null) {\n $this->accessToken = $newToken;\n }\n }\n\n public function getConfig()\n {\n return $this->config;\n }\n\n // returns only active (archived=false)\n public function getOwners(): array\n {\n return $this->getNewInstance()->crm()->owners()->getAll();\n }\n\n /**\n * @param bool $archived\n *\n * @return array<Owner>|[]\n */\n public function getOwnersArchived(bool $archived = true): array\n {\n $endpoint = '/crm/v3/owners';\n $queryParams = [\n 'archived' => $archived ? 'true' : 'false',\n ];\n $queryString = http_build_query($queryParams);\n\n $owners = [];\n\n try {\n $response = $this->makeRequest(endpoint: $endpoint, queryString: $queryString);\n $responseData = $response?->toArray();\n\n foreach ($responseData['results'] as $result) {\n try {\n $owners[] = Owner::create($result);\n } catch (Throwable $e) {\n $this->log->error('[HubSpot] Failed to process owner data', [\n 'result' => $result,\n 'error' => $e->getMessage(),\n ]);\n\n continue;\n }\n }\n } catch (Throwable $e) {\n $this->log->error('HubSpot] Failed to fetch owners', [\n 'archived' => $archived,\n 'error' => $e->getMessage(),\n ]);\n\n return [];\n }\n\n return $owners;\n }\n\n public function getMeeting(string $engagementId): ObjectWithAssociations\n {\n return $this->getNewInstance()->crm()->objects()->basicApi()\n ->getById('meeting', $engagementId, null, 'contact,company,deal');\n }\n\n public function deleteEngagement(string $engagementId): void\n {\n $this->getInstance()->engagements()->delete((int) $engagementId);\n }\n\n public function getAssociationsData(array $ids, string $fromObject, string $toObject): array\n {\n $associationData = [];\n $idChunks = array_chunk($ids, self::ASSOCIATIONS_BATCH_SIZE_LIMIT);\n\n foreach ($idChunks as $idChunk) {\n try {\n $batchInput = new \\HubSpot\\Client\\Crm\\Associations\\Model\\BatchInputPublicObjectId();\n $batchInput->setInputs(array_map(function ($id) {\n $publicObjectId = new \\HubSpot\\Client\\Crm\\Associations\\Model\\PublicObjectId();\n $publicObjectId->setId($id);\n\n return $publicObjectId;\n }, $idChunk));\n\n $associatedObjectsData = $this\n ->getNewInstance()\n ->crm()\n ->associations()\n ->batchApi()\n ->read($fromObject, $toObject, $batchInput);\n\n if ($associatedObjectsData instanceof \\HubSpot\\Client\\Crm\\Associations\\Model\\BatchResponsePublicAssociationMulti) {\n foreach ($associatedObjectsData->getResults() as $association) {\n $from = $association->getFrom()->getId();\n $toAssociations = $association->getTo();\n\n if (! empty($toAssociations)) {\n $associationData[$from] = array_map(function ($item) {\n return $item->getId();\n }, $toAssociations);\n }\n }\n }\n } catch (\\Exception $e) {\n $this->log->error('[Hubspot] Failed to fetch associations', [\n 'from_object' => $fromObject,\n 'to_object' => $toObject,\n 'reason' => $e->getMessage(),\n ]);\n }\n }\n\n return $associationData;\n }\n\n /**\n * @throws \\Exception\n */\n private function getNoteAssociationType(NoteObject $noteObject): string\n {\n return match($noteObject) {\n NoteObject::Opportunity => 'note_to_deal',\n NoteObject::Lead, NoteObject::Contact => 'note_to_contact', // or 'note_to_lead' if your portal supports it\n NoteObject::Account => 'note_to_company',\n NoteObject::Call, NoteObject::Event => throw new \\Exception('Not supported'),\n };\n }\n\n /**\n * @throws \\Exception\n */\n private function getNoteObject(NoteObject $noteObject): string\n {\n return match($noteObject) {\n NoteObject::Opportunity => 'deal',\n NoteObject::Lead, NoteObject::Contact => 'contact',\n NoteObject::Account => 'company',\n NoteObject::Call, NoteObject::Event => throw new \\Exception('Not supported'),\n };\n }\n\n public function addAssociations(string $objectType, string $associationType, array $payload): Response\n {\n $endpoint = \"/crm/v4/associations/$objectType/$associationType/batch/create\";\n\n return $this->makeRequest($endpoint, 'POST', $payload);\n }\n\n public function removeAssociations(string $objectType, string $associationType, array $payload): Response\n {\n $endpoint = \"/crm/v4/associations/$objectType/$associationType/batch/archive\";\n\n return $this->makeRequest($endpoint, 'POST', $payload);\n }\n}","depth":4,"on_screen":true,"value":"<?php\n\ndeclare(strict_types=1);\n\nnamespace Jiminny\\Services\\Crm\\Hubspot;\n\nuse HubSpot\\Client\\Crm\\Deals\\ApiException as DealApiException;\nuse HubSpot\\Client\\Crm\\Contacts\\ApiException as ContactApiException;\nuse HubSpot\\Client\\Crm\\Companies\\ApiException as CompanyApiException;\nuse HubSpot\\Client\\Crm\\Contacts\\Model\\SimplePublicObjectWithAssociations as ContactsWithAssociations;\nuse HubSpot\\Client\\Crm\\Companies\\Model\\SimplePublicObjectWithAssociations as CompaniesWithAssociations;\nuse HubSpot\\Client\\Crm\\Deals\\Model\\SimplePublicObjectWithAssociations as DealWithAssociations;\nuse HubSpot\\Client\\Crm\\Objects\\Model\\SimplePublicObjectInput;\nuse HubSpot\\Client\\Crm\\Objects\\Model\\SimplePublicObjectWithAssociations as ObjectWithAssociations;\nuse HubSpot\\Client\\Crm\\Pipelines\\Model\\Error;\nuse HubSpot\\Client\\Crm\\Pipelines\\Model\\PipelineStage;\nuse HubSpot\\Client\\Crm\\Properties\\Model\\Property;\nuse HubSpot\\Discovery\\Discovery;\nuse Jiminny\\Component\\Utility\\Service\\ProviderRateLimiter;\nuse Jiminny\\Exceptions\\CrmException;\nuse Jiminny\\Exceptions\\RateLimitException;\nuse Jiminny\\Exceptions\\SocialAccountTokenInvalidException;\nuse Jiminny\\Jobs\\Crm\\NoteObject;\nuse Jiminny\\Models\\Crm\\Field;\nuse Jiminny\\Services\\Crm\\BaseClient;\nuse Jiminny\\Services\\Crm\\Hubspot\\DTO\\Response\\Owner;\nuse Jiminny\\Services\\SocialAccountService;\nuse SevenShores\\Hubspot\\Exceptions\\BadRequest;\nuse SevenShores\\Hubspot\\Exceptions\\HubspotException;\nuse SevenShores\\Hubspot\\Factory;\nuse SevenShores\\Hubspot\\Http\\Response;\nuse Jiminny\\Services\\Crm\\Hubspot\\Pagination\\HubspotPaginationService;\nuse Throwable;\n\n/**\n * @phpstan-type CrmFieldOption array{id:string, label:string, value?:string}\n */\nclass Client extends BaseClient implements HubspotClientInterface\n{\n public const string MIN_API_VERSION = '2';\n\n public const string BASE_URL = 'https://api.hubapi.com';\n\n public const int ASSOCIATIONS_BATCH_SIZE_LIMIT = 1000;\n\n private HubspotPaginationService $paginationService;\n private HubspotTokenManager $tokenManager;\n private ProviderRateLimiter $rateLimiter;\n\n public function __construct(\n SocialAccountService $socialAccountService,\n HubspotPaginationService $paginationService,\n HubspotTokenManager $tokenManager,\n ProviderRateLimiter $rateLimiter,\n ) {\n parent::__construct($socialAccountService);\n $this->paginationService = $paginationService;\n $this->tokenManager = $tokenManager;\n $this->rateLimiter = $rateLimiter;\n\n $this->setBaseUrl(self::BASE_URL);\n $this->setVersion(self::MIN_API_VERSION);\n }\n\n /**\n * Single entry point for every HubSpot API call. Enforces the per-portal\n * rate limit configured in the rate_limits table (morphed to the current\n * Configuration) and reacts to a real 429 from HubSpot by translating it\n * into a RateLimitException carrying Retry-After.\n *\n * Wrap any outbound HubSpot call (SDK or raw HTTP) like:\n *\n * $this->executeRequest(fn () => $this->getNewInstance()->crm()->...);\n *\n * @template T\n * @param callable(): T $apiCall\n * @return T\n *\n * @throws RateLimitException\n */\n private function executeRequest(callable $apiCall)\n {\n if (! $this->rateLimiter->canMakeRequest($this->config)) {\n $retryAfter = $this->rateLimiter->requestAvailableIn($this->config);\n\n $this->log->warning('[Hubspot] Rate limit exceeded, deferring request', [\n 'team_id' => $this->config->team_id,\n 'config_id' => $this->config->getId(),\n 'retry_after' => $retryAfter,\n ]);\n\n throw new RateLimitException(\n 'Hubspot rate limit reached for configuration ' . $this->config->getId(),\n $retryAfter,\n );\n }\n\n $this->rateLimiter->incrementRequestCount($this->config);\n\n try {\n return $apiCall();\n } catch (Throwable $e) {\n if ($this->isHubspotRateLimit($e)) {\n $retryAfter = $this->parseRetryAfter($e);\n\n $this->log->warning('[Hubspot] Received 429 from API', [\n 'team_id' => $this->config->team_id,\n 'config_id' => $this->config->getId(),\n 'retry_after' => $retryAfter,\n 'reason' => $e->getMessage(),\n ]);\n\n throw new RateLimitException('Hubspot returned 429', $retryAfter, $e);\n }\n\n throw $e;\n }\n }\n\n private function isHubspotRateLimit(Throwable $e): bool\n {\n return method_exists($e, 'getCode') && (int) $e->getCode() === 429;\n }\n\n private function parseRetryAfter(Throwable $e): int\n {\n if (method_exists($e, 'getResponseHeaders')) {\n $headers = $e->getResponseHeaders() ?: [];\n $value = $headers['Retry-After'] ?? $headers['retry-after'] ?? null;\n if (is_array($value)) {\n $value = $value[0] ?? null;\n }\n if (is_numeric($value)) {\n return (int) $value;\n }\n }\n\n return 10;\n }\n\n public function getMinimumApiVersion(): string\n {\n return self::MIN_API_VERSION;\n }\n\n public function getInstance(): Factory\n {\n return new Factory([\n 'key' => $this->accessToken,\n 'oauth2' => true,\n 'base_url' => $this->baseUrl,\n ]);\n }\n\n public function getNewInstance(): Discovery\n {\n return \\HubSpot\\Factory::createWithAccessToken($this->accessToken);\n }\n\n /**\n * Secondly and daily limits for Hubspot API\n *\n * Product Tier: Free & Starter | Professional & Enterprise | API add-on (any tier)\n * Burst: 100/10 seconds | 150/10 seconds | 200/10 seconds\n * Daily: 250,000 | 500,000 | 1,000,000\n *\n * Official documentation states: The search endpoints are rate limited to five requests per second.\n * Since with 5 RPS were still hitting secondly rate limits we lowered it to 4\n */\n public function getPaginatedData(array $payload, string $type, int $offset = 0): array\n {\n $total = 0;\n $lastId = null;\n $rows = [];\n foreach ($this->getPaginatedDataGenerator($payload, $type, $offset, $total, $lastId) as $row) {\n $rows[] = $row;\n }\n\n return ['results' => $rows, 'total' => $total, 'last_record' => $lastId];\n }\n\n /**\n * @throws HubspotException\n * @throws SocialAccountTokenInvalidException\n * @throws BadRequest\n */\n public function getPaginatedDataGenerator(\n array $payload,\n string $type,\n int $offset = 0,\n int &$total = 0,\n ?string &$lastRecordId = null\n ): \\Generator {\n return $this->paginationService->getPaginatedDataGenerator(\n $this,\n $payload,\n $type,\n $offset,\n $total,\n $lastRecordId\n );\n }\n\n /**\n * @throws DealApiException\n * @throws CrmException\n */\n public function getOpportunityById(string $crmId, array $fields): array\n {\n try {\n $deal = $this->executeRequest(fn () => $this->getNewInstance()->crm()->deals()->basicApi()->getById(\n $crmId,\n implode(',', $fields),\n 'companies,contacts'\n ));\n } catch (DealApiException $e) {\n $this->log->info('[Hubspot] Failed to fetch opportunity', [\n 'crm_id' => $crmId,\n 'reason' => $e->getMessage(),\n ]);\n\n throw $e;\n }\n\n if (! $deal instanceof DealWithAssociations) {\n throw new CrmException('Deal not found');\n }\n\n return [\n 'id' => $deal->getId(),\n 'properties' => $deal->getProperties(),\n 'associations' => $deal->getAssociations(),\n ];\n }\n\n /**\n * Generic batch read method for HubSpot objects\n *\n * @param string $objectType The object type ('deals', 'companies', 'contacts')\n * @param array<string> $crmIds Array of HubSpot object IDs (max 100)\n * @param array<string> $fields Array of property names to fetch\n *\n * @return array<string, array> Array keyed by CRM ID with object data\n */\n private function batchReadObjects(string $objectType, array $crmIds, array $fields): array\n {\n if (empty($crmIds)) {\n return [];\n }\n\n $this->validateBatchSize($objectType, $crmIds);\n $this->ensureValidToken();\n\n try {\n $batchConfig = $this->createBatchConfiguration($objectType);\n $batchReadRequest = $this->prepareBatchRequest($batchConfig, $crmIds, $fields);\n $response = $batchConfig['api']->read($batchReadRequest);\n\n $this->validateApiResponse($response, $objectType);\n\n $results = $this->processApiResults($response);\n $this->logBatchResults($objectType, $crmIds, $results);\n\n return $results;\n } catch (\\Throwable $e) {\n $this->handleBatchError($e, $objectType, $crmIds);\n }\n }\n\n private function validateBatchSize(string $objectType, array $crmIds): void\n {\n if (count($crmIds) > 100) {\n throw new \\InvalidArgumentException(\"Batch size cannot exceed 100 {$objectType}\");\n }\n }\n\n private function createBatchConfiguration(string $objectType): array\n {\n $configurations = [\n 'deals' => [\n 'batchReadRequest' => new \\HubSpot\\Client\\Crm\\Deals\\Model\\BatchReadInputSimplePublicObjectId(),\n 'inputClass' => \\HubSpot\\Client\\Crm\\Deals\\Model\\SimplePublicObjectId::class,\n 'api' => $this->getNewInstance()->crm()->deals()->batchApi(),\n ],\n 'companies' => [\n 'batchReadRequest' => new \\HubSpot\\Client\\Crm\\Companies\\Model\\BatchReadInputSimplePublicObjectId(),\n 'inputClass' => \\HubSpot\\Client\\Crm\\Companies\\Model\\SimplePublicObjectId::class,\n 'api' => $this->getNewInstance()->crm()->companies()->batchApi(),\n ],\n 'contacts' => [\n 'batchReadRequest' => new \\HubSpot\\Client\\Crm\\Contacts\\Model\\BatchReadInputSimplePublicObjectId(),\n 'inputClass' => \\HubSpot\\Client\\Crm\\Contacts\\Model\\SimplePublicObjectId::class,\n 'api' => $this->getNewInstance()->crm()->contacts()->batchApi(),\n ],\n ];\n\n if (! isset($configurations[$objectType])) {\n throw new \\InvalidArgumentException(\"Unsupported object type: {$objectType}\");\n }\n\n return $configurations[$objectType];\n }\n\n private function prepareBatchRequest(array $batchConfig, array $crmIds, array $fields): object\n {\n $batchReadRequest = $batchConfig['batchReadRequest'];\n $inputClass = $batchConfig['inputClass'];\n\n $inputs = array_map(function ($crmId) use ($inputClass) {\n $input = new $inputClass();\n $input->setId($crmId);\n\n return $input;\n }, $crmIds);\n\n $batchReadRequest->setInputs($inputs);\n $batchReadRequest->setProperties($fields);\n\n return $batchReadRequest;\n }\n\n private function validateApiResponse($response, string $objectType): void\n {\n if (! $response) {\n throw new CrmException(\"HubSpot API returned null response for {$objectType} batch read\");\n }\n }\n\n private function processApiResults($response): array\n {\n $results = [];\n $responseResults = $response->getResults();\n\n if ($responseResults) {\n foreach ($responseResults as $object) {\n if ($object && $object->getId()) {\n $results[$object->getId()] = [\n 'id' => $object->getId(),\n 'properties' => $object->getProperties() ?: [],\n ];\n }\n }\n }\n\n return $results;\n }\n\n private function logBatchResults(string $objectType, array $crmIds, array $results): void\n {\n $this->log->info(\"[HubSpot] Batch fetched {$objectType}\", [\n 'requested_count' => count($crmIds),\n 'returned_count' => count($results),\n 'crm_ids' => $crmIds,\n ]);\n }\n\n private function handleBatchError(\\Throwable $e, string $objectType, array $crmIds): void\n {\n $errorMessage = $e->getMessage() ?: 'Unknown error';\n $errorTrace = $e->getTraceAsString() ?: 'No trace available';\n\n $this->log->error(\"[HubSpot] Failed to batch fetch {$objectType}\", [\n 'crm_ids' => $crmIds,\n 'error' => $errorMessage,\n 'trace' => $errorTrace,\n ]);\n\n throw new CrmException(\"Failed to batch fetch {$objectType}: \" . $errorMessage);\n }\n\n /**\n * Batch read multiple opportunities by their CRM IDs\n *\n * @param array<string> $crmIds Array of HubSpot deal IDs (max 100)\n * @param array<string> $fields Array of property names to fetch\n *\n * @return array<string, array> Array keyed by CRM ID with opportunity data\n */\n public function getOpportunitiesByIds(array $crmIds, array $fields): array\n {\n return $this->batchReadObjects('deals', $crmIds, $fields);\n }\n\n /**\n * Batch read multiple companies by their CRM IDs\n *\n * @param array<string> $crmIds Array of HubSpot company IDs (max 100)\n * @param array<string> $fields Array of property names to fetch\n *\n * @return array<string, array> Array keyed by CRM ID with company data\n */\n public function getCompaniesByIds(array $crmIds, array $fields): array\n {\n return $this->batchReadObjects('companies', $crmIds, $fields);\n }\n\n /**\n * Batch read multiple contacts by their CRM IDs\n *\n * @param array<string> $crmIds Array of HubSpot contact IDs (max 100)\n * @param array<string> $fields Array of property names to fetch\n *\n * @return array<string, array> Array keyed by CRM ID with contact data\n */\n public function getContactsByIds(array $crmIds, array $fields): array\n {\n return $this->batchReadObjects('contacts', $crmIds, $fields);\n }\n\n /**\n * @throws CompanyApiException\n * @throws CrmException\n */\n public function getAccountById(string $crmId, array $fields): array\n {\n try {\n $company = $this->getNewInstance()->crm()->companies()->basicApi()->getById(\n $crmId,\n implode(',', $fields),\n );\n } catch (CompanyApiException $e) {\n $this->log->info('[Hubspot] Failed to fetch account', [\n 'crm_id' => $crmId,\n 'reason' => $e->getMessage(),\n ]);\n\n throw $e;\n }\n\n if (! $company instanceof CompaniesWithAssociations) {\n throw new CrmException('Account not found');\n }\n\n return [\n 'id' => $company->getId(),\n 'properties' => $company->getProperties(),\n ];\n }\n\n /**\n * @throws ContactApiException\n * @throws CrmException\n */\n public function getContactById(string $crmId, array $fields): array\n {\n try {\n $contact = $this->getNewInstance()->crm()->contacts()->basicApi()->getById(\n $crmId,\n implode(',', $fields)\n );\n } catch (ContactApiException $e) {\n $this->log->info('[Hubspot] Failed to fetch contact', [\n 'crm_id' => $crmId,\n 'reason' => $e->getMessage(),\n ]);\n\n throw $e;\n }\n\n if (! $contact instanceof ContactsWithAssociations) {\n throw new CrmException('Contact not found');\n }\n\n return [\n 'id' => $contact->getId(),\n 'properties' => $contact->getProperties(),\n ];\n }\n\n /**\n * This is email search request that Hubspot offers as GET (more generous quota)\n */\n public function getContactByEmail(string $email, array $fields = []): array\n {\n try {\n $contact = $this->getNewInstance()->crm()->contacts()->basicApi()->getById(\n $email,\n implode(',', $fields),\n null,\n false,\n 'email'\n );\n\n return [\n 'id' => $contact->getId(),\n 'properties' => $contact->getProperties(),\n ];\n } catch (ContactApiException $e) {\n $this->log->info('[Hubspot] Failed to fetch contact', [\n 'email' => $email,\n 'reason' => $e->getMessage(),\n ]);\n\n return [];\n }\n }\n\n /**\n * @throws CrmException\n */\n public function fetchProperty(string $objectType, string $propertyId): Property\n {\n $result = $this->getNewInstance()->crm()->properties()->coreApi()->getByName($objectType, $propertyId);\n\n if (! $result instanceof Property) {\n $this->log->error('[Hubspot] Failed to fetch property', [\n 'object_type' => $objectType,\n 'property_id' => $propertyId,\n 'reason' => $result->getMessage(),\n ]);\n\n throw new CrmException('Failed to fetch property');\n }\n\n return $result;\n }\n\n /**\n * @return array<CrmFieldOption>\n */\n public function fetchPropertyOptions(string $objectType, string $propertyId): array\n {\n /** @var array<CrmFieldOption> */\n return $this->fetchProperty($objectType, $propertyId)->getOptions();\n }\n\n /**\n * @return array<array{id:string, label:string, deleted:bool}>\n */\n public function fetchCallDispositions(): array\n {\n /** @var Response $response */\n $response = $this->getInstance()->engagements()->getCallDispositions();\n\n /**\n * @var array<array{\n * id:string,\n * label:string,\n * deleted: bool\n * }>\n */\n return $response->toArray();\n }\n\n /**\n * @return array<CrmFieldOption>\n */\n public function fetchOpportunityPipelineStages(): array\n {\n $stages = [];\n $apiResponse = $this->getNewInstance()->crm()->pipelines()->pipelinesApi()->getAll('deals');\n\n if ($apiResponse instanceof Error) {\n $this->log->error('[Hubspot] Failed to fetch opportunity pipelines', [\n 'reason' => $apiResponse->getMessage(),\n ]);\n\n return [];\n }\n\n foreach ($apiResponse->getResults() as $pipeline) {\n $pipelineStages = array_map(\n static function (PipelineStage $stage) {\n return [\n 'id' => $stage->getId(),\n 'label' => $stage->getLabel(),\n ];\n },\n $pipeline->getStages()\n );\n\n $stages = array_merge($stages, $pipelineStages);\n }\n\n return $stages;\n }\n\n public function fetchOpportunityPipelines(): array\n {\n $pipelines = [];\n\n try {\n $apiResponse = $this->makeRequest('/crm/v3/pipelines/deals');\n } catch (\\Exception $e) {\n $this->log->info('[Hubspot] Failed to fetch opportunity pipelines', [\n 'reason' => $e->getMessage(),\n ]);\n\n return [];\n }\n\n $response = $apiResponse->toArray();\n\n foreach ($response['results'] as $pipeline) {\n $pipelines[] = [\n 'id' => $pipeline['id'],\n 'label' => $pipeline['label'],\n ];\n }\n\n return $pipelines;\n }\n\n /**\n * @return array<CrmFieldOption>\n */\n public function fetchMeetingOutcomeFieldOptions(Field $field): array\n {\n return $field->getCrmProviderId() === 'meetingOutcome'\n ? $this->fetchMeetingOutcomeTypes()\n : $this->fetchCallActivityTypes();\n }\n\n public function fetchMeetingOutcomeTypes(): array\n {\n return $this->extractMeetingTypeOptions(\n 'https://api.hubapi.com/crm/v3/properties/meeting/hs_meeting_outcome'\n );\n }\n\n public function fetchCallActivityTypes(): array\n {\n return $this->extractMeetingTypeOptions(\n 'https://api.hubapi.com/crm/v3/properties/call/hs_activity_type'\n );\n }\n\n private function extractMeetingTypeOptions(string $endpoint): array\n {\n /** @var Response $response */\n $response = $this->getInstance()\n ->getClient()\n ->request('GET', $endpoint);\n\n /**\n * @var array<array{\n * value: string,\n * label: string,\n * displayOrder: int\n * }> $optionData\n */\n $optionData = $response->toArray()['options'] ?? [];\n\n $options = [];\n foreach ($optionData as $item) {\n $options[] = [\n 'id' => $item['value'],\n 'value' => $item['value'],\n 'label' => $item['label'],\n 'display_order' => $item['displayOrder'],\n ];\n }\n\n return $options;\n }\n\n /**\n * @return array<CrmFieldOption>\n */\n public function fetchDispositionFieldOptions(): array\n {\n $options = [];\n\n $dispositions = $this->fetchCallDispositions();\n\n foreach ($dispositions as $disposition) {\n if ($disposition['deleted'] !== false) {\n continue;\n }\n\n $option['value'] = $disposition['id'];\n $option['id'] = $disposition['id'];\n $option['label'] = $disposition['label'];\n\n $options[] = $option;\n }\n\n return $options;\n }\n\n /**\n * @return array<CrmFieldOption>\n */\n public function fetchOpportunityFieldOptions(Field $field): array\n {\n if ($field->isStageField()) {\n return $this->fetchOpportunityPipelineStages();\n }\n\n if ($field->isPipelineField()) {\n return $this->fetchOpportunityPipelines();\n }\n\n return $this->fetchPropertyOptions('deals', $field->getCrmProviderId());\n }\n\n /**\n * @throws BadRequest\n * @throws HubspotException\n */\n public function makeRequest(string $endpoint, $method = 'GET', $payload = [], ?string $queryString = null)\n {\n $endpoint = self::BASE_URL . $endpoint;\n\n if ($method === 'GET') {\n $response = $this->getInstance()->getClient()?->request(\n method: $method,\n endpoint: $endpoint,\n query_string: $queryString\n );\n } else {\n $response = $this->getInstance()->getClient()->request($method, $endpoint, [\n 'json' => ($payload),\n ]);\n }\n \n $max = $response->getHeaderLine('X-HubSpot-RateLimit-Max'); // \"110\"\n $remaining = $response->getHeaderLine('X-HubSpot-RateLimit-Remaining'); // \"109\"\n $interval = $response->getHeaderLine('X-HubSpot-RateLimit-Interval-Milliseconds'); // \"10000\"\n $body = json_decode((string) $response->getBody(), true);\n \n \\Illuminate\\Support\\Facades\\Log::channel('custom_channel')->info('$max ' . PHP_EOL . print_r($max, true));\n \\Illuminate\\Support\\Facades\\Log::channel('custom_channel')->info('$remaining ' . PHP_EOL . print_r($remaining, true));\n \\Illuminate\\Support\\Facades\\Log::channel('custom_channel')->info('$interval ' . PHP_EOL . print_r($interval, true));\n \n return $response;\n }\n\n /**\n * @throws BadRequest\n * @throws HubspotException\n */\n public function createMeeting(array $payload): Response\n {\n $endpoint = '/crm/v3/objects/meetings';\n\n return $this->makeRequest($endpoint, 'POST', $payload);\n }\n\n /**\n * @throws BadRequest\n * @throws HubspotException\n */\n public function updateMeeting(string $meetingId, array $payload): Response\n {\n $endpoint = '/crm/v3/objects/meetings/' . $meetingId;\n\n return $this->makeRequest($endpoint, 'PATCH', $payload);\n }\n\n /**\n * @throws \\Exception\n */\n public function createNote(\n string $body,\n string $ownerId,\n int $timestamp,\n string $objectId,\n NoteObject $noteObject\n ): ?string {\n try {\n $noteInput = new SimplePublicObjectInput([\n 'properties' => [\n 'hs_note_body' => $body,\n 'hubspot_owner_id' => $ownerId,\n 'hs_timestamp' => $timestamp,\n ],\n ]);\n\n // Create note\n $note = $this->getNewInstance()->crm()->objects()->basicApi()->create('note', $noteInput);\n\n $this->getNewInstance()->crm()->objects()->associationsApi()->create(\n 'note',\n $note->getId(),\n $this->getNoteObject($noteObject),\n $objectId,\n $this->getNoteAssociationType($noteObject),\n );\n\n return $note->getId();\n } catch (\\Exception $e) {\n $this->log->error('[Hubspot] Failed to create note', [\n 'objectId' => $objectId,\n 'noteObject' => $noteObject->getObjectType(),\n 'reason' => $e->getMessage(),\n ]);\n\n \\Sentry::captureException($e);\n }\n\n return null;\n }\n\n public function updateEngagement(string $objectId, array $engagement, array $metadata): void\n {\n $this->getInstance()->engagements()->update($objectId, $engagement, $metadata);\n }\n\n public function getEngagementData(string $engagementId): array\n {\n $engagement = $this->getInstance()->engagements()->get($engagementId);\n\n return $engagement->toArray();\n }\n\n public function createEngagement(array $engagement, array $associations, array $metadata): Response\n {\n return $this->getInstance()\n ->engagements()\n ->create($engagement, $associations, $metadata);\n }\n\n public function isUnauthorizedException(\\Exception $e): bool\n {\n // Check for specific HubSpot API exception types first\n if ($e instanceof BadRequest) {\n // BadRequest can contain 401 status codes\n return $e->getCode() === 401;\n }\n\n // Check for HTTP client exceptions with status codes\n if ($e instanceof \\GuzzleHttp\\Exception\\RequestException && $e->hasResponse()) {\n $response = $e->getResponse();\n if ($response !== null) {\n return $response->getStatusCode() === 401;\n }\n }\n\n // Check for Guzzle HTTP exceptions\n if ($e instanceof \\GuzzleHttp\\Exception\\ClientException) {\n return $e->getCode() === 401;\n }\n\n // Fallback to string matching as last resort, but be more specific\n $message = strtolower($e->getMessage());\n\n return str_contains($message, '401 unauthorized') ||\n str_contains($message, 'http 401') ||\n str_contains($message, 'status code 401') ||\n (preg_match('/\\b401\\b/', $message) && str_contains($message, 'unauthorized'));\n }\n\n /**\n * Validates and refreshes the access token if needed before API requests.\n * This ensures long-running processes don't fail due to token expiration.\n *\n * @throws SocialAccountTokenInvalidException\n */\n public function ensureValidToken(): void\n {\n if ($this->oauthAccount === null) {\n return;\n }\n\n $newToken = $this->tokenManager->ensureValidToken($this->oauthAccount);\n if ($newToken !== null) {\n $this->accessToken = $newToken;\n }\n }\n\n public function getConfig()\n {\n return $this->config;\n }\n\n // returns only active (archived=false)\n public function getOwners(): array\n {\n return $this->getNewInstance()->crm()->owners()->getAll();\n }\n\n /**\n * @param bool $archived\n *\n * @return array<Owner>|[]\n */\n public function getOwnersArchived(bool $archived = true): array\n {\n $endpoint = '/crm/v3/owners';\n $queryParams = [\n 'archived' => $archived ? 'true' : 'false',\n ];\n $queryString = http_build_query($queryParams);\n\n $owners = [];\n\n try {\n $response = $this->makeRequest(endpoint: $endpoint, queryString: $queryString);\n $responseData = $response?->toArray();\n\n foreach ($responseData['results'] as $result) {\n try {\n $owners[] = Owner::create($result);\n } catch (Throwable $e) {\n $this->log->error('[HubSpot] Failed to process owner data', [\n 'result' => $result,\n 'error' => $e->getMessage(),\n ]);\n\n continue;\n }\n }\n } catch (Throwable $e) {\n $this->log->error('HubSpot] Failed to fetch owners', [\n 'archived' => $archived,\n 'error' => $e->getMessage(),\n ]);\n\n return [];\n }\n\n return $owners;\n }\n\n public function getMeeting(string $engagementId): ObjectWithAssociations\n {\n return $this->getNewInstance()->crm()->objects()->basicApi()\n ->getById('meeting', $engagementId, null, 'contact,company,deal');\n }\n\n public function deleteEngagement(string $engagementId): void\n {\n $this->getInstance()->engagements()->delete((int) $engagementId);\n }\n\n public function getAssociationsData(array $ids, string $fromObject, string $toObject): array\n {\n $associationData = [];\n $idChunks = array_chunk($ids, self::ASSOCIATIONS_BATCH_SIZE_LIMIT);\n\n foreach ($idChunks as $idChunk) {\n try {\n $batchInput = new \\HubSpot\\Client\\Crm\\Associations\\Model\\BatchInputPublicObjectId();\n $batchInput->setInputs(array_map(function ($id) {\n $publicObjectId = new \\HubSpot\\Client\\Crm\\Associations\\Model\\PublicObjectId();\n $publicObjectId->setId($id);\n\n return $publicObjectId;\n }, $idChunk));\n\n $associatedObjectsData = $this\n ->getNewInstance()\n ->crm()\n ->associations()\n ->batchApi()\n ->read($fromObject, $toObject, $batchInput);\n\n if ($associatedObjectsData instanceof \\HubSpot\\Client\\Crm\\Associations\\Model\\BatchResponsePublicAssociationMulti) {\n foreach ($associatedObjectsData->getResults() as $association) {\n $from = $association->getFrom()->getId();\n $toAssociations = $association->getTo();\n\n if (! empty($toAssociations)) {\n $associationData[$from] = array_map(function ($item) {\n return $item->getId();\n }, $toAssociations);\n }\n }\n }\n } catch (\\Exception $e) {\n $this->log->error('[Hubspot] Failed to fetch associations', [\n 'from_object' => $fromObject,\n 'to_object' => $toObject,\n 'reason' => $e->getMessage(),\n ]);\n }\n }\n\n return $associationData;\n }\n\n /**\n * @throws \\Exception\n */\n private function getNoteAssociationType(NoteObject $noteObject): string\n {\n return match($noteObject) {\n NoteObject::Opportunity => 'note_to_deal',\n NoteObject::Lead, NoteObject::Contact => 'note_to_contact', // or 'note_to_lead' if your portal supports it\n NoteObject::Account => 'note_to_company',\n NoteObject::Call, NoteObject::Event => throw new \\Exception('Not supported'),\n };\n }\n\n /**\n * @throws \\Exception\n */\n private function getNoteObject(NoteObject $noteObject): string\n {\n return match($noteObject) {\n NoteObject::Opportunity => 'deal',\n NoteObject::Lead, NoteObject::Contact => 'contact',\n NoteObject::Account => 'company',\n NoteObject::Call, NoteObject::Event => throw new \\Exception('Not supported'),\n };\n }\n\n public function addAssociations(string $objectType, string $associationType, array $payload): Response\n {\n $endpoint = \"/crm/v4/associations/$objectType/$associationType/batch/create\";\n\n return $this->makeRequest($endpoint, 'POST', $payload);\n }\n\n public function removeAssociations(string $objectType, string $associationType, array $payload): Response\n {\n $endpoint = \"/crm/v4/associations/$objectType/$associationType/batch/archive\";\n\n return $this->makeRequest($endpoint, 'POST', $payload);\n }\n}","role_description":"text entry area","is_enabled":true,"is_focused":true,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"Project","depth":3,"on_screen":false,"role_description":"text"},{"role":"AXButton","text":"Project","depth":3,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"New File or Directory…","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.018055556,"height":0.026666667},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Expand Selected","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.018055556,"height":0.026666667},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Collapse All","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.018055556,"height":0.026666667},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Options","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.018055556,"height":0.026666667},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Hide","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.018055556,"height":0.026666667},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false}]...
|
-3585960046888772555
|
5531537304406333540
|
click
|
accessibility
|
NULL
|
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
Execute
Explain Plan
Browse Query History
View Parameters
Open Query Execution Settings…
In-Editor Results
Tx: Auto
Cancel Running Statements
Playground
jiminny
Sync Changes
Hide This Notification
Code changed:
Hide
6
1
6
Previous Highlighted Error
Next Highlighted Error
# [PASSWORD_DOTS] HS [PASSWORD_DOTS]
select * from teams where id = 2; # 2
select * from features; # 2
select * from team_features where team_id = 2; # 2
select * from crm_configurations where id = 2; # 2
select * from users where team_id = 2; #
select * from playbooks where team_id = 2; # event 38
select * from playbook_categories where playbook_id = 38; #
SELECT * FROM activities WHERE crm_configuration_id = 2 and crm_provider_id is not null order by id desc;
[URL_WITH_CREDENTIALS] CrmFieldOption array{id:string, label:string, value?:string}
*/
class Client extends BaseClient implements HubspotClientInterface
{
public const string MIN_API_VERSION = '2';
public const string BASE_URL = '[URL_WITH_CREDENTIALS] T
* @param callable(): T $apiCall
* @return T
*
* @throws RateLimitException
*/
private function executeRequest(callable $apiCall)
{
if (! $this->rateLimiter->canMakeRequest($this->config)) {
$retryAfter = $this->rateLimiter->requestAvailableIn($this->config);
$this->log->warning('[Hubspot] Rate limit exceeded, deferring request', [
'team_id' => $this->config->team_id,
'config_id' => $this->config->getId(),
'retry_after' => $retryAfter,
]);
throw new RateLimitException(
'Hubspot rate limit reached for configuration ' . $this->config->getId(),
$retryAfter,
);
}
$this->rateLimiter->incrementRequestCount($this->config);
try {
return $apiCall();
} catch (Throwable $e) {
if ($this->isHubspotRateLimit($e)) {
$retryAfter = $this->parseRetryAfter($e);
$this->log->warning('[Hubspot] Received 429 from API', [
'team_id' => $this->config->team_id,
'config_id' => $this->config->getId(),
'retry_after' => $retryAfter,
'reason' => $e->getMessage(),
]);
throw new RateLimitException('Hubspot returned 429', $retryAfter, $e);
}
throw $e;
}
}
private function isHubspotRateLimit(Throwable $e): bool
{
return method_exists($e, 'getCode') && (int) $e->getCode() === 429;
}
private function parseRetryAfter(Throwable $e): int
{
if (method_exists($e, 'getResponseHeaders')) {
$headers = $e->getResponseHeaders() ?: [];
$value = $headers['Retry-After'] ?? $headers['retry-after'] ?? null;
if (is_array($value)) {
$value = $value[0] ?? null;
}
if (is_numeric($value)) {
return (int) $value;
}
}
return 10;
}
public function getMinimumApiVersion(): string
{
return self::MIN_API_VERSION;
}
public function getInstance(): Factory
{
return new Factory([
'key' => $this->accessToken,
'oauth2' => true,
'base_url' => $this->baseUrl,
]);
}
public function getNewInstance(): Discovery
{
return \HubSpot\Factory::createWithAccessToken($this->accessToken);
}
/**
* Secondly and daily limits for Hubspot API
*
* Product Tier: Free & Starter | Professional & Enterprise | API add-on (any tier)
* Burst: 100/10 seconds | 150/10 seconds | 200/10 seconds
* Daily: 250,000 | 500,000 | 1,000,000
*
* Official documentation states: The search endpoints are rate limited to five requests per second.
* Since with 5 RPS were still hitting secondly rate limits we lowered it to 4
*/
public function getPaginatedData(array $payload, string $type, int $offset = 0): array
{
$total = 0;
$lastId = null;
$rows = [];
foreach ($this->getPaginatedDataGenerator($payload, $type, $offset, $total, $lastId) as $row) {
$rows[] = $row;
}
return ['results' => $rows, 'total' => $total, 'last_record' => $lastId];
}
/**
* @throws HubspotException
* @throws SocialAccountTokenInvalidException
* @throws BadRequest
*/
public function getPaginatedDataGenerator(
array $payload,
string $type,
int $offset = 0,
int &$total = 0,
?string &$lastRecordId = null
): \Generator {
return $this->paginationService->getPaginatedDataGenerator(
$this,
$payload,
$type,
$offset,
$total,
$lastRecordId
);
}
/**
* @throws DealApiException
* @throws CrmException
*/
public function getOpportunityById(string $crmId, array $fields): array
{
try {
$deal = $this->executeRequest(fn () => $this->getNewInstance()->crm()->deals()->basicApi()->getById(
$crmId,
implode(',', $fields),
'companies,contacts'
));
} catch (DealApiException $e) {
$this->log->info('[Hubspot] Failed to fetch opportunity', [
'crm_id' => $crmId,
'reason' => $e->getMessage(),
]);
throw $e;
}
if (! $deal instanceof DealWithAssociations) {
throw new CrmException('Deal not found');
}
return [
'id' => $deal->getId(),
'properties' => $deal->getProperties(),
'associations' => $deal->getAssociations(),
];
}
/**
* Generic batch read method for HubSpot objects
*
* @param string $objectType The object type ('deals', 'companies', 'contacts')
* @param array<string> $crmIds Array of HubSpot object IDs (max 100)
* @param array<string> $fields Array of property names to fetch
*
* @return array<string, array> Array keyed by CRM ID with object data
*/
private function batchReadObjects(string $objectType, array $crmIds, array $fields): array
{
if (empty($crmIds)) {
return [];
}
$this->validateBatchSize($objectType, $crmIds);
$this->ensureValidToken();
try {
$batchConfig = $this->createBatchConfiguration($objectType);
$batchReadRequest = $this->prepareBatchRequest($batchConfig, $crmIds, $fields);
$response = $batchConfig['api']->read($batchReadRequest);
$this->validateApiResponse($response, $objectType);
$results = $this->processApiResults($response);
$this->logBatchResults($objectType, $crmIds, $results);
return $results;
} catch (\Throwable $e) {
$this->handleBatchError($e, $objectType, $crmIds);
}
}
private function validateBatchSize(string $objectType, array $crmIds): void
{
if (count($crmIds) > 100) {
throw new \InvalidArgumentException("Batch size cannot exceed 100 {$objectType}");
}
}
private function createBatchConfiguration(string $objectType): array
{
$configurations = [
'deals' => [
'batchReadRequest' => new \HubSpot\Client\Crm\Deals\Model\BatchReadInputSimplePublicObjectId(),
'inputClass' => \HubSpot\Client\Crm\Deals\Model\SimplePublicObjectId::class,
'api' => $this->getNewInstance()->crm()->deals()->batchApi(),
],
'companies' => [
'batchReadRequest' => new \HubSpot\Client\Crm\Companies\Model\BatchReadInputSimplePublicObjectId(),
'inputClass' => \HubSpot\Client\Crm\Companies\Model\SimplePublicObjectId::class,
'api' => $this->getNewInstance()->crm()->companies()->batchApi(),
],
'contacts' => [
'batchReadRequest' => new \HubSpot\Client\Crm\Contacts\Model\BatchReadInputSimplePublicObjectId(),
'inputClass' => \HubSpot\Client\Crm\Contacts\Model\SimplePublicObjectId::class,
'api' => $this->getNewInstance()->crm()->contacts()->batchApi(),
],
];
if (! isset($configurations[$objectType])) {
throw new \InvalidArgumentException("Unsupported object type: {$objectType}");
}
return $configurations[$objectType];
}
private function prepareBatchRequest(array $batchConfig, array $crmIds, array $fields): object
{
$batchReadRequest = $batchConfig['batchReadRequest'];
$inputClass = $batchConfig['inputClass'];
$inputs = array_map(function ($crmId) use ($inputClass) {
$input = new $inputClass();
$input->setId($crmId);
return $input;
}, $crmIds);
$batchReadRequest->setInputs($inputs);
$batchReadRequest->setProperties($fields);
return $batchReadRequest;
}
private function validateApiResponse($response, string $objectType): void
{
if (! $response) {
throw new CrmException("HubSpot API returned null response for {$objectType} batch read");
}
}
private function processApiResults($response): array
{
$results = [];
$responseResults = $response->getResults();
if ($responseResults) {
foreach ($responseResults as $object) {
if ($object && $object->getId()) {
$results[$object->getId()] = [
'id' => $object->getId(),
'properties' => $object->getProperties() ?: [],
];
}
}
}
return $results;
}
private function logBatchResults(string $objectType, array $crmIds, array $results): void
{
$this->log->info("[HubSpot] Batch fetched {$objectType}", [
'requested_count' => count($crmIds),
'returned_count' => count($results),
'crm_ids' => $crmIds,
]);
}
private function handleBatchError(\Throwable $e, string $objectType, array $crmIds): void
{
$errorMessage = $e->getMessage() ?: 'Unknown error';
$errorTrace = $e->getTraceAsString() ?: 'No trace available';
$this->log->error("[HubSpot] Failed to batch fetch {$objectType}", [
'crm_ids' => $crmIds,
'error' => $errorMessage,
'trace' => $errorTrace,
]);
throw new CrmException("Failed to batch fetch {$objectType}: " . $errorMessage);
}
/**
* Batch read multiple opportunities by their CRM IDs
*
* @param array<string> $crmIds Array of HubSpot deal IDs (max 100)
* @param array<string> $fields Array of property names to fetch
*
* @return array<string, array> Array keyed by CRM ID with opportunity data
*/
public function getOpportunitiesByIds(array $crmIds, array $fields): array
{
return $this->batchReadObjects('deals', $crmIds, $fields);
}
/**
* Batch read multiple companies by their CRM IDs
*
* @param array<string> $crmIds Array of HubSpot company IDs (max 100)
* @param array<string> $fields Array of property names to fetch
*
* @return array<string, array> Array keyed by CRM ID with company data
*/
public function getCompaniesByIds(array $crmIds, array $fields): array
{
return $this->batchReadObjects('companies', $crmIds, $fields);
}
/**
* Batch read multiple contacts by their CRM IDs
*
* @param array<string> $crmIds Array of HubSpot contact IDs (max 100)
* @param array<string> $fields Array of property names to fetch
*
* @return array<string, array> Array keyed by CRM ID with contact data
*/
public function getContactsByIds(array $crmIds, array $fields): array
{
return $this->batchReadObjects('contacts', $crmIds, $fields);
}
/**
* @throws CompanyApiException
* @throws CrmException
*/
public function getAccountById(string $crmId, array $fields): array
{
try {
$company = $this->getNewInstance()->crm()->companies()->basicApi()->getById(
$crmId,
implode(',', $fields),
);
} catch (CompanyApiException $e) {
$this->log->info('[Hubspot] Failed to fetch account', [
'crm_id' => $crmId,
'reason' => $e->getMessage(),
]);
throw $e;
}
if (! $company instanceof CompaniesWithAssociations) {
throw new CrmException('Account not found');
}
return [
'id' => $company->getId(),
'properties' => $company->getProperties(),
];
}
/**
* @throws ContactApiException
* @throws CrmException
*/
public function getContactById(string $crmId, array $fields): array
{
try {
$contact = $this->getNewInstance()->crm()->contacts()->basicApi()->getById(
$crmId,
implode(',', $fields)
);
} catch (ContactApiException $e) {
$this->log->info('[Hubspot] Failed to fetch contact', [
'crm_id' => $crmId,
'reason' => $e->getMessage(),
]);
throw $e;
}
if (! $contact instanceof ContactsWithAssociations) {
throw new CrmException('Contact not found');
}
return [
'id' => $contact->getId(),
'properties' => $contact->getProperties(),
];
}
/**
* This is email search request that Hubspot offers as GET (more generous quota)
*/
public function getContactByEmail(string $email, array $fields = []): array
{
try {
$contact = $this->getNewInstance()->crm()->contacts()->basicApi()->getById(
$email,
implode(',', $fields),
null,
false,
'email'
);
return [
'id' => $contact->getId(),
'properties' => $contact->getProperties(),
];
} catch (ContactApiException $e) {
$this->log->info('[Hubspot] Failed to fetch contact', [
'email' => $email,
'reason' => $e->getMessage(),
]);
return [];
}
}
/**
* @throws CrmException
*/
public function fetchProperty(string $objectType, string $propertyId): Property
{
$result = $this->getNewInstance()->crm()->properties()->coreApi()->getByName($objectType, $propertyId);
if (! $result instanceof Property) {
$this->log->error('[Hubspot] Failed to fetch property', [
'object_type' => $objectType,
'property_id' => $propertyId,
'reason' => $result->getMessage(),
]);
throw new CrmException('Failed to fetch property');
}
return $result;
}
/**
* @return array<CrmFieldOption>
*/
public function fetchPropertyOptions(string $objectType, string $propertyId): array
{
/** @var array<CrmFieldOption> */
return $this->fetchProperty($objectType, $propertyId)->getOptions();
}
/**
* @return array<array{id:string, label:string, deleted:bool}>
*/
public function fetchCallDispositions(): array
{
/** @var Response $response */
$response = $this->getInstance()->engagements()->getCallDispositions();
/**
* @var array<array{
* id:string,
* label:string,
* deleted: bool
* }>
*/
return $response->toArray();
}
/**
* @return array<CrmFieldOption>
*/
public function fetchOpportunityPipelineStages(): array
{
$stages = [];
$apiResponse = $this->getNewInstance()->crm()->pipelines()->pipelinesApi()->getAll('deals');
if ($apiResponse instanceof Error) {
$this->log->error('[Hubspot] Failed to fetch opportunity pipelines', [
'reason' => $apiResponse->getMessage(),
]);
return [];
}
foreach ($apiResponse->getResults() as $pipeline) {
$pipelineStages = array_map(
static function (PipelineStage $stage) {
return [
'id' => $stage->getId(),
'label' => $stage->getLabel(),
];
},
$pipeline->getStages()
);
$stages = array_merge($stages, $pipelineStages);
}
return $stages;
}
public function fetchOpportunityPipelines(): array
{
$pipelines = [];
try {
$apiResponse = $this->makeRequest('/crm/v3/pipelines/deals');
} catch (\Exception $e) {
$this->log->info('[Hubspot] Failed to fetch opportunity pipelines', [
'reason' => $e->getMessage(),
]);
return [];
}
$response = $apiResponse->toArray();
foreach ($response['results'] as $pipeline) {
$pipelines[] = [
'id' => $pipeline['id'],
'label' => $pipeline['label'],
];
}
return $pipelines;
}
/**
* @return array<CrmFieldOption>
*/
public function fetchMeetingOutcomeFieldOptions(Field $field): array
{
return $field->getCrmProviderId() === 'meetingOutcome'
? $this->fetchMeetingOutcomeTypes()
: $this->fetchCallActivityTypes();
}
public function fetchMeetingOutcomeTypes(): array
{
return $this->extractMeetingTypeOptions(
'[URL_WITH_CREDENTIALS] Response $response */
$response = $this->getInstance()
->getClient()
->request('GET', $endpoint);
/**
* @var array<array{
* value: string,
* label: string,
* displayOrder: int
* }> $optionData
*/
$optionData = $response->toArray()['options'] ?? [];
$options = [];
foreach ($optionData as $item) {
$options[] = [
'id' => $item['value'],
'value' => $item['value'],
'label' => $item['label'],
'display_order' => $item['displayOrder'],
];
}
return $options;
}
/**
* @return array<CrmFieldOption>
*/
public function fetchDispositionFieldOptions(): array
{
$options = [];
$dispositions = $this->fetchCallDispositions();
foreach ($dispositions as $disposition) {
if ($disposition['deleted'] !== false) {
continue;
}
$option['value'] = $disposition['id'];
$option['id'] = $disposition['id'];
$option['label'] = $disposition['label'];
$options[] = $option;
}
return $options;
}
/**
* @return array<CrmFieldOption>
*/
public function fetchOpportunityFieldOptions(Field $field): array
{
if ($field->isStageField()) {
return $this->fetchOpportunityPipelineStages();
}
if ($field->isPipelineField()) {
return $this->fetchOpportunityPipelines();
}
return $this->fetchPropertyOptions('deals', $field->getCrmProviderId());
}
/**
* @throws BadRequest
* @throws HubspotException
*/
public function makeRequest(string $endpoint, $method = 'GET', $payload = [], ?string $queryString = null)
{
$endpoint = self::BASE_URL . $endpoint;
if ($method === 'GET') {
$response = $this->getInstance()->getClient()?->request(
method: $method,
endpoint: $endpoint,
query_string: $queryString
);
} else {
$response = $this->getInstance()->getClient()->request($method, $endpoint, [
'json' => ($payload),
]);
}
$max = $response->getHeaderLine('X-HubSpot-RateLimit-Max'); // "110"
$remaining = $response->getHeaderLine('X-HubSpot-RateLimit-Remaining'); // "109"
$interval = $response->getHeaderLine('X-HubSpot-RateLimit-Interval-Milliseconds'); // "10000"
$body = json_decode((string) $response->getBody(), true);
\Illuminate\Support\Facades\Log::channel('custom_channel')->info('$max ' . PHP_EOL . print_r($max, true));
\Illuminate\Support\Facades\Log::channel('custom_channel')->info('$remaining ' . PHP_EOL . print_r($remaining, true));
\Illuminate\Support\Facades\Log::channel('custom_channel')->info('$interval ' . PHP_EOL . print_r($interval, true));
return $response;
}
/**
* @throws BadRequest
* @throws HubspotException
*/
public function createMeeting(array $payload): Response
{
$endpoint = '/crm/v3/objects/meetings';
return $this->makeRequest($endpoint, 'POST', $payload);
}
/**
* @throws BadRequest
* @throws HubspotException
*/
public function updateMeeting(string $meetingId, array $payload): Response
{
$endpoint = '/crm/v3/objects/meetings/' . $meetingId;
return $this->makeRequest($endpoint, 'PATCH', $payload);
}
/**
* @throws \Exception
*/
public function createNote(
string $body,
string $ownerId,
int $timestamp,
string $objectId,
NoteObject $noteObject
): ?string {
try {
$noteInput = new SimplePublicObjectInput([
'properties' => [
'hs_note_body' => $body,
'hubspot_owner_id' => $ownerId,
'hs_timestamp' => $timestamp,
],
]);
// Create note
$note = $this->getNewInstance()->crm()->objects()->basicApi()->create('note', $noteInput);
$this->getNewInstance()->crm()->objects()->associationsApi()->create(
'note',
$note->getId(),
$this->getNoteObject($noteObject),
$objectId,
$this->getNoteAssociationType($noteObject),
);
return $note->getId();
} catch (\Exception $e) {
$this->log->error('[Hubspot] Failed to create note', [
'objectId' => $objectId,
'noteObject' => $noteObject->getObjectType(),
'reason' => $e->getMessage(),
]);
\Sentry::captureException($e);
}
return null;
}
public function updateEngagement(string $objectId, array $engagement, array $metadata): void
{
$this->getInstance()->engagements()->update($objectId, $engagement, $metadata);
}
public function getEngagementData(string $engagementId): array
{
$engagement = $this->getInstance()->engagements()->get($engagementId);
return $engagement->toArray();
}
public function createEngagement(array $engagement, array $associations, array $metadata): Response
{
return $this->getInstance()
->engagements()
->create($engagement, $associations, $metadata);
}
public function isUnauthorizedException(\Exception $e): bool
{
// Check for specific HubSpot API exception types first
if ($e instanceof BadRequest) {
// BadRequest can contain 401 status codes
return $e->getCode() === 401;
}
// Check for HTTP client exceptions with status codes
if ($e instanceof \GuzzleHttp\Exception\RequestException && $e->hasResponse()) {
$response = $e->getResponse();
if ($response !== null) {
return $response->getStatusCode() === 401;
}
}
// Check for Guzzle HTTP exceptions
if ($e instanceof \GuzzleHttp\Exception\ClientException) {
return $e->getCode() === 401;
}
// Fallback to string matching as last resort, but be more specific
$message = strtolower($e->getMessage());
return str_contains($message, '401 unauthorized') ||
str_contains($message, 'http 401') ||
str_contains($message, 'status code 401') ||
(preg_match('/\b401\b/', $message) && str_contains($message, 'unauthorized'));
}
/**
* Validates and refreshes the access token if needed before API requests.
* This ensures long-running processes don't fail due to token expiration.
*
* @throws SocialAccountTokenInvalidException
*/
public function ensureValidToken(): void
{
if ($this->oauthAccount === null) {
return;
}
$newToken = $this->tokenManager->ensureValidToken($this->oauthAccount);
if ($newToken !== null) {
$this->accessToken = $newToken;
}
}
public function getConfig()
{
return $this->config;
}
// returns only active (archived=false)
public function getOwners(): array
{
return $this->getNewInstance()->crm()->owners()->getAll();
}
/**
* @param bool $archived
*
* @return array<Owner>|[]
*/
public function getOwnersArchived(bool $archived = true): array
{
$endpoint = '/crm/v3/owners';
$queryParams = [
'archived' => $archived ? 'true' : 'false',
];
$queryString = http_build_query($queryParams);
$owners = [];
try {
$response = $this->makeRequest(endpoint: $endpoint, queryString: $queryString);
$responseData = $response?->toArray();
foreach ($responseData['results'] as $result) {
try {
$owners[] = Owner::create($result);
} catch (Throwable $e) {
$this->log->error('[HubSpot] Failed to process owner data', [
'result' => $result,
'error' => $e->getMessage(),
]);
continue;
}
}
} catch (Throwable $e) {
$this->log->error('HubSpot] Failed to fetch owners', [
'archived' => $archived,
'error' => $e->getMessage(),
]);
return [];
}
return $owners;
}
public function getMeeting(string $engagementId): ObjectWithAssociations
{
return $this->getNewInstance()->crm()->objects()->basicApi()
->getById('meeting', $engagementId, null, 'contact,company,deal');
}
public function deleteEngagement(string $engagementId): void
{
$this->getInstance()->engagements()->delete((int) $engagementId);
}
public function getAssociationsData(array $ids, string $fromObject, string $toObject): array
{
$associationData = [];
$idChunks = array_chunk($ids, self::ASSOCIATIONS_BATCH_SIZE_LIMIT);
foreach ($idChunks as $idChunk) {
try {
$batchInput = new \HubSpot\Client\Crm\Associations\Model\BatchInputPublicObjectId();
$batchInput->setInputs(array_map(function ($id) {
$publicObjectId = new \HubSpot\Client\Crm\Associations\Model\PublicObjectId();
$publicObjectId->setId($id);
return $publicObjectId;
}, $idChunk));
$associatedObjectsData = $this
->getNewInstance()
->crm()
->associations()
->batchApi()
->read($fromObject, $toObject, $batchInput);
if ($associatedObjectsData instanceof \HubSpot\Client\Crm\Associations\Model\BatchResponsePublicAssociationMulti) {
foreach ($associatedObjectsData->getResults() as $association) {
$from = $association->getFrom()->getId();
$toAssociations = $association->getTo();
if (! empty($toAssociations)) {
$associationData[$from] = array_map(function ($item) {
return $item->getId();
}, $toAssociations);
}
}
}
} catch (\Exception $e) {
$this->log->error('[Hubspot] Failed to fetch associations', [
'from_object' => $fromObject,
'to_object' => $toObject,
'reason' => $e->getMessage(),
]);
}
}
return $associationData;
}
/**
* @throws \Exception
*/
private function getNoteAssociationType(NoteObject $noteObject): string
{
return match($noteObject) {
NoteObject::Opportunity => 'note_to_deal',
NoteObject::Lead, NoteObject::Contact => 'note_to_contact', // or 'note_to_lead' if your portal supports it
NoteObject::Account => 'note_to_company',
NoteObject::Call, NoteObject::Event => throw new \Exception('Not supported'),
};
}
/**
* @throws \Exception
*/
private function getNoteObject(NoteObject $noteObject): string
{
return match($noteObject) {
NoteObject::Opportunity => 'deal',
NoteObject::Lead, NoteObject::Contact => 'contact',
NoteObject::Account => 'company',
NoteObject::Call, NoteObject::Event => throw new \Exception('Not supported'),
};
}
public function addAssociations(string $objectType, string $associationType, array $payload): Response
{
$endpoint = "/crm/v4/associations/$objectType/$associationType/batch/create";
return $this->makeRequest($endpoint, 'POST', $payload);
}
public function removeAssociations(string $objectType, string $associationType, array $payload): Response
{
$endpoint = "/crm/v4/associations/$objectType/$associationType/batch/archive";
return $this->makeRequest($endpoint, 'POST', $payload);
}
}
Project
Project
New File or Directory…
Expand Selected
Collapse All
Options
Hide...
|
NULL
|
NULL
|
NULL
|
NULL
|
|
2684
|
112
|
15
|
2026-05-07T11:35:31.277884+00:00
|
/Users/lukas/.screenpipe/data/data/2026-05-07/1778 /Users/lukas/.screenpipe/data/data/2026-05-07/1778153731277_m2.jpg...
|
PhpStorm
|
faVsco.js – Hubspot/Client.php
|
True
|
NULL
|
monitor_2
|
NULL
|
NULL
|
NULL
|
NULL
|
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
Execute
Explain Plan
Browse Query History
View Parameters
Open Query Execution Settings…
In-Editor Results
Tx: Auto
Cancel Running Statements
Playground
jiminny
Sync Changes
Hide This Notification
Code changed:
Hide
6
1
6
Previous Highlighted Error
Next Highlighted Error
# [PASSWORD_DOTS] HS [PASSWORD_DOTS]
select * from teams where id = 2; # 2
select * from features; # 2
select * from team_features where team_id = 2; # 2
select * from crm_configurations where id = 2; # 2
select * from users where team_id = 2; #
select * from playbooks where team_id = 2; # event 38
select * from playbook_categories where playbook_id = 38; #
SELECT * FROM activities WHERE crm_configuration_id = 2 and crm_provider_id is not null order by id desc;
[URL_WITH_CREDENTIALS] CrmFieldOption array{id:string, label:string, value?:string}
*/
class Client extends BaseClient implements HubspotClientInterface
{
public const string MIN_API_VERSION = '2';
public const string BASE_URL = '[URL_WITH_CREDENTIALS] T
* @param callable(): T $apiCall
* @return T
*
* @throws RateLimitException
*/
private function executeRequest(callable $apiCall)
{
if (! $this->rateLimiter->canMakeRequest($this->config)) {
$retryAfter = $this->rateLimiter->requestAvailableIn($this->config);
$this->log->warning('[Hubspot] Rate limit exceeded, deferring request', [
'team_id' => $this->config->team_id,
'config_id' => $this->config->getId(),
'retry_after' => $retryAfter,
]);
throw new RateLimitException(
'Hubspot rate limit reached for configuration ' . $this->config->getId(),
$retryAfter,
);
}
$this->rateLimiter->incrementRequestCount($this->config);
try {
return $apiCall();
} catch (Throwable $e) {
if ($this->isHubspotRateLimit($e)) {
$retryAfter = $this->parseRetryAfter($e);
$this->log->warning('[Hubspot] Received 429 from API', [
'team_id' => $this->config->team_id,
'config_id' => $this->config->getId(),
'retry_after' => $retryAfter,
'reason' => $e->getMessage(),
]);
throw new RateLimitException('Hubspot returned 429', $retryAfter, $e);
}
throw $e;
}
}
private function isHubspotRateLimit(Throwable $e): bool
{
return method_exists($e, 'getCode') && (int) $e->getCode() === 429;
}
private function parseRetryAfter(Throwable $e): int
{
if (method_exists($e, 'getResponseHeaders')) {
$headers = $e->getResponseHeaders() ?: [];
$value = $headers['Retry-After'] ?? $headers['retry-after'] ?? null;
if (is_array($value)) {
$value = $value[0] ?? null;
}
if (is_numeric($value)) {
return (int) $value;
}
}
return 10;
}
public function getMinimumApiVersion(): string
{
return self::MIN_API_VERSION;
}
public function getInstance(): Factory
{
return new Factory([
'key' => $this->accessToken,
'oauth2' => true,
'base_url' => $this->baseUrl,
]);
}
public function getNewInstance(): Discovery
{
return \HubSpot\Factory::createWithAccessToken($this->accessToken);
}
/**
* Secondly and daily limits for Hubspot API
*
* Product Tier: Free & Starter | Professional & Enterprise | API add-on (any tier)
* Burst: 100/10 seconds | 150/10 seconds | 200/10 seconds
* Daily: 250,000 | 500,000 | 1,000,000
*
* Official documentation states: The search endpoints are rate limited to five requests per second.
* Since with 5 RPS were still hitting secondly rate limits we lowered it to 4
*/
public function getPaginatedData(array $payload, string $type, int $offset = 0): array
{
$total = 0;
$lastId = null;
$rows = [];
foreach ($this->getPaginatedDataGenerator($payload, $type, $offset, $total, $lastId) as $row) {
$rows[] = $row;
}
return ['results' => $rows, 'total' => $total, 'last_record' => $lastId];
}
/**
* @throws HubspotException
* @throws SocialAccountTokenInvalidException
* @throws BadRequest
*/
public function getPaginatedDataGenerator(
array $payload,
string $type,
int $offset = 0,
int &$total = 0,
?string &$lastRecordId = null
): \Generator {
return $this->paginationService->getPaginatedDataGenerator(
$this,
$payload,
$type,
$offset,
$total,
$lastRecordId
);
}
/**
* @throws DealApiException
* @throws CrmException
*/
public function getOpportunityById(string $crmId, array $fields): array
{
try {
$deal = $this->executeRequest(fn () => $this->getNewInstance()->crm()->deals()->basicApi()->getById(
$crmId,
implode(',', $fields),
'companies,contacts'
));
} catch (DealApiException $e) {
$this->log->info('[Hubspot] Failed to fetch opportunity', [
'crm_id' => $crmId,
'reason' => $e->getMessage(),
]);
throw $e;
}
if (! $deal instanceof DealWithAssociations) {
throw new CrmException('Deal not found');
}
return [
'id' => $deal->getId(),
'properties' => $deal->getProperties(),
'associations' => $deal->getAssociations(),
];
}
/**
* Generic batch read method for HubSpot objects
*
* @param string $objectType The object type ('deals', 'companies', 'contacts')
* @param array<string> $crmIds Array of HubSpot object IDs (max 100)
* @param array<string> $fields Array of property names to fetch
*
* @return array<string, array> Array keyed by CRM ID with object data
*/
private function batchReadObjects(string $objectType, array $crmIds, array $fields): array
{
if (empty($crmIds)) {
return [];
}
$this->validateBatchSize($objectType, $crmIds);
$this->ensureValidToken();
try {
$batchConfig = $this->createBatchConfiguration($objectType);
$batchReadRequest = $this->prepareBatchRequest($batchConfig, $crmIds, $fields);
$response = $batchConfig['api']->read($batchReadRequest);
$this->validateApiResponse($response, $objectType);
$results = $this->processApiResults($response);
$this->logBatchResults($objectType, $crmIds, $results);
return $results;
} catch (\Throwable $e) {
$this->handleBatchError($e, $objectType, $crmIds);
}
}
private function validateBatchSize(string $objectType, array $crmIds): void
{
if (count($crmIds) > 100) {
throw new \InvalidArgumentException("Batch size cannot exceed 100 {$objectType}");
}
}
private function createBatchConfiguration(string $objectType): array
{
$configurations = [
'deals' => [
'batchReadRequest' => new \HubSpot\Client\Crm\Deals\Model\BatchReadInputSimplePublicObjectId(),
'inputClass' => \HubSpot\Client\Crm\Deals\Model\SimplePublicObjectId::class,
'api' => $this->getNewInstance()->crm()->deals()->batchApi(),
],
'companies' => [
'batchReadRequest' => new \HubSpot\Client\Crm\Companies\Model\BatchReadInputSimplePublicObjectId(),
'inputClass' => \HubSpot\Client\Crm\Companies\Model\SimplePublicObjectId::class,
'api' => $this->getNewInstance()->crm()->companies()->batchApi(),
],
'contacts' => [
'batchReadRequest' => new \HubSpot\Client\Crm\Contacts\Model\BatchReadInputSimplePublicObjectId(),
'inputClass' => \HubSpot\Client\Crm\Contacts\Model\SimplePublicObjectId::class,
'api' => $this->getNewInstance()->crm()->contacts()->batchApi(),
],
];
if (! isset($configurations[$objectType])) {
throw new \InvalidArgumentException("Unsupported object type: {$objectType}");
}
return $configurations[$objectType];
}
private function prepareBatchRequest(array $batchConfig, array $crmIds, array $fields): object
{
$batchReadRequest = $batchConfig['batchReadRequest'];
$inputClass = $batchConfig['inputClass'];
$inputs = array_map(function ($crmId) use ($inputClass) {
$input = new $inputClass();
$input->setId($crmId);
return $input;
}, $crmIds);
$batchReadRequest->setInputs($inputs);
$batchReadRequest->setProperties($fields);
return $batchReadRequest;
}
private function validateApiResponse($response, string $objectType): void
{
if (! $response) {
throw new CrmException("HubSpot API returned null response for {$objectType} batch read");
}
}
private function processApiResults($response): array
{
$results = [];
$responseResults = $response->getResults();
if ($responseResults) {
foreach ($responseResults as $object) {
if ($object && $object->getId()) {
$results[$object->getId()] = [
'id' => $object->getId(),
'properties' => $object->getProperties() ?: [],
];
}
}
}
return $results;
}
private function logBatchResults(string $objectType, array $crmIds, array $results): void
{
$this->log->info("[HubSpot] Batch fetched {$objectType}", [
'requested_count' => count($crmIds),
'returned_count' => count($results),
'crm_ids' => $crmIds,
]);
}
private function handleBatchError(\Throwable $e, string $objectType, array $crmIds): void
{
$errorMessage = $e->getMessage() ?: 'Unknown error';
$errorTrace = $e->getTraceAsString() ?: 'No trace available';
$this->log->error("[HubSpot] Failed to batch fetch {$objectType}", [
'crm_ids' => $crmIds,
'error' => $errorMessage,
'trace' => $errorTrace,
]);
throw new CrmException("Failed to batch fetch {$objectType}: " . $errorMessage);
}
/**
* Batch read multiple opportunities by their CRM IDs
*
* @param array<string> $crmIds Array of HubSpot deal IDs (max 100)
* @param array<string> $fields Array of property names to fetch
*
* @return array<string, array> Array keyed by CRM ID with opportunity data
*/
public function getOpportunitiesByIds(array $crmIds, array $fields): array
{
return $this->batchReadObjects('deals', $crmIds, $fields);
}
/**
* Batch read multiple companies by their CRM IDs
*
* @param array<string> $crmIds Array of HubSpot company IDs (max 100)
* @param array<string> $fields Array of property names to fetch
*
* @return array<string, array> Array keyed by CRM ID with company data
*/
public function getCompaniesByIds(array $crmIds, array $fields): array
{
return $this->batchReadObjects('companies', $crmIds, $fields);
}
/**
* Batch read multiple contacts by their CRM IDs
*
* @param array<string> $crmIds Array of HubSpot contact IDs (max 100)
* @param array<string> $fields Array of property names to fetch
*
* @return array<string, array> Array keyed by CRM ID with contact data
*/
public function getContactsByIds(array $crmIds, array $fields): array
{
return $this->batchReadObjects('contacts', $crmIds, $fields);
}
/**
* @throws CompanyApiException
* @throws CrmException
*/
public function getAccountById(string $crmId, array $fields): array
{
try {
$company = $this->getNewInstance()->crm()->companies()->basicApi()->getById(
$crmId,
implode(',', $fields),
);
} catch (CompanyApiException $e) {
$this->log->info('[Hubspot] Failed to fetch account', [
'crm_id' => $crmId,
'reason' => $e->getMessage(),
]);
throw $e;
}
if (! $company instanceof CompaniesWithAssociations) {
throw new CrmException('Account not found');
}
return [
'id' => $company->getId(),
'properties' => $company->getProperties(),
];
}
/**
* @throws ContactApiException
* @throws CrmException
*/
public function getContactById(string $crmId, array $fields): array
{
try {
$contact = $this->getNewInstance()->crm()->contacts()->basicApi()->getById(
$crmId,
implode(',', $fields)
);
} catch (ContactApiException $e) {
$this->log->info('[Hubspot] Failed to fetch contact', [
'crm_id' => $crmId,
'reason' => $e->getMessage(),
]);
throw $e;
}
if (! $contact instanceof ContactsWithAssociations) {
throw new CrmException('Contact not found');
}
return [
'id' => $contact->getId(),
'properties' => $contact->getProperties(),
];
}
/**
* This is email search request that Hubspot offers as GET (more generous quota)
*/
public function getContactByEmail(string $email, array $fields = []): array
{
try {
$contact = $this->getNewInstance()->crm()->contacts()->basicApi()->getById(
$email,
implode(',', $fields),
null,
false,
'email'
);
return [
'id' => $contact->getId(),
'properties' => $contact->getProperties(),
];
} catch (ContactApiException $e) {
$this->log->info('[Hubspot] Failed to fetch contact', [
'email' => $email,
'reason' => $e->getMessage(),
]);
return [];
}
}
/**
* @throws CrmException
*/
public function fetchProperty(string $objectType, string $propertyId): Property
{
$result = $this->getNewInstance()->crm()->properties()->coreApi()->getByName($objectType, $propertyId);
if (! $result instanceof Property) {
$this->log->error('[Hubspot] Failed to fetch property', [
'object_type' => $objectType,
'property_id' => $propertyId,
'reason' => $result->getMessage(),
]);
throw new CrmException('Failed to fetch property');
}
return $result;
}
/**
* @return array<CrmFieldOption>
*/
public function fetchPropertyOptions(string $objectType, string $propertyId): array
{
/** @var array<CrmFieldOption> */
return $this->fetchProperty($objectType, $propertyId)->getOptions();
}
/**
* @return array<array{id:string, label:string, deleted:bool}>
*/
public function fetchCallDispositions(): array
{
/** @var Response $response */
$response = $this->getInstance()->engagements()->getCallDispositions();
/**
* @var array<array{
* id:string,
* label:string,
* deleted: bool
* }>
*/
return $response->toArray();
}
/**
* @return array<CrmFieldOption>
*/
public function fetchOpportunityPipelineStages(): array
{
$stages = [];
$apiResponse = $this->getNewInstance()->crm()->pipelines()->pipelinesApi()->getAll('deals');
if ($apiResponse instanceof Error) {
$this->log->error('[Hubspot] Failed to fetch opportunity pipelines', [
'reason' => $apiResponse->getMessage(),
]);
return [];
}
foreach ($apiResponse->getResults() as $pipeline) {
$pipelineStages = array_map(
static function (PipelineStage $stage) {
return [
'id' => $stage->getId(),
'label' => $stage->getLabel(),
];
},
$pipeline->getStages()
);
$stages = array_merge($stages, $pipelineStages);
}
return $stages;
}
public function fetchOpportunityPipelines(): array
{
$pipelines = [];
try {
$apiResponse = $this->makeRequest('/crm/v3/pipelines/deals');
} catch (\Exception $e) {
$this->log->info('[Hubspot] Failed to fetch opportunity pipelines', [
'reason' => $e->getMessage(),
]);
return [];
}
$response = $apiResponse->toArray();
foreach ($response['results'] as $pipeline) {
$pipelines[] = [
'id' => $pipeline['id'],
'label' => $pipeline['label'],
];
}
return $pipelines;
}
/**
* @return array<CrmFieldOption>
*/
public function fetchMeetingOutcomeFieldOptions(Field $field): array
{
return $field->getCrmProviderId() === 'meetingOutcome'
? $this->fetchMeetingOutcomeTypes()
: $this->fetchCallActivityTypes();
}
public function fetchMeetingOutcomeTypes(): array
{
return $this->extractMeetingTypeOptions(
'[URL_WITH_CREDENTIALS] Response $response */
$response = $this->getInstance()
->getClient()
->request('GET', $endpoint);
/**
* @var array<array{
* value: string,
* label: string,
* displayOrder: int
* }> $optionData
*/
$optionData = $response->toArray()['options'] ?? [];
$options = [];
foreach ($optionData as $item) {
$options[] = [
'id' => $item['value'],
'value' => $item['value'],
'label' => $item['label'],
'display_order' => $item['displayOrder'],
];
}
return $options;
}
/**
* @return array<CrmFieldOption>
*/
public function fetchDispositionFieldOptions(): array
{
$options = [];
$dispositions = $this->fetchCallDispositions();
foreach ($dispositions as $disposition) {
if ($disposition['deleted'] !== false) {
continue;
}
$option['value'] = $disposition['id'];
$option['id'] = $disposition['id'];
$option['label'] = $disposition['label'];
$options[] = $option;
}
return $options;
}
/**
* @return array<CrmFieldOption>
*/
public function fetchOpportunityFieldOptions(Field $field): array
{
if ($field->isStageField()) {
return $this->fetchOpportunityPipelineStages();
}
if ($field->isPipelineField()) {
return $this->fetchOpportunityPipelines();
}
return $this->fetchPropertyOptions('deals', $field->getCrmProviderId());
}
/**
* @throws BadRequest
* @throws HubspotException
*/
public function makeRequest(string $endpoint, $method = 'GET', $payload = [], ?string $queryString = null)
{
$endpoint = self::BASE_URL . $endpoint;
if ($method === 'GET') {
$response = $this->getInstance()->getClient()?->request(
method: $method,
endpoint: $endpoint,
query_string: $queryString
);
} else {
$response = $this->getInstance()->getClient()->request($method, $endpoint, [
'json' => ($payload),
]);
}
$max = $response->getHeaderLine('X-HubSpot-RateLimit-Max'); // "110"
$remaining = $response->getHeaderLine('X-HubSpot-RateLimit-Remaining'); // "109"
$interval = $response->getHeaderLine('X-HubSpot-RateLimit-Interval-Milliseconds'); // "10000"
$body = json_decode((string) $response->getBody(), true);
\Illuminate\Support\Facades\Log::channel('custom_channel')->info('$max ' . PHP_EOL . print_r($max, true));
\Illuminate\Support\Facades\Log::channel('custom_channel')->info('$remaining ' . PHP_EOL . print_r($remaining, true));
\Illuminate\Support\Facades\Log::channel('custom_channel')->info('$interval ' . PHP_EOL . print_r($interval, true));
return $response;
}
/**
* @throws BadRequest
* @throws HubspotException
*/
public function createMeeting(array $payload): Response
{
$endpoint = '/crm/v3/objects/meetings';
return $this->makeRequest($endpoint, 'POST', $payload);
}
/**
* @throws BadRequest
* @throws HubspotException
*/
public function updateMeeting(string $meetingId, array $payload): Response
{
$endpoint = '/crm/v3/objects/meetings/' . $meetingId;
return $this->makeRequest($endpoint, 'PATCH', $payload);
}
/**
* @throws \Exception
*/
public function createNote(
string $body,
string $ownerId,
int $timestamp,
string $objectId,
NoteObject $noteObject
): ?string {
try {
$noteInput = new SimplePublicObjectInput([
'properties' => [
'hs_note_body' => $body,
'hubspot_owner_id' => $ownerId,
'hs_timestamp' => $timestamp,
],
]);
// Create note
$note = $this->getNewInstance()->crm()->objects()->basicApi()->create('note', $noteInput);
$this->getNewInstance()->crm()->objects()->associationsApi()->create(
'note',
$note->getId(),
$this->getNoteObject($noteObject),
$objectId,
$this->getNoteAssociationType($noteObject),
);
return $note->getId();
} catch (\Exception $e) {
$this->log->error('[Hubspot] Failed to create note', [
'objectId' => $objectId,
'noteObject' => $noteObject->getObjectType(),
'reason' => $e->getMessage(),
]);
\Sentry::captureException($e);
}
return null;
}
public function updateEngagement(string $objectId, array $engagement, array $metadata): void
{
$this->getInstance()->engagements()->update($objectId, $engagement, $metadata);
}
public function getEngagementData(string $engagementId): array
{
$engagement = $this->getInstance()->engagements()->get($engagementId);
return $engagement->toArray();
}
public function createEngagement(array $engagement, array $associations, array $metadata): Response
{
return $this->getInstance()
->engagements()
->create($engagement, $associations, $metadata);
}
public function isUnauthorizedException(\Exception $e): bool
{
// Check for specific HubSpot API exception types first
if ($e instanceof BadRequest) {
// BadRequest can contain 401 status codes
return $e->getCode() === 401;
}
// Check for HTTP client exceptions with status codes
if ($e instanceof \GuzzleHttp\Exception\RequestException && $e->hasResponse()) {
$response = $e->getResponse();
if ($response !== null) {
return $response->getStatusCode() === 401;
}
}
// Check for Guzzle HTTP exceptions
if ($e instanceof \GuzzleHttp\Exception\ClientException) {
return $e->getCode() === 401;
}
// Fallback to string matching as last resort, but be more specific
$message = strtolower($e->getMessage());
return str_contains($message, '401 unauthorized') ||
str_contains($message, 'http 401') ||
str_contains($message, 'status code 401') ||
(preg_match('/\b401\b/', $message) && str_contains($message, 'unauthorized'));
}
/**
* Validates and refreshes the access token if needed before API requests.
* This ensures long-running processes don't fail due to token expiration.
*
* @throws SocialAccountTokenInvalidException
*/
public function ensureValidToken(): void
{
if ($this->oauthAccount === null) {
return;
}
$newToken = $this->tokenManager->ensureValidToken($this->oauthAccount);
if ($newToken !== null) {
$this->accessToken = $newToken;
}
}
public function getConfig()
{
return $this->config;
}
// returns only active (archived=false)
public function getOwners(): array
{
return $this->getNewInstance()->crm()->owners()->getAll();
}
/**
* @param bool $archived
*
* @return array<Owner>|[]
*/
public function getOwnersArchived(bool $archived = true): array
{
$endpoint = '/crm/v3/owners';
$queryParams = [
'archived' => $archived ? 'true' : 'false',
];
$queryString = http_build_query($queryParams);
$owners = [];
try {
$response = $this->makeRequest(endpoint: $endpoint, queryString: $queryString);
$responseData = $response?->toArray();
foreach ($responseData['results'] as $result) {
try {
$owners[] = Owner::create($result);
} catch (Throwable $e) {
$this->log->error('[HubSpot] Failed to process owner data', [
'result' => $result,
'error' => $e->getMessage(),
]);
continue;
}
}
} catch (Throwable $e) {
$this->log->error('HubSpot] Failed to fetch owners', [
'archived' => $archived,
'error' => $e->getMessage(),
]);
return [];
}
return $owners;
}
public function getMeeting(string $engagementId): ObjectWithAssociations
{
return $this->getNewInstance()->crm()->objects()->basicApi()
->getById('meeting', $engagementId, null, 'contact,company,deal');
}
public function deleteEngagement(string $engagementId): void
{
$this->getInstance()->engagements()->delete((int) $engagementId);
}
public function getAssociationsData(array $ids, string $fromObject, string $toObject): array
{
$associationData = [];
$idChunks = array_chunk($ids, self::ASSOCIATIONS_BATCH_SIZE_LIMIT);
foreach ($idChunks as $idChunk) {
try {
$batchInput = new \HubSpot\Client\Crm\Associations\Model\BatchInputPublicObjectId();
$batchInput->setInputs(array_map(function ($id) {
$publicObjectId = new \HubSpot\Client\Crm\Associations\Model\PublicObjectId();
$publicObjectId->setId($id);
return $publicObjectId;
}, $idChunk));
$associatedObjectsData = $this
->getNewInstance()
->crm()
->associations()
->batchApi()
->read($fromObject, $toObject, $batchInput);
if ($associatedObjectsData instanceof \HubSpot\Client\Crm\Associations\Model\BatchResponsePublicAssociationMulti) {
foreach ($associatedObjectsData->getResults() as $association) {
$from = $association->getFrom()->getId();
$toAssociations = $association->getTo();
if (! empty($toAssociations)) {
$associationData[$from] = array_map(function ($item) {
return $item->getId();
}, $toAssociations);
}
}
}
} catch (\Exception $e) {
$this->log->error('[Hubspot] Failed to fetch associations', [
'from_object' => $fromObject,
'to_object' => $toObject,
'reason' => $e->getMessage(),
]);
}
}
return $associationData;
}
/**
* @throws \Exception
*/
private function getNoteAssociationType(NoteObject $noteObject): string
{
return match($noteObject) {
NoteObject::Opportunity => 'note_to_deal',
NoteObject::Lead, NoteObject::Contact => 'note_to_contact', // or 'note_to_lead' if your portal supports it
NoteObject::Account => 'note_to_company',
NoteObject::Call, NoteObject::Event => throw new \Exception('Not supported'),
};
}
/**
* @throws \Exception
*/
private function getNoteObject(NoteObject $noteObject): string
{
return match($noteObject) {
NoteObject::Opportunity => 'deal',
NoteObject::Lead, NoteObject::Contact => 'contact',
NoteObject::Account => 'company',
NoteObject::Call, NoteObject::Event => throw new \Exception('Not supported'),
};
}
public function addAssociations(string $objectType, string $associationType, array $payload): Response
{
$endpoint = "/crm/v4/associations/$objectType/$associationType/batch/create";
return $this->makeRequest($endpoint, 'POST', $payload);
}
public function removeAssociations(string $objectType, string $associationType, array $payload): Response
{
$endpoint = "/crm/v4/associations/$objectType/$associationType/batch/archive";
return $this->makeRequest($endpoint, 'POST', $payload);
}
}
Project
Project
New File or Directory…
Expand Selected
Collapse All
Options
Hide...
|
[{"role":"AXButton","text" [{"role":"AXButton","text":"Project: faVsco.js, menu","depth":5,"bounds":{"left":0.025930852,"top":0.019952115,"width":0.03856383,"height":0.025538707},"on_screen":true,"help_text":"~/jiminny/app","role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"master, menu","depth":5,"bounds":{"left":0.064494684,"top":0.019952115,"width":0.034242023,"height":0.025538707},"on_screen":true,"help_text":"Git Branch: master","role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Start Listening for PHP Debug Connections","depth":5,"bounds":{"left":0.8081782,"top":0.019952115,"width":0.011303191,"height":0.025538707},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"AskJiminnyReportActivityServiceTest","depth":6,"bounds":{"left":0.8234708,"top":0.019952115,"width":0.09208777,"height":0.025538707},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Run 'AskJiminnyReportActivityServiceTest'","depth":6,"bounds":{"left":0.9155585,"top":0.019952115,"width":0.011303191,"height":0.025538707},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Debug 'AskJiminnyReportActivityServiceTest'","depth":6,"bounds":{"left":0.9268617,"top":0.019952115,"width":0.011303191,"height":0.025538707},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"More Actions","depth":6,"bounds":{"left":0.9381649,"top":0.019952115,"width":0.011303191,"height":0.025538707},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"JetBrains AI","depth":5,"bounds":{"left":0.96609044,"top":0.019952115,"width":0.011303191,"height":0.025538707},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Search Everywhere","depth":5,"bounds":{"left":0.9773936,"top":0.019952115,"width":0.011303191,"height":0.025538707},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"IDE and Project Settings","depth":5,"bounds":{"left":0.9886968,"top":0.019952115,"width":0.011303186,"height":0.025538707},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Execute","depth":4,"bounds":{"left":0.38297874,"top":0.09896249,"width":0.008643617,"height":0.01915403},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Explain Plan","depth":4,"bounds":{"left":0.39162233,"top":0.09896249,"width":0.008643617,"height":0.01915403},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Browse Query History","depth":4,"bounds":{"left":0.40259308,"top":0.09896249,"width":0.008643617,"height":0.01915403},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"View Parameters","depth":4,"bounds":{"left":0.4112367,"top":0.09896249,"width":0.008643617,"height":0.01915403},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Open Query Execution Settings…","depth":4,"bounds":{"left":0.41988033,"top":0.09896249,"width":0.008643617,"height":0.01915403},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"In-Editor Results","depth":4,"bounds":{"left":0.43085107,"top":0.09896249,"width":0.008643617,"height":0.01915403},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Tx: Auto","depth":4,"bounds":{"left":0.4418218,"top":0.09896249,"width":0.024268618,"height":0.01915403},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Cancel Running Statements","depth":4,"bounds":{"left":0.46841756,"top":0.09896249,"width":0.008643617,"height":0.01915403},"on_screen":true,"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Playground","depth":4,"bounds":{"left":0.4793883,"top":0.09896249,"width":0.029587766,"height":0.01915403},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"jiminny","depth":4,"bounds":{"left":0.65359044,"top":0.09896249,"width":0.02825798,"height":0.01915403},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Sync Changes","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.008643617,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Hide This Notification","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.008643617,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Code changed:","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.042220745,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Hide","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.008643617,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"6","depth":4,"bounds":{"left":0.6399601,"top":0.123703115,"width":0.007978723,"height":0.015163607},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"1","depth":4,"bounds":{"left":0.6499335,"top":0.123703115,"width":0.00731383,"height":0.015163607},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"6","depth":4,"bounds":{"left":0.65924203,"top":0.123703115,"width":0.007978723,"height":0.015163607},"on_screen":true,"role_description":"text"},{"role":"AXButton","text":"Previous Highlighted Error","depth":4,"bounds":{"left":0.66888297,"top":0.12210695,"width":0.00731383,"height":0.018355945},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Next Highlighted Error","depth":4,"bounds":{"left":0.6761968,"top":0.12210695,"width":0.006981383,"height":0.018355945},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXTextArea","text":"# **************************** HS **************************************\n\nselect * from teams where id = 2; # 2\nselect * from features; # 2\nselect * from team_features where team_id = 2; # 2\nselect * from crm_configurations where id = 2; # 2\nselect * from users where team_id = 2; #\nselect * from playbooks where team_id = 2; # event 38\nselect * from playbook_categories where playbook_id = 38; #\n\nSELECT * FROM activities WHERE crm_configuration_id = 2 and crm_provider_id is not null order by id desc;\nhttps://app.hubspot.com/contacts/4392066/deal/16964514951/?engagement=96069102624\n https://app.staging.jiminny.com/playback/d5df34dc-bd66-4ff5-a7b3-8d3be30322a0\n\nSELECT * FROM activities WHERE uuid_to_bin('04fdcd0d-818f-4c53-92dc-6f18bc753ffd') = uuid;\n# 609126 softphone tr. 11241\n\nSELECT * FROM activities WHERE uuid_to_bin('6521bfcd-5a30-46e5-9f74-5440fd48befd') = uuid;\n# 608874 conference tr. 11226 crmId: 103422236596\n\nselect * from ai_prompts where transcription_id IN (11241, 11226);\nselect * from activity_summary_logs where activity_id = 608874;\n\nselect * from sidekick_settings;\nselect * from default_activity_types;\n\nselect * from crm_field_data where activity_id = 1223;\n\nselect * from crm_layouts where crm_configuration_id = 2;\nSELECT * FROM crm_layout_entities WHERE crm_layout_id IN (554);\nselect * from crm_fields where crm_configuration_id = 11 and object_type = 'event';\nSELECT * FROM crm_field_values WHERE crm_field_id IN (1455,1450);\n\nSELECT * FROM crm_field_data WHERE crm_layout_entity_id = 971;\nSELECT * FROM crm_field_data WHERE crm_layout_entity_id IN (6494,6495,6496,6497,6498,6499);\n\nSELECT\n CONCAT(u.id, CASE WHEN u.id = t.owner_id THEN ' (owner)' ELSE '' END) AS user_id,\n u.email,\n sa.*,\n t.owner_id FROM social_accounts sa\nJOIN users u\n on u.id = sa.sociable_id\nJOIN teams t on t.id = u.team_id\nWHERE u.team_id = 2 and sa.provider = 'hubspot';\n\nselect * from social_accounts where id = 1499;\n\nselect * from opportunities where team_id = 2\nand crm_provider_id IN ('51317301383');\n\nselect * from contacts where id = 85;\n\nselect * from opportunities where team_id = 2 order by id desc;\nselect * from opportunities where team_id = 2 and crm_provider_id = '51317301383'; # 5112\nselect * from opportunities where team_id = 2 and crm_provider_id = '55976759904'; # 5112\nselect * from opportunity_contacts where opportunity_id = 5117;\nselect * from crm_field_data where object_id = 1365;\nSELECT * FROM crm_fields WHERE id IN (1405, 1407, 1972, 2128);\n\nselect * from features;\nselect * from team_features where team_id IN (1);\nselect * from team_features where feature_id IN (36);\n\nSHOW CREATE TABLE opportunity_contacts;\nSELECT * FROM opportunity_contacts WHERE crm_provider_id = '111751';\n\n# $slug = 'HUBSPOT_WEBHOOK_SYNC';\n# $team = Jiminny\\Models\\Team::find(2);\n# $feature = Feature::query()->where('slug', $slug)->first();\n# TeamFeature::query()->create(['feature_id' => $feature->getId(),'team_id' => $team->getId()]);\n\n# hubspot_webhook_metrics\n\nselect * from opportunities where team_id = 2 and crm_provider_id IN ('374720564','14527423589','49908861993','50435771779'); # 1365\nSELECT * FROM opportunity_contacts WHERE opportunity_id = '414';\nSELECT * FROM opportunity_contacts WHERE crm_provider_id = '131501';\nselect * from contacts where id in (414, 464);\n\nselect * from activities where crm_configuration_id = 2;\n\nselect settings from crm_configurations where id = 11;\n\nselect * from teams; # 1, 2\nselect * from users;\nselect * from crm_configurations where id = 39;\nselect * from team_features where team_id = 2;\nselect * from features;\n# SELECT * FROM opportunities WHERE crm_configuration_id = 2\n# order by id desc;\n# and crm_provider_id = '49908861993';\n\n\nselect * from activity_providers where id IN (443, 202, 203, 227);\n\nselect * from activity_imports where id = 795889;\n\nselect c.id, c.provider, c.settings, t.* from teams t join crm_configurations c on t.id = c.team_id\nwhere c.provider = 'hubspot';\n\nselect * from crm_configurations crm JOIN teams t on crm.team_id = t.id\nwhere provider = 'hubspot';\nSELECT * FROM teams WHERE id = 31;\nSELECT * FROM users WHERE id = 257;\nSELECT * FROM opportunities WHERE team_id = 2;\n\nselect * from opportunity_contacts where opportunity_id = 5124;\nselect * from contacts where id IN (3850,3853,3851,4073,4140,4155,4480,4530,4623,5986,513,687,1806,1523,3613)\n\nselect * from activities where crm_configuration_id = 13;\n\nSELECT * FROM activities WHERE uuid_to_bin('826619ce-ec8e-4e59-8467-a01f5f6ad71e') = uuid; # 418141\n\n\nselect id, team_id, crm_provider_id from crm_configurations where provider = 'hubspot' and crm_provider_id IS NOT NULL;\nSELECT * FROM accounts WHERE team_id = 2 and crm_provider_id = '1212213464' order by id desc;\nSELECT * FROM contacts WHERE team_id = 2 and account_id = 5189 order by id desc;\nSELECT * FROM contacts WHERE team_id = 2 order by id desc;\nselect * from opportunity_contacts where contact_id = 6223;\nSELECT * FROM opportunities WHERE team_id = 2 and account_id = 5189 order by id desc;\n\nselect * from crm_profiles where crm_configuration_id = 2;\n\nselect * from activities where account_id = 46;","depth":4,"on_screen":true,"value":"# **************************** HS **************************************\n\nselect * from teams where id = 2; # 2\nselect * from features; # 2\nselect * from team_features where team_id = 2; # 2\nselect * from crm_configurations where id = 2; # 2\nselect * from users where team_id = 2; #\nselect * from playbooks where team_id = 2; # event 38\nselect * from playbook_categories where playbook_id = 38; #\n\nSELECT * FROM activities WHERE crm_configuration_id = 2 and crm_provider_id is not null order by id desc;\nhttps://app.hubspot.com/contacts/4392066/deal/16964514951/?engagement=96069102624\n https://app.staging.jiminny.com/playback/d5df34dc-bd66-4ff5-a7b3-8d3be30322a0\n\nSELECT * FROM activities WHERE uuid_to_bin('04fdcd0d-818f-4c53-92dc-6f18bc753ffd') = uuid;\n# 609126 softphone tr. 11241\n\nSELECT * FROM activities WHERE uuid_to_bin('6521bfcd-5a30-46e5-9f74-5440fd48befd') = uuid;\n# 608874 conference tr. 11226 crmId: 103422236596\n\nselect * from ai_prompts where transcription_id IN (11241, 11226);\nselect * from activity_summary_logs where activity_id = 608874;\n\nselect * from sidekick_settings;\nselect * from default_activity_types;\n\nselect * from crm_field_data where activity_id = 1223;\n\nselect * from crm_layouts where crm_configuration_id = 2;\nSELECT * FROM crm_layout_entities WHERE crm_layout_id IN (554);\nselect * from crm_fields where crm_configuration_id = 11 and object_type = 'event';\nSELECT * FROM crm_field_values WHERE crm_field_id IN (1455,1450);\n\nSELECT * FROM crm_field_data WHERE crm_layout_entity_id = 971;\nSELECT * FROM crm_field_data WHERE crm_layout_entity_id IN (6494,6495,6496,6497,6498,6499);\n\nSELECT\n CONCAT(u.id, CASE WHEN u.id = t.owner_id THEN ' (owner)' ELSE '' END) AS user_id,\n u.email,\n sa.*,\n t.owner_id FROM social_accounts sa\nJOIN users u\n on u.id = sa.sociable_id\nJOIN teams t on t.id = u.team_id\nWHERE u.team_id = 2 and sa.provider = 'hubspot';\n\nselect * from social_accounts where id = 1499;\n\nselect * from opportunities where team_id = 2\nand crm_provider_id IN ('51317301383');\n\nselect * from contacts where id = 85;\n\nselect * from opportunities where team_id = 2 order by id desc;\nselect * from opportunities where team_id = 2 and crm_provider_id = '51317301383'; # 5112\nselect * from opportunities where team_id = 2 and crm_provider_id = '55976759904'; # 5112\nselect * from opportunity_contacts where opportunity_id = 5117;\nselect * from crm_field_data where object_id = 1365;\nSELECT * FROM crm_fields WHERE id IN (1405, 1407, 1972, 2128);\n\nselect * from features;\nselect * from team_features where team_id IN (1);\nselect * from team_features where feature_id IN (36);\n\nSHOW CREATE TABLE opportunity_contacts;\nSELECT * FROM opportunity_contacts WHERE crm_provider_id = '111751';\n\n# $slug = 'HUBSPOT_WEBHOOK_SYNC';\n# $team = Jiminny\\Models\\Team::find(2);\n# $feature = Feature::query()->where('slug', $slug)->first();\n# TeamFeature::query()->create(['feature_id' => $feature->getId(),'team_id' => $team->getId()]);\n\n# hubspot_webhook_metrics\n\nselect * from opportunities where team_id = 2 and crm_provider_id IN ('374720564','14527423589','49908861993','50435771779'); # 1365\nSELECT * FROM opportunity_contacts WHERE opportunity_id = '414';\nSELECT * FROM opportunity_contacts WHERE crm_provider_id = '131501';\nselect * from contacts where id in (414, 464);\n\nselect * from activities where crm_configuration_id = 2;\n\nselect settings from crm_configurations where id = 11;\n\nselect * from teams; # 1, 2\nselect * from users;\nselect * from crm_configurations where id = 39;\nselect * from team_features where team_id = 2;\nselect * from features;\n# SELECT * FROM opportunities WHERE crm_configuration_id = 2\n# order by id desc;\n# and crm_provider_id = '49908861993';\n\n\nselect * from activity_providers where id IN (443, 202, 203, 227);\n\nselect * from activity_imports where id = 795889;\n\nselect c.id, c.provider, c.settings, t.* from teams t join crm_configurations c on t.id = c.team_id\nwhere c.provider = 'hubspot';\n\nselect * from crm_configurations crm JOIN teams t on crm.team_id = t.id\nwhere provider = 'hubspot';\nSELECT * FROM teams WHERE id = 31;\nSELECT * FROM users WHERE id = 257;\nSELECT * FROM opportunities WHERE team_id = 2;\n\nselect * from opportunity_contacts where opportunity_id = 5124;\nselect * from contacts where id IN (3850,3853,3851,4073,4140,4155,4480,4530,4623,5986,513,687,1806,1523,3613)\n\nselect * from activities where crm_configuration_id = 13;\n\nSELECT * FROM activities WHERE uuid_to_bin('826619ce-ec8e-4e59-8467-a01f5f6ad71e') = uuid; # 418141\n\n\nselect id, team_id, crm_provider_id from crm_configurations where provider = 'hubspot' and crm_provider_id IS NOT NULL;\nSELECT * FROM accounts WHERE team_id = 2 and crm_provider_id = '1212213464' order by id desc;\nSELECT * FROM contacts WHERE team_id = 2 and account_id = 5189 order by id desc;\nSELECT * FROM contacts WHERE team_id = 2 order by id desc;\nselect * from opportunity_contacts where contact_id = 6223;\nSELECT * FROM opportunities WHERE team_id = 2 and account_id = 5189 order by id desc;\n\nselect * from crm_profiles where crm_configuration_id = 2;\n\nselect * from activities where account_id = 46;","role_description":"text entry area","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Sync Changes","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.008643617,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Hide This Notification","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.008643617,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Code changed:","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.042220745,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Hide","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.008643617,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"2","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.007978723,"height":0.0},"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"60","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.010305851,"height":0.0},"on_screen":false,"role_description":"text"},{"role":"AXButton","text":"Previous Highlighted Error","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.00731383,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Next Highlighted Error","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.006981383,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXTextArea","text":"<?php\n\ndeclare(strict_types=1);\n\nnamespace Jiminny\\Services\\Crm\\Hubspot;\n\nuse HubSpot\\Client\\Crm\\Deals\\ApiException as DealApiException;\nuse HubSpot\\Client\\Crm\\Contacts\\ApiException as ContactApiException;\nuse HubSpot\\Client\\Crm\\Companies\\ApiException as CompanyApiException;\nuse HubSpot\\Client\\Crm\\Contacts\\Model\\SimplePublicObjectWithAssociations as ContactsWithAssociations;\nuse HubSpot\\Client\\Crm\\Companies\\Model\\SimplePublicObjectWithAssociations as CompaniesWithAssociations;\nuse HubSpot\\Client\\Crm\\Deals\\Model\\SimplePublicObjectWithAssociations as DealWithAssociations;\nuse HubSpot\\Client\\Crm\\Objects\\Model\\SimplePublicObjectInput;\nuse HubSpot\\Client\\Crm\\Objects\\Model\\SimplePublicObjectWithAssociations as ObjectWithAssociations;\nuse HubSpot\\Client\\Crm\\Pipelines\\Model\\Error;\nuse HubSpot\\Client\\Crm\\Pipelines\\Model\\PipelineStage;\nuse HubSpot\\Client\\Crm\\Properties\\Model\\Property;\nuse HubSpot\\Discovery\\Discovery;\nuse Jiminny\\Component\\Utility\\Service\\ProviderRateLimiter;\nuse Jiminny\\Exceptions\\CrmException;\nuse Jiminny\\Exceptions\\RateLimitException;\nuse Jiminny\\Exceptions\\SocialAccountTokenInvalidException;\nuse Jiminny\\Jobs\\Crm\\NoteObject;\nuse Jiminny\\Models\\Crm\\Field;\nuse Jiminny\\Services\\Crm\\BaseClient;\nuse Jiminny\\Services\\Crm\\Hubspot\\DTO\\Response\\Owner;\nuse Jiminny\\Services\\SocialAccountService;\nuse SevenShores\\Hubspot\\Exceptions\\BadRequest;\nuse SevenShores\\Hubspot\\Exceptions\\HubspotException;\nuse SevenShores\\Hubspot\\Factory;\nuse SevenShores\\Hubspot\\Http\\Response;\nuse Jiminny\\Services\\Crm\\Hubspot\\Pagination\\HubspotPaginationService;\nuse Throwable;\n\n/**\n * @phpstan-type CrmFieldOption array{id:string, label:string, value?:string}\n */\nclass Client extends BaseClient implements HubspotClientInterface\n{\n public const string MIN_API_VERSION = '2';\n\n public const string BASE_URL = 'https://api.hubapi.com';\n\n public const int ASSOCIATIONS_BATCH_SIZE_LIMIT = 1000;\n\n private HubspotPaginationService $paginationService;\n private HubspotTokenManager $tokenManager;\n private ProviderRateLimiter $rateLimiter;\n\n public function __construct(\n SocialAccountService $socialAccountService,\n HubspotPaginationService $paginationService,\n HubspotTokenManager $tokenManager,\n ProviderRateLimiter $rateLimiter,\n ) {\n parent::__construct($socialAccountService);\n $this->paginationService = $paginationService;\n $this->tokenManager = $tokenManager;\n $this->rateLimiter = $rateLimiter;\n\n $this->setBaseUrl(self::BASE_URL);\n $this->setVersion(self::MIN_API_VERSION);\n }\n\n /**\n * Single entry point for every HubSpot API call. Enforces the per-portal\n * rate limit configured in the rate_limits table (morphed to the current\n * Configuration) and reacts to a real 429 from HubSpot by translating it\n * into a RateLimitException carrying Retry-After.\n *\n * Wrap any outbound HubSpot call (SDK or raw HTTP) like:\n *\n * $this->executeRequest(fn () => $this->getNewInstance()->crm()->...);\n *\n * @template T\n * @param callable(): T $apiCall\n * @return T\n *\n * @throws RateLimitException\n */\n private function executeRequest(callable $apiCall)\n {\n if (! $this->rateLimiter->canMakeRequest($this->config)) {\n $retryAfter = $this->rateLimiter->requestAvailableIn($this->config);\n\n $this->log->warning('[Hubspot] Rate limit exceeded, deferring request', [\n 'team_id' => $this->config->team_id,\n 'config_id' => $this->config->getId(),\n 'retry_after' => $retryAfter,\n ]);\n\n throw new RateLimitException(\n 'Hubspot rate limit reached for configuration ' . $this->config->getId(),\n $retryAfter,\n );\n }\n\n $this->rateLimiter->incrementRequestCount($this->config);\n\n try {\n return $apiCall();\n } catch (Throwable $e) {\n if ($this->isHubspotRateLimit($e)) {\n $retryAfter = $this->parseRetryAfter($e);\n\n $this->log->warning('[Hubspot] Received 429 from API', [\n 'team_id' => $this->config->team_id,\n 'config_id' => $this->config->getId(),\n 'retry_after' => $retryAfter,\n 'reason' => $e->getMessage(),\n ]);\n\n throw new RateLimitException('Hubspot returned 429', $retryAfter, $e);\n }\n\n throw $e;\n }\n }\n\n private function isHubspotRateLimit(Throwable $e): bool\n {\n return method_exists($e, 'getCode') && (int) $e->getCode() === 429;\n }\n\n private function parseRetryAfter(Throwable $e): int\n {\n if (method_exists($e, 'getResponseHeaders')) {\n $headers = $e->getResponseHeaders() ?: [];\n $value = $headers['Retry-After'] ?? $headers['retry-after'] ?? null;\n if (is_array($value)) {\n $value = $value[0] ?? null;\n }\n if (is_numeric($value)) {\n return (int) $value;\n }\n }\n\n return 10;\n }\n\n public function getMinimumApiVersion(): string\n {\n return self::MIN_API_VERSION;\n }\n\n public function getInstance(): Factory\n {\n return new Factory([\n 'key' => $this->accessToken,\n 'oauth2' => true,\n 'base_url' => $this->baseUrl,\n ]);\n }\n\n public function getNewInstance(): Discovery\n {\n return \\HubSpot\\Factory::createWithAccessToken($this->accessToken);\n }\n\n /**\n * Secondly and daily limits for Hubspot API\n *\n * Product Tier: Free & Starter | Professional & Enterprise | API add-on (any tier)\n * Burst: 100/10 seconds | 150/10 seconds | 200/10 seconds\n * Daily: 250,000 | 500,000 | 1,000,000\n *\n * Official documentation states: The search endpoints are rate limited to five requests per second.\n * Since with 5 RPS were still hitting secondly rate limits we lowered it to 4\n */\n public function getPaginatedData(array $payload, string $type, int $offset = 0): array\n {\n $total = 0;\n $lastId = null;\n $rows = [];\n foreach ($this->getPaginatedDataGenerator($payload, $type, $offset, $total, $lastId) as $row) {\n $rows[] = $row;\n }\n\n return ['results' => $rows, 'total' => $total, 'last_record' => $lastId];\n }\n\n /**\n * @throws HubspotException\n * @throws SocialAccountTokenInvalidException\n * @throws BadRequest\n */\n public function getPaginatedDataGenerator(\n array $payload,\n string $type,\n int $offset = 0,\n int &$total = 0,\n ?string &$lastRecordId = null\n ): \\Generator {\n return $this->paginationService->getPaginatedDataGenerator(\n $this,\n $payload,\n $type,\n $offset,\n $total,\n $lastRecordId\n );\n }\n\n /**\n * @throws DealApiException\n * @throws CrmException\n */\n public function getOpportunityById(string $crmId, array $fields): array\n {\n try {\n $deal = $this->executeRequest(fn () => $this->getNewInstance()->crm()->deals()->basicApi()->getById(\n $crmId,\n implode(',', $fields),\n 'companies,contacts'\n ));\n } catch (DealApiException $e) {\n $this->log->info('[Hubspot] Failed to fetch opportunity', [\n 'crm_id' => $crmId,\n 'reason' => $e->getMessage(),\n ]);\n\n throw $e;\n }\n\n if (! $deal instanceof DealWithAssociations) {\n throw new CrmException('Deal not found');\n }\n\n return [\n 'id' => $deal->getId(),\n 'properties' => $deal->getProperties(),\n 'associations' => $deal->getAssociations(),\n ];\n }\n\n /**\n * Generic batch read method for HubSpot objects\n *\n * @param string $objectType The object type ('deals', 'companies', 'contacts')\n * @param array<string> $crmIds Array of HubSpot object IDs (max 100)\n * @param array<string> $fields Array of property names to fetch\n *\n * @return array<string, array> Array keyed by CRM ID with object data\n */\n private function batchReadObjects(string $objectType, array $crmIds, array $fields): array\n {\n if (empty($crmIds)) {\n return [];\n }\n\n $this->validateBatchSize($objectType, $crmIds);\n $this->ensureValidToken();\n\n try {\n $batchConfig = $this->createBatchConfiguration($objectType);\n $batchReadRequest = $this->prepareBatchRequest($batchConfig, $crmIds, $fields);\n $response = $batchConfig['api']->read($batchReadRequest);\n\n $this->validateApiResponse($response, $objectType);\n\n $results = $this->processApiResults($response);\n $this->logBatchResults($objectType, $crmIds, $results);\n\n return $results;\n } catch (\\Throwable $e) {\n $this->handleBatchError($e, $objectType, $crmIds);\n }\n }\n\n private function validateBatchSize(string $objectType, array $crmIds): void\n {\n if (count($crmIds) > 100) {\n throw new \\InvalidArgumentException(\"Batch size cannot exceed 100 {$objectType}\");\n }\n }\n\n private function createBatchConfiguration(string $objectType): array\n {\n $configurations = [\n 'deals' => [\n 'batchReadRequest' => new \\HubSpot\\Client\\Crm\\Deals\\Model\\BatchReadInputSimplePublicObjectId(),\n 'inputClass' => \\HubSpot\\Client\\Crm\\Deals\\Model\\SimplePublicObjectId::class,\n 'api' => $this->getNewInstance()->crm()->deals()->batchApi(),\n ],\n 'companies' => [\n 'batchReadRequest' => new \\HubSpot\\Client\\Crm\\Companies\\Model\\BatchReadInputSimplePublicObjectId(),\n 'inputClass' => \\HubSpot\\Client\\Crm\\Companies\\Model\\SimplePublicObjectId::class,\n 'api' => $this->getNewInstance()->crm()->companies()->batchApi(),\n ],\n 'contacts' => [\n 'batchReadRequest' => new \\HubSpot\\Client\\Crm\\Contacts\\Model\\BatchReadInputSimplePublicObjectId(),\n 'inputClass' => \\HubSpot\\Client\\Crm\\Contacts\\Model\\SimplePublicObjectId::class,\n 'api' => $this->getNewInstance()->crm()->contacts()->batchApi(),\n ],\n ];\n\n if (! isset($configurations[$objectType])) {\n throw new \\InvalidArgumentException(\"Unsupported object type: {$objectType}\");\n }\n\n return $configurations[$objectType];\n }\n\n private function prepareBatchRequest(array $batchConfig, array $crmIds, array $fields): object\n {\n $batchReadRequest = $batchConfig['batchReadRequest'];\n $inputClass = $batchConfig['inputClass'];\n\n $inputs = array_map(function ($crmId) use ($inputClass) {\n $input = new $inputClass();\n $input->setId($crmId);\n\n return $input;\n }, $crmIds);\n\n $batchReadRequest->setInputs($inputs);\n $batchReadRequest->setProperties($fields);\n\n return $batchReadRequest;\n }\n\n private function validateApiResponse($response, string $objectType): void\n {\n if (! $response) {\n throw new CrmException(\"HubSpot API returned null response for {$objectType} batch read\");\n }\n }\n\n private function processApiResults($response): array\n {\n $results = [];\n $responseResults = $response->getResults();\n\n if ($responseResults) {\n foreach ($responseResults as $object) {\n if ($object && $object->getId()) {\n $results[$object->getId()] = [\n 'id' => $object->getId(),\n 'properties' => $object->getProperties() ?: [],\n ];\n }\n }\n }\n\n return $results;\n }\n\n private function logBatchResults(string $objectType, array $crmIds, array $results): void\n {\n $this->log->info(\"[HubSpot] Batch fetched {$objectType}\", [\n 'requested_count' => count($crmIds),\n 'returned_count' => count($results),\n 'crm_ids' => $crmIds,\n ]);\n }\n\n private function handleBatchError(\\Throwable $e, string $objectType, array $crmIds): void\n {\n $errorMessage = $e->getMessage() ?: 'Unknown error';\n $errorTrace = $e->getTraceAsString() ?: 'No trace available';\n\n $this->log->error(\"[HubSpot] Failed to batch fetch {$objectType}\", [\n 'crm_ids' => $crmIds,\n 'error' => $errorMessage,\n 'trace' => $errorTrace,\n ]);\n\n throw new CrmException(\"Failed to batch fetch {$objectType}: \" . $errorMessage);\n }\n\n /**\n * Batch read multiple opportunities by their CRM IDs\n *\n * @param array<string> $crmIds Array of HubSpot deal IDs (max 100)\n * @param array<string> $fields Array of property names to fetch\n *\n * @return array<string, array> Array keyed by CRM ID with opportunity data\n */\n public function getOpportunitiesByIds(array $crmIds, array $fields): array\n {\n return $this->batchReadObjects('deals', $crmIds, $fields);\n }\n\n /**\n * Batch read multiple companies by their CRM IDs\n *\n * @param array<string> $crmIds Array of HubSpot company IDs (max 100)\n * @param array<string> $fields Array of property names to fetch\n *\n * @return array<string, array> Array keyed by CRM ID with company data\n */\n public function getCompaniesByIds(array $crmIds, array $fields): array\n {\n return $this->batchReadObjects('companies', $crmIds, $fields);\n }\n\n /**\n * Batch read multiple contacts by their CRM IDs\n *\n * @param array<string> $crmIds Array of HubSpot contact IDs (max 100)\n * @param array<string> $fields Array of property names to fetch\n *\n * @return array<string, array> Array keyed by CRM ID with contact data\n */\n public function getContactsByIds(array $crmIds, array $fields): array\n {\n return $this->batchReadObjects('contacts', $crmIds, $fields);\n }\n\n /**\n * @throws CompanyApiException\n * @throws CrmException\n */\n public function getAccountById(string $crmId, array $fields): array\n {\n try {\n $company = $this->getNewInstance()->crm()->companies()->basicApi()->getById(\n $crmId,\n implode(',', $fields),\n );\n } catch (CompanyApiException $e) {\n $this->log->info('[Hubspot] Failed to fetch account', [\n 'crm_id' => $crmId,\n 'reason' => $e->getMessage(),\n ]);\n\n throw $e;\n }\n\n if (! $company instanceof CompaniesWithAssociations) {\n throw new CrmException('Account not found');\n }\n\n return [\n 'id' => $company->getId(),\n 'properties' => $company->getProperties(),\n ];\n }\n\n /**\n * @throws ContactApiException\n * @throws CrmException\n */\n public function getContactById(string $crmId, array $fields): array\n {\n try {\n $contact = $this->getNewInstance()->crm()->contacts()->basicApi()->getById(\n $crmId,\n implode(',', $fields)\n );\n } catch (ContactApiException $e) {\n $this->log->info('[Hubspot] Failed to fetch contact', [\n 'crm_id' => $crmId,\n 'reason' => $e->getMessage(),\n ]);\n\n throw $e;\n }\n\n if (! $contact instanceof ContactsWithAssociations) {\n throw new CrmException('Contact not found');\n }\n\n return [\n 'id' => $contact->getId(),\n 'properties' => $contact->getProperties(),\n ];\n }\n\n /**\n * This is email search request that Hubspot offers as GET (more generous quota)\n */\n public function getContactByEmail(string $email, array $fields = []): array\n {\n try {\n $contact = $this->getNewInstance()->crm()->contacts()->basicApi()->getById(\n $email,\n implode(',', $fields),\n null,\n false,\n 'email'\n );\n\n return [\n 'id' => $contact->getId(),\n 'properties' => $contact->getProperties(),\n ];\n } catch (ContactApiException $e) {\n $this->log->info('[Hubspot] Failed to fetch contact', [\n 'email' => $email,\n 'reason' => $e->getMessage(),\n ]);\n\n return [];\n }\n }\n\n /**\n * @throws CrmException\n */\n public function fetchProperty(string $objectType, string $propertyId): Property\n {\n $result = $this->getNewInstance()->crm()->properties()->coreApi()->getByName($objectType, $propertyId);\n\n if (! $result instanceof Property) {\n $this->log->error('[Hubspot] Failed to fetch property', [\n 'object_type' => $objectType,\n 'property_id' => $propertyId,\n 'reason' => $result->getMessage(),\n ]);\n\n throw new CrmException('Failed to fetch property');\n }\n\n return $result;\n }\n\n /**\n * @return array<CrmFieldOption>\n */\n public function fetchPropertyOptions(string $objectType, string $propertyId): array\n {\n /** @var array<CrmFieldOption> */\n return $this->fetchProperty($objectType, $propertyId)->getOptions();\n }\n\n /**\n * @return array<array{id:string, label:string, deleted:bool}>\n */\n public function fetchCallDispositions(): array\n {\n /** @var Response $response */\n $response = $this->getInstance()->engagements()->getCallDispositions();\n\n /**\n * @var array<array{\n * id:string,\n * label:string,\n * deleted: bool\n * }>\n */\n return $response->toArray();\n }\n\n /**\n * @return array<CrmFieldOption>\n */\n public function fetchOpportunityPipelineStages(): array\n {\n $stages = [];\n $apiResponse = $this->getNewInstance()->crm()->pipelines()->pipelinesApi()->getAll('deals');\n\n if ($apiResponse instanceof Error) {\n $this->log->error('[Hubspot] Failed to fetch opportunity pipelines', [\n 'reason' => $apiResponse->getMessage(),\n ]);\n\n return [];\n }\n\n foreach ($apiResponse->getResults() as $pipeline) {\n $pipelineStages = array_map(\n static function (PipelineStage $stage) {\n return [\n 'id' => $stage->getId(),\n 'label' => $stage->getLabel(),\n ];\n },\n $pipeline->getStages()\n );\n\n $stages = array_merge($stages, $pipelineStages);\n }\n\n return $stages;\n }\n\n public function fetchOpportunityPipelines(): array\n {\n $pipelines = [];\n\n try {\n $apiResponse = $this->makeRequest('/crm/v3/pipelines/deals');\n } catch (\\Exception $e) {\n $this->log->info('[Hubspot] Failed to fetch opportunity pipelines', [\n 'reason' => $e->getMessage(),\n ]);\n\n return [];\n }\n\n $response = $apiResponse->toArray();\n\n foreach ($response['results'] as $pipeline) {\n $pipelines[] = [\n 'id' => $pipeline['id'],\n 'label' => $pipeline['label'],\n ];\n }\n\n return $pipelines;\n }\n\n /**\n * @return array<CrmFieldOption>\n */\n public function fetchMeetingOutcomeFieldOptions(Field $field): array\n {\n return $field->getCrmProviderId() === 'meetingOutcome'\n ? $this->fetchMeetingOutcomeTypes()\n : $this->fetchCallActivityTypes();\n }\n\n public function fetchMeetingOutcomeTypes(): array\n {\n return $this->extractMeetingTypeOptions(\n 'https://api.hubapi.com/crm/v3/properties/meeting/hs_meeting_outcome'\n );\n }\n\n public function fetchCallActivityTypes(): array\n {\n return $this->extractMeetingTypeOptions(\n 'https://api.hubapi.com/crm/v3/properties/call/hs_activity_type'\n );\n }\n\n private function extractMeetingTypeOptions(string $endpoint): array\n {\n /** @var Response $response */\n $response = $this->getInstance()\n ->getClient()\n ->request('GET', $endpoint);\n\n /**\n * @var array<array{\n * value: string,\n * label: string,\n * displayOrder: int\n * }> $optionData\n */\n $optionData = $response->toArray()['options'] ?? [];\n\n $options = [];\n foreach ($optionData as $item) {\n $options[] = [\n 'id' => $item['value'],\n 'value' => $item['value'],\n 'label' => $item['label'],\n 'display_order' => $item['displayOrder'],\n ];\n }\n\n return $options;\n }\n\n /**\n * @return array<CrmFieldOption>\n */\n public function fetchDispositionFieldOptions(): array\n {\n $options = [];\n\n $dispositions = $this->fetchCallDispositions();\n\n foreach ($dispositions as $disposition) {\n if ($disposition['deleted'] !== false) {\n continue;\n }\n\n $option['value'] = $disposition['id'];\n $option['id'] = $disposition['id'];\n $option['label'] = $disposition['label'];\n\n $options[] = $option;\n }\n\n return $options;\n }\n\n /**\n * @return array<CrmFieldOption>\n */\n public function fetchOpportunityFieldOptions(Field $field): array\n {\n if ($field->isStageField()) {\n return $this->fetchOpportunityPipelineStages();\n }\n\n if ($field->isPipelineField()) {\n return $this->fetchOpportunityPipelines();\n }\n\n return $this->fetchPropertyOptions('deals', $field->getCrmProviderId());\n }\n\n /**\n * @throws BadRequest\n * @throws HubspotException\n */\n public function makeRequest(string $endpoint, $method = 'GET', $payload = [], ?string $queryString = null)\n {\n $endpoint = self::BASE_URL . $endpoint;\n\n if ($method === 'GET') {\n $response = $this->getInstance()->getClient()?->request(\n method: $method,\n endpoint: $endpoint,\n query_string: $queryString\n );\n } else {\n $response = $this->getInstance()->getClient()->request($method, $endpoint, [\n 'json' => ($payload),\n ]);\n }\n \n $max = $response->getHeaderLine('X-HubSpot-RateLimit-Max'); // \"110\"\n $remaining = $response->getHeaderLine('X-HubSpot-RateLimit-Remaining'); // \"109\"\n $interval = $response->getHeaderLine('X-HubSpot-RateLimit-Interval-Milliseconds'); // \"10000\"\n $body = json_decode((string) $response->getBody(), true);\n \n \\Illuminate\\Support\\Facades\\Log::channel('custom_channel')->info('$max ' . PHP_EOL . print_r($max, true));\n \\Illuminate\\Support\\Facades\\Log::channel('custom_channel')->info('$remaining ' . PHP_EOL . print_r($remaining, true));\n \\Illuminate\\Support\\Facades\\Log::channel('custom_channel')->info('$interval ' . PHP_EOL . print_r($interval, true));\n \n return $response;\n }\n\n /**\n * @throws BadRequest\n * @throws HubspotException\n */\n public function createMeeting(array $payload): Response\n {\n $endpoint = '/crm/v3/objects/meetings';\n\n return $this->makeRequest($endpoint, 'POST', $payload);\n }\n\n /**\n * @throws BadRequest\n * @throws HubspotException\n */\n public function updateMeeting(string $meetingId, array $payload): Response\n {\n $endpoint = '/crm/v3/objects/meetings/' . $meetingId;\n\n return $this->makeRequest($endpoint, 'PATCH', $payload);\n }\n\n /**\n * @throws \\Exception\n */\n public function createNote(\n string $body,\n string $ownerId,\n int $timestamp,\n string $objectId,\n NoteObject $noteObject\n ): ?string {\n try {\n $noteInput = new SimplePublicObjectInput([\n 'properties' => [\n 'hs_note_body' => $body,\n 'hubspot_owner_id' => $ownerId,\n 'hs_timestamp' => $timestamp,\n ],\n ]);\n\n // Create note\n $note = $this->getNewInstance()->crm()->objects()->basicApi()->create('note', $noteInput);\n\n $this->getNewInstance()->crm()->objects()->associationsApi()->create(\n 'note',\n $note->getId(),\n $this->getNoteObject($noteObject),\n $objectId,\n $this->getNoteAssociationType($noteObject),\n );\n\n return $note->getId();\n } catch (\\Exception $e) {\n $this->log->error('[Hubspot] Failed to create note', [\n 'objectId' => $objectId,\n 'noteObject' => $noteObject->getObjectType(),\n 'reason' => $e->getMessage(),\n ]);\n\n \\Sentry::captureException($e);\n }\n\n return null;\n }\n\n public function updateEngagement(string $objectId, array $engagement, array $metadata): void\n {\n $this->getInstance()->engagements()->update($objectId, $engagement, $metadata);\n }\n\n public function getEngagementData(string $engagementId): array\n {\n $engagement = $this->getInstance()->engagements()->get($engagementId);\n\n return $engagement->toArray();\n }\n\n public function createEngagement(array $engagement, array $associations, array $metadata): Response\n {\n return $this->getInstance()\n ->engagements()\n ->create($engagement, $associations, $metadata);\n }\n\n public function isUnauthorizedException(\\Exception $e): bool\n {\n // Check for specific HubSpot API exception types first\n if ($e instanceof BadRequest) {\n // BadRequest can contain 401 status codes\n return $e->getCode() === 401;\n }\n\n // Check for HTTP client exceptions with status codes\n if ($e instanceof \\GuzzleHttp\\Exception\\RequestException && $e->hasResponse()) {\n $response = $e->getResponse();\n if ($response !== null) {\n return $response->getStatusCode() === 401;\n }\n }\n\n // Check for Guzzle HTTP exceptions\n if ($e instanceof \\GuzzleHttp\\Exception\\ClientException) {\n return $e->getCode() === 401;\n }\n\n // Fallback to string matching as last resort, but be more specific\n $message = strtolower($e->getMessage());\n\n return str_contains($message, '401 unauthorized') ||\n str_contains($message, 'http 401') ||\n str_contains($message, 'status code 401') ||\n (preg_match('/\\b401\\b/', $message) && str_contains($message, 'unauthorized'));\n }\n\n /**\n * Validates and refreshes the access token if needed before API requests.\n * This ensures long-running processes don't fail due to token expiration.\n *\n * @throws SocialAccountTokenInvalidException\n */\n public function ensureValidToken(): void\n {\n if ($this->oauthAccount === null) {\n return;\n }\n\n $newToken = $this->tokenManager->ensureValidToken($this->oauthAccount);\n if ($newToken !== null) {\n $this->accessToken = $newToken;\n }\n }\n\n public function getConfig()\n {\n return $this->config;\n }\n\n // returns only active (archived=false)\n public function getOwners(): array\n {\n return $this->getNewInstance()->crm()->owners()->getAll();\n }\n\n /**\n * @param bool $archived\n *\n * @return array<Owner>|[]\n */\n public function getOwnersArchived(bool $archived = true): array\n {\n $endpoint = '/crm/v3/owners';\n $queryParams = [\n 'archived' => $archived ? 'true' : 'false',\n ];\n $queryString = http_build_query($queryParams);\n\n $owners = [];\n\n try {\n $response = $this->makeRequest(endpoint: $endpoint, queryString: $queryString);\n $responseData = $response?->toArray();\n\n foreach ($responseData['results'] as $result) {\n try {\n $owners[] = Owner::create($result);\n } catch (Throwable $e) {\n $this->log->error('[HubSpot] Failed to process owner data', [\n 'result' => $result,\n 'error' => $e->getMessage(),\n ]);\n\n continue;\n }\n }\n } catch (Throwable $e) {\n $this->log->error('HubSpot] Failed to fetch owners', [\n 'archived' => $archived,\n 'error' => $e->getMessage(),\n ]);\n\n return [];\n }\n\n return $owners;\n }\n\n public function getMeeting(string $engagementId): ObjectWithAssociations\n {\n return $this->getNewInstance()->crm()->objects()->basicApi()\n ->getById('meeting', $engagementId, null, 'contact,company,deal');\n }\n\n public function deleteEngagement(string $engagementId): void\n {\n $this->getInstance()->engagements()->delete((int) $engagementId);\n }\n\n public function getAssociationsData(array $ids, string $fromObject, string $toObject): array\n {\n $associationData = [];\n $idChunks = array_chunk($ids, self::ASSOCIATIONS_BATCH_SIZE_LIMIT);\n\n foreach ($idChunks as $idChunk) {\n try {\n $batchInput = new \\HubSpot\\Client\\Crm\\Associations\\Model\\BatchInputPublicObjectId();\n $batchInput->setInputs(array_map(function ($id) {\n $publicObjectId = new \\HubSpot\\Client\\Crm\\Associations\\Model\\PublicObjectId();\n $publicObjectId->setId($id);\n\n return $publicObjectId;\n }, $idChunk));\n\n $associatedObjectsData = $this\n ->getNewInstance()\n ->crm()\n ->associations()\n ->batchApi()\n ->read($fromObject, $toObject, $batchInput);\n\n if ($associatedObjectsData instanceof \\HubSpot\\Client\\Crm\\Associations\\Model\\BatchResponsePublicAssociationMulti) {\n foreach ($associatedObjectsData->getResults() as $association) {\n $from = $association->getFrom()->getId();\n $toAssociations = $association->getTo();\n\n if (! empty($toAssociations)) {\n $associationData[$from] = array_map(function ($item) {\n return $item->getId();\n }, $toAssociations);\n }\n }\n }\n } catch (\\Exception $e) {\n $this->log->error('[Hubspot] Failed to fetch associations', [\n 'from_object' => $fromObject,\n 'to_object' => $toObject,\n 'reason' => $e->getMessage(),\n ]);\n }\n }\n\n return $associationData;\n }\n\n /**\n * @throws \\Exception\n */\n private function getNoteAssociationType(NoteObject $noteObject): string\n {\n return match($noteObject) {\n NoteObject::Opportunity => 'note_to_deal',\n NoteObject::Lead, NoteObject::Contact => 'note_to_contact', // or 'note_to_lead' if your portal supports it\n NoteObject::Account => 'note_to_company',\n NoteObject::Call, NoteObject::Event => throw new \\Exception('Not supported'),\n };\n }\n\n /**\n * @throws \\Exception\n */\n private function getNoteObject(NoteObject $noteObject): string\n {\n return match($noteObject) {\n NoteObject::Opportunity => 'deal',\n NoteObject::Lead, NoteObject::Contact => 'contact',\n NoteObject::Account => 'company',\n NoteObject::Call, NoteObject::Event => throw new \\Exception('Not supported'),\n };\n }\n\n public function addAssociations(string $objectType, string $associationType, array $payload): Response\n {\n $endpoint = \"/crm/v4/associations/$objectType/$associationType/batch/create\";\n\n return $this->makeRequest($endpoint, 'POST', $payload);\n }\n\n public function removeAssociations(string $objectType, string $associationType, array $payload): Response\n {\n $endpoint = \"/crm/v4/associations/$objectType/$associationType/batch/archive\";\n\n return $this->makeRequest($endpoint, 'POST', $payload);\n }\n}","depth":4,"on_screen":true,"value":"<?php\n\ndeclare(strict_types=1);\n\nnamespace Jiminny\\Services\\Crm\\Hubspot;\n\nuse HubSpot\\Client\\Crm\\Deals\\ApiException as DealApiException;\nuse HubSpot\\Client\\Crm\\Contacts\\ApiException as ContactApiException;\nuse HubSpot\\Client\\Crm\\Companies\\ApiException as CompanyApiException;\nuse HubSpot\\Client\\Crm\\Contacts\\Model\\SimplePublicObjectWithAssociations as ContactsWithAssociations;\nuse HubSpot\\Client\\Crm\\Companies\\Model\\SimplePublicObjectWithAssociations as CompaniesWithAssociations;\nuse HubSpot\\Client\\Crm\\Deals\\Model\\SimplePublicObjectWithAssociations as DealWithAssociations;\nuse HubSpot\\Client\\Crm\\Objects\\Model\\SimplePublicObjectInput;\nuse HubSpot\\Client\\Crm\\Objects\\Model\\SimplePublicObjectWithAssociations as ObjectWithAssociations;\nuse HubSpot\\Client\\Crm\\Pipelines\\Model\\Error;\nuse HubSpot\\Client\\Crm\\Pipelines\\Model\\PipelineStage;\nuse HubSpot\\Client\\Crm\\Properties\\Model\\Property;\nuse HubSpot\\Discovery\\Discovery;\nuse Jiminny\\Component\\Utility\\Service\\ProviderRateLimiter;\nuse Jiminny\\Exceptions\\CrmException;\nuse Jiminny\\Exceptions\\RateLimitException;\nuse Jiminny\\Exceptions\\SocialAccountTokenInvalidException;\nuse Jiminny\\Jobs\\Crm\\NoteObject;\nuse Jiminny\\Models\\Crm\\Field;\nuse Jiminny\\Services\\Crm\\BaseClient;\nuse Jiminny\\Services\\Crm\\Hubspot\\DTO\\Response\\Owner;\nuse Jiminny\\Services\\SocialAccountService;\nuse SevenShores\\Hubspot\\Exceptions\\BadRequest;\nuse SevenShores\\Hubspot\\Exceptions\\HubspotException;\nuse SevenShores\\Hubspot\\Factory;\nuse SevenShores\\Hubspot\\Http\\Response;\nuse Jiminny\\Services\\Crm\\Hubspot\\Pagination\\HubspotPaginationService;\nuse Throwable;\n\n/**\n * @phpstan-type CrmFieldOption array{id:string, label:string, value?:string}\n */\nclass Client extends BaseClient implements HubspotClientInterface\n{\n public const string MIN_API_VERSION = '2';\n\n public const string BASE_URL = 'https://api.hubapi.com';\n\n public const int ASSOCIATIONS_BATCH_SIZE_LIMIT = 1000;\n\n private HubspotPaginationService $paginationService;\n private HubspotTokenManager $tokenManager;\n private ProviderRateLimiter $rateLimiter;\n\n public function __construct(\n SocialAccountService $socialAccountService,\n HubspotPaginationService $paginationService,\n HubspotTokenManager $tokenManager,\n ProviderRateLimiter $rateLimiter,\n ) {\n parent::__construct($socialAccountService);\n $this->paginationService = $paginationService;\n $this->tokenManager = $tokenManager;\n $this->rateLimiter = $rateLimiter;\n\n $this->setBaseUrl(self::BASE_URL);\n $this->setVersion(self::MIN_API_VERSION);\n }\n\n /**\n * Single entry point for every HubSpot API call. Enforces the per-portal\n * rate limit configured in the rate_limits table (morphed to the current\n * Configuration) and reacts to a real 429 from HubSpot by translating it\n * into a RateLimitException carrying Retry-After.\n *\n * Wrap any outbound HubSpot call (SDK or raw HTTP) like:\n *\n * $this->executeRequest(fn () => $this->getNewInstance()->crm()->...);\n *\n * @template T\n * @param callable(): T $apiCall\n * @return T\n *\n * @throws RateLimitException\n */\n private function executeRequest(callable $apiCall)\n {\n if (! $this->rateLimiter->canMakeRequest($this->config)) {\n $retryAfter = $this->rateLimiter->requestAvailableIn($this->config);\n\n $this->log->warning('[Hubspot] Rate limit exceeded, deferring request', [\n 'team_id' => $this->config->team_id,\n 'config_id' => $this->config->getId(),\n 'retry_after' => $retryAfter,\n ]);\n\n throw new RateLimitException(\n 'Hubspot rate limit reached for configuration ' . $this->config->getId(),\n $retryAfter,\n );\n }\n\n $this->rateLimiter->incrementRequestCount($this->config);\n\n try {\n return $apiCall();\n } catch (Throwable $e) {\n if ($this->isHubspotRateLimit($e)) {\n $retryAfter = $this->parseRetryAfter($e);\n\n $this->log->warning('[Hubspot] Received 429 from API', [\n 'team_id' => $this->config->team_id,\n 'config_id' => $this->config->getId(),\n 'retry_after' => $retryAfter,\n 'reason' => $e->getMessage(),\n ]);\n\n throw new RateLimitException('Hubspot returned 429', $retryAfter, $e);\n }\n\n throw $e;\n }\n }\n\n private function isHubspotRateLimit(Throwable $e): bool\n {\n return method_exists($e, 'getCode') && (int) $e->getCode() === 429;\n }\n\n private function parseRetryAfter(Throwable $e): int\n {\n if (method_exists($e, 'getResponseHeaders')) {\n $headers = $e->getResponseHeaders() ?: [];\n $value = $headers['Retry-After'] ?? $headers['retry-after'] ?? null;\n if (is_array($value)) {\n $value = $value[0] ?? null;\n }\n if (is_numeric($value)) {\n return (int) $value;\n }\n }\n\n return 10;\n }\n\n public function getMinimumApiVersion(): string\n {\n return self::MIN_API_VERSION;\n }\n\n public function getInstance(): Factory\n {\n return new Factory([\n 'key' => $this->accessToken,\n 'oauth2' => true,\n 'base_url' => $this->baseUrl,\n ]);\n }\n\n public function getNewInstance(): Discovery\n {\n return \\HubSpot\\Factory::createWithAccessToken($this->accessToken);\n }\n\n /**\n * Secondly and daily limits for Hubspot API\n *\n * Product Tier: Free & Starter | Professional & Enterprise | API add-on (any tier)\n * Burst: 100/10 seconds | 150/10 seconds | 200/10 seconds\n * Daily: 250,000 | 500,000 | 1,000,000\n *\n * Official documentation states: The search endpoints are rate limited to five requests per second.\n * Since with 5 RPS were still hitting secondly rate limits we lowered it to 4\n */\n public function getPaginatedData(array $payload, string $type, int $offset = 0): array\n {\n $total = 0;\n $lastId = null;\n $rows = [];\n foreach ($this->getPaginatedDataGenerator($payload, $type, $offset, $total, $lastId) as $row) {\n $rows[] = $row;\n }\n\n return ['results' => $rows, 'total' => $total, 'last_record' => $lastId];\n }\n\n /**\n * @throws HubspotException\n * @throws SocialAccountTokenInvalidException\n * @throws BadRequest\n */\n public function getPaginatedDataGenerator(\n array $payload,\n string $type,\n int $offset = 0,\n int &$total = 0,\n ?string &$lastRecordId = null\n ): \\Generator {\n return $this->paginationService->getPaginatedDataGenerator(\n $this,\n $payload,\n $type,\n $offset,\n $total,\n $lastRecordId\n );\n }\n\n /**\n * @throws DealApiException\n * @throws CrmException\n */\n public function getOpportunityById(string $crmId, array $fields): array\n {\n try {\n $deal = $this->executeRequest(fn () => $this->getNewInstance()->crm()->deals()->basicApi()->getById(\n $crmId,\n implode(',', $fields),\n 'companies,contacts'\n ));\n } catch (DealApiException $e) {\n $this->log->info('[Hubspot] Failed to fetch opportunity', [\n 'crm_id' => $crmId,\n 'reason' => $e->getMessage(),\n ]);\n\n throw $e;\n }\n\n if (! $deal instanceof DealWithAssociations) {\n throw new CrmException('Deal not found');\n }\n\n return [\n 'id' => $deal->getId(),\n 'properties' => $deal->getProperties(),\n 'associations' => $deal->getAssociations(),\n ];\n }\n\n /**\n * Generic batch read method for HubSpot objects\n *\n * @param string $objectType The object type ('deals', 'companies', 'contacts')\n * @param array<string> $crmIds Array of HubSpot object IDs (max 100)\n * @param array<string> $fields Array of property names to fetch\n *\n * @return array<string, array> Array keyed by CRM ID with object data\n */\n private function batchReadObjects(string $objectType, array $crmIds, array $fields): array\n {\n if (empty($crmIds)) {\n return [];\n }\n\n $this->validateBatchSize($objectType, $crmIds);\n $this->ensureValidToken();\n\n try {\n $batchConfig = $this->createBatchConfiguration($objectType);\n $batchReadRequest = $this->prepareBatchRequest($batchConfig, $crmIds, $fields);\n $response = $batchConfig['api']->read($batchReadRequest);\n\n $this->validateApiResponse($response, $objectType);\n\n $results = $this->processApiResults($response);\n $this->logBatchResults($objectType, $crmIds, $results);\n\n return $results;\n } catch (\\Throwable $e) {\n $this->handleBatchError($e, $objectType, $crmIds);\n }\n }\n\n private function validateBatchSize(string $objectType, array $crmIds): void\n {\n if (count($crmIds) > 100) {\n throw new \\InvalidArgumentException(\"Batch size cannot exceed 100 {$objectType}\");\n }\n }\n\n private function createBatchConfiguration(string $objectType): array\n {\n $configurations = [\n 'deals' => [\n 'batchReadRequest' => new \\HubSpot\\Client\\Crm\\Deals\\Model\\BatchReadInputSimplePublicObjectId(),\n 'inputClass' => \\HubSpot\\Client\\Crm\\Deals\\Model\\SimplePublicObjectId::class,\n 'api' => $this->getNewInstance()->crm()->deals()->batchApi(),\n ],\n 'companies' => [\n 'batchReadRequest' => new \\HubSpot\\Client\\Crm\\Companies\\Model\\BatchReadInputSimplePublicObjectId(),\n 'inputClass' => \\HubSpot\\Client\\Crm\\Companies\\Model\\SimplePublicObjectId::class,\n 'api' => $this->getNewInstance()->crm()->companies()->batchApi(),\n ],\n 'contacts' => [\n 'batchReadRequest' => new \\HubSpot\\Client\\Crm\\Contacts\\Model\\BatchReadInputSimplePublicObjectId(),\n 'inputClass' => \\HubSpot\\Client\\Crm\\Contacts\\Model\\SimplePublicObjectId::class,\n 'api' => $this->getNewInstance()->crm()->contacts()->batchApi(),\n ],\n ];\n\n if (! isset($configurations[$objectType])) {\n throw new \\InvalidArgumentException(\"Unsupported object type: {$objectType}\");\n }\n\n return $configurations[$objectType];\n }\n\n private function prepareBatchRequest(array $batchConfig, array $crmIds, array $fields): object\n {\n $batchReadRequest = $batchConfig['batchReadRequest'];\n $inputClass = $batchConfig['inputClass'];\n\n $inputs = array_map(function ($crmId) use ($inputClass) {\n $input = new $inputClass();\n $input->setId($crmId);\n\n return $input;\n }, $crmIds);\n\n $batchReadRequest->setInputs($inputs);\n $batchReadRequest->setProperties($fields);\n\n return $batchReadRequest;\n }\n\n private function validateApiResponse($response, string $objectType): void\n {\n if (! $response) {\n throw new CrmException(\"HubSpot API returned null response for {$objectType} batch read\");\n }\n }\n\n private function processApiResults($response): array\n {\n $results = [];\n $responseResults = $response->getResults();\n\n if ($responseResults) {\n foreach ($responseResults as $object) {\n if ($object && $object->getId()) {\n $results[$object->getId()] = [\n 'id' => $object->getId(),\n 'properties' => $object->getProperties() ?: [],\n ];\n }\n }\n }\n\n return $results;\n }\n\n private function logBatchResults(string $objectType, array $crmIds, array $results): void\n {\n $this->log->info(\"[HubSpot] Batch fetched {$objectType}\", [\n 'requested_count' => count($crmIds),\n 'returned_count' => count($results),\n 'crm_ids' => $crmIds,\n ]);\n }\n\n private function handleBatchError(\\Throwable $e, string $objectType, array $crmIds): void\n {\n $errorMessage = $e->getMessage() ?: 'Unknown error';\n $errorTrace = $e->getTraceAsString() ?: 'No trace available';\n\n $this->log->error(\"[HubSpot] Failed to batch fetch {$objectType}\", [\n 'crm_ids' => $crmIds,\n 'error' => $errorMessage,\n 'trace' => $errorTrace,\n ]);\n\n throw new CrmException(\"Failed to batch fetch {$objectType}: \" . $errorMessage);\n }\n\n /**\n * Batch read multiple opportunities by their CRM IDs\n *\n * @param array<string> $crmIds Array of HubSpot deal IDs (max 100)\n * @param array<string> $fields Array of property names to fetch\n *\n * @return array<string, array> Array keyed by CRM ID with opportunity data\n */\n public function getOpportunitiesByIds(array $crmIds, array $fields): array\n {\n return $this->batchReadObjects('deals', $crmIds, $fields);\n }\n\n /**\n * Batch read multiple companies by their CRM IDs\n *\n * @param array<string> $crmIds Array of HubSpot company IDs (max 100)\n * @param array<string> $fields Array of property names to fetch\n *\n * @return array<string, array> Array keyed by CRM ID with company data\n */\n public function getCompaniesByIds(array $crmIds, array $fields): array\n {\n return $this->batchReadObjects('companies', $crmIds, $fields);\n }\n\n /**\n * Batch read multiple contacts by their CRM IDs\n *\n * @param array<string> $crmIds Array of HubSpot contact IDs (max 100)\n * @param array<string> $fields Array of property names to fetch\n *\n * @return array<string, array> Array keyed by CRM ID with contact data\n */\n public function getContactsByIds(array $crmIds, array $fields): array\n {\n return $this->batchReadObjects('contacts', $crmIds, $fields);\n }\n\n /**\n * @throws CompanyApiException\n * @throws CrmException\n */\n public function getAccountById(string $crmId, array $fields): array\n {\n try {\n $company = $this->getNewInstance()->crm()->companies()->basicApi()->getById(\n $crmId,\n implode(',', $fields),\n );\n } catch (CompanyApiException $e) {\n $this->log->info('[Hubspot] Failed to fetch account', [\n 'crm_id' => $crmId,\n 'reason' => $e->getMessage(),\n ]);\n\n throw $e;\n }\n\n if (! $company instanceof CompaniesWithAssociations) {\n throw new CrmException('Account not found');\n }\n\n return [\n 'id' => $company->getId(),\n 'properties' => $company->getProperties(),\n ];\n }\n\n /**\n * @throws ContactApiException\n * @throws CrmException\n */\n public function getContactById(string $crmId, array $fields): array\n {\n try {\n $contact = $this->getNewInstance()->crm()->contacts()->basicApi()->getById(\n $crmId,\n implode(',', $fields)\n );\n } catch (ContactApiException $e) {\n $this->log->info('[Hubspot] Failed to fetch contact', [\n 'crm_id' => $crmId,\n 'reason' => $e->getMessage(),\n ]);\n\n throw $e;\n }\n\n if (! $contact instanceof ContactsWithAssociations) {\n throw new CrmException('Contact not found');\n }\n\n return [\n 'id' => $contact->getId(),\n 'properties' => $contact->getProperties(),\n ];\n }\n\n /**\n * This is email search request that Hubspot offers as GET (more generous quota)\n */\n public function getContactByEmail(string $email, array $fields = []): array\n {\n try {\n $contact = $this->getNewInstance()->crm()->contacts()->basicApi()->getById(\n $email,\n implode(',', $fields),\n null,\n false,\n 'email'\n );\n\n return [\n 'id' => $contact->getId(),\n 'properties' => $contact->getProperties(),\n ];\n } catch (ContactApiException $e) {\n $this->log->info('[Hubspot] Failed to fetch contact', [\n 'email' => $email,\n 'reason' => $e->getMessage(),\n ]);\n\n return [];\n }\n }\n\n /**\n * @throws CrmException\n */\n public function fetchProperty(string $objectType, string $propertyId): Property\n {\n $result = $this->getNewInstance()->crm()->properties()->coreApi()->getByName($objectType, $propertyId);\n\n if (! $result instanceof Property) {\n $this->log->error('[Hubspot] Failed to fetch property', [\n 'object_type' => $objectType,\n 'property_id' => $propertyId,\n 'reason' => $result->getMessage(),\n ]);\n\n throw new CrmException('Failed to fetch property');\n }\n\n return $result;\n }\n\n /**\n * @return array<CrmFieldOption>\n */\n public function fetchPropertyOptions(string $objectType, string $propertyId): array\n {\n /** @var array<CrmFieldOption> */\n return $this->fetchProperty($objectType, $propertyId)->getOptions();\n }\n\n /**\n * @return array<array{id:string, label:string, deleted:bool}>\n */\n public function fetchCallDispositions(): array\n {\n /** @var Response $response */\n $response = $this->getInstance()->engagements()->getCallDispositions();\n\n /**\n * @var array<array{\n * id:string,\n * label:string,\n * deleted: bool\n * }>\n */\n return $response->toArray();\n }\n\n /**\n * @return array<CrmFieldOption>\n */\n public function fetchOpportunityPipelineStages(): array\n {\n $stages = [];\n $apiResponse = $this->getNewInstance()->crm()->pipelines()->pipelinesApi()->getAll('deals');\n\n if ($apiResponse instanceof Error) {\n $this->log->error('[Hubspot] Failed to fetch opportunity pipelines', [\n 'reason' => $apiResponse->getMessage(),\n ]);\n\n return [];\n }\n\n foreach ($apiResponse->getResults() as $pipeline) {\n $pipelineStages = array_map(\n static function (PipelineStage $stage) {\n return [\n 'id' => $stage->getId(),\n 'label' => $stage->getLabel(),\n ];\n },\n $pipeline->getStages()\n );\n\n $stages = array_merge($stages, $pipelineStages);\n }\n\n return $stages;\n }\n\n public function fetchOpportunityPipelines(): array\n {\n $pipelines = [];\n\n try {\n $apiResponse = $this->makeRequest('/crm/v3/pipelines/deals');\n } catch (\\Exception $e) {\n $this->log->info('[Hubspot] Failed to fetch opportunity pipelines', [\n 'reason' => $e->getMessage(),\n ]);\n\n return [];\n }\n\n $response = $apiResponse->toArray();\n\n foreach ($response['results'] as $pipeline) {\n $pipelines[] = [\n 'id' => $pipeline['id'],\n 'label' => $pipeline['label'],\n ];\n }\n\n return $pipelines;\n }\n\n /**\n * @return array<CrmFieldOption>\n */\n public function fetchMeetingOutcomeFieldOptions(Field $field): array\n {\n return $field->getCrmProviderId() === 'meetingOutcome'\n ? $this->fetchMeetingOutcomeTypes()\n : $this->fetchCallActivityTypes();\n }\n\n public function fetchMeetingOutcomeTypes(): array\n {\n return $this->extractMeetingTypeOptions(\n 'https://api.hubapi.com/crm/v3/properties/meeting/hs_meeting_outcome'\n );\n }\n\n public function fetchCallActivityTypes(): array\n {\n return $this->extractMeetingTypeOptions(\n 'https://api.hubapi.com/crm/v3/properties/call/hs_activity_type'\n );\n }\n\n private function extractMeetingTypeOptions(string $endpoint): array\n {\n /** @var Response $response */\n $response = $this->getInstance()\n ->getClient()\n ->request('GET', $endpoint);\n\n /**\n * @var array<array{\n * value: string,\n * label: string,\n * displayOrder: int\n * }> $optionData\n */\n $optionData = $response->toArray()['options'] ?? [];\n\n $options = [];\n foreach ($optionData as $item) {\n $options[] = [\n 'id' => $item['value'],\n 'value' => $item['value'],\n 'label' => $item['label'],\n 'display_order' => $item['displayOrder'],\n ];\n }\n\n return $options;\n }\n\n /**\n * @return array<CrmFieldOption>\n */\n public function fetchDispositionFieldOptions(): array\n {\n $options = [];\n\n $dispositions = $this->fetchCallDispositions();\n\n foreach ($dispositions as $disposition) {\n if ($disposition['deleted'] !== false) {\n continue;\n }\n\n $option['value'] = $disposition['id'];\n $option['id'] = $disposition['id'];\n $option['label'] = $disposition['label'];\n\n $options[] = $option;\n }\n\n return $options;\n }\n\n /**\n * @return array<CrmFieldOption>\n */\n public function fetchOpportunityFieldOptions(Field $field): array\n {\n if ($field->isStageField()) {\n return $this->fetchOpportunityPipelineStages();\n }\n\n if ($field->isPipelineField()) {\n return $this->fetchOpportunityPipelines();\n }\n\n return $this->fetchPropertyOptions('deals', $field->getCrmProviderId());\n }\n\n /**\n * @throws BadRequest\n * @throws HubspotException\n */\n public function makeRequest(string $endpoint, $method = 'GET', $payload = [], ?string $queryString = null)\n {\n $endpoint = self::BASE_URL . $endpoint;\n\n if ($method === 'GET') {\n $response = $this->getInstance()->getClient()?->request(\n method: $method,\n endpoint: $endpoint,\n query_string: $queryString\n );\n } else {\n $response = $this->getInstance()->getClient()->request($method, $endpoint, [\n 'json' => ($payload),\n ]);\n }\n \n $max = $response->getHeaderLine('X-HubSpot-RateLimit-Max'); // \"110\"\n $remaining = $response->getHeaderLine('X-HubSpot-RateLimit-Remaining'); // \"109\"\n $interval = $response->getHeaderLine('X-HubSpot-RateLimit-Interval-Milliseconds'); // \"10000\"\n $body = json_decode((string) $response->getBody(), true);\n \n \\Illuminate\\Support\\Facades\\Log::channel('custom_channel')->info('$max ' . PHP_EOL . print_r($max, true));\n \\Illuminate\\Support\\Facades\\Log::channel('custom_channel')->info('$remaining ' . PHP_EOL . print_r($remaining, true));\n \\Illuminate\\Support\\Facades\\Log::channel('custom_channel')->info('$interval ' . PHP_EOL . print_r($interval, true));\n \n return $response;\n }\n\n /**\n * @throws BadRequest\n * @throws HubspotException\n */\n public function createMeeting(array $payload): Response\n {\n $endpoint = '/crm/v3/objects/meetings';\n\n return $this->makeRequest($endpoint, 'POST', $payload);\n }\n\n /**\n * @throws BadRequest\n * @throws HubspotException\n */\n public function updateMeeting(string $meetingId, array $payload): Response\n {\n $endpoint = '/crm/v3/objects/meetings/' . $meetingId;\n\n return $this->makeRequest($endpoint, 'PATCH', $payload);\n }\n\n /**\n * @throws \\Exception\n */\n public function createNote(\n string $body,\n string $ownerId,\n int $timestamp,\n string $objectId,\n NoteObject $noteObject\n ): ?string {\n try {\n $noteInput = new SimplePublicObjectInput([\n 'properties' => [\n 'hs_note_body' => $body,\n 'hubspot_owner_id' => $ownerId,\n 'hs_timestamp' => $timestamp,\n ],\n ]);\n\n // Create note\n $note = $this->getNewInstance()->crm()->objects()->basicApi()->create('note', $noteInput);\n\n $this->getNewInstance()->crm()->objects()->associationsApi()->create(\n 'note',\n $note->getId(),\n $this->getNoteObject($noteObject),\n $objectId,\n $this->getNoteAssociationType($noteObject),\n );\n\n return $note->getId();\n } catch (\\Exception $e) {\n $this->log->error('[Hubspot] Failed to create note', [\n 'objectId' => $objectId,\n 'noteObject' => $noteObject->getObjectType(),\n 'reason' => $e->getMessage(),\n ]);\n\n \\Sentry::captureException($e);\n }\n\n return null;\n }\n\n public function updateEngagement(string $objectId, array $engagement, array $metadata): void\n {\n $this->getInstance()->engagements()->update($objectId, $engagement, $metadata);\n }\n\n public function getEngagementData(string $engagementId): array\n {\n $engagement = $this->getInstance()->engagements()->get($engagementId);\n\n return $engagement->toArray();\n }\n\n public function createEngagement(array $engagement, array $associations, array $metadata): Response\n {\n return $this->getInstance()\n ->engagements()\n ->create($engagement, $associations, $metadata);\n }\n\n public function isUnauthorizedException(\\Exception $e): bool\n {\n // Check for specific HubSpot API exception types first\n if ($e instanceof BadRequest) {\n // BadRequest can contain 401 status codes\n return $e->getCode() === 401;\n }\n\n // Check for HTTP client exceptions with status codes\n if ($e instanceof \\GuzzleHttp\\Exception\\RequestException && $e->hasResponse()) {\n $response = $e->getResponse();\n if ($response !== null) {\n return $response->getStatusCode() === 401;\n }\n }\n\n // Check for Guzzle HTTP exceptions\n if ($e instanceof \\GuzzleHttp\\Exception\\ClientException) {\n return $e->getCode() === 401;\n }\n\n // Fallback to string matching as last resort, but be more specific\n $message = strtolower($e->getMessage());\n\n return str_contains($message, '401 unauthorized') ||\n str_contains($message, 'http 401') ||\n str_contains($message, 'status code 401') ||\n (preg_match('/\\b401\\b/', $message) && str_contains($message, 'unauthorized'));\n }\n\n /**\n * Validates and refreshes the access token if needed before API requests.\n * This ensures long-running processes don't fail due to token expiration.\n *\n * @throws SocialAccountTokenInvalidException\n */\n public function ensureValidToken(): void\n {\n if ($this->oauthAccount === null) {\n return;\n }\n\n $newToken = $this->tokenManager->ensureValidToken($this->oauthAccount);\n if ($newToken !== null) {\n $this->accessToken = $newToken;\n }\n }\n\n public function getConfig()\n {\n return $this->config;\n }\n\n // returns only active (archived=false)\n public function getOwners(): array\n {\n return $this->getNewInstance()->crm()->owners()->getAll();\n }\n\n /**\n * @param bool $archived\n *\n * @return array<Owner>|[]\n */\n public function getOwnersArchived(bool $archived = true): array\n {\n $endpoint = '/crm/v3/owners';\n $queryParams = [\n 'archived' => $archived ? 'true' : 'false',\n ];\n $queryString = http_build_query($queryParams);\n\n $owners = [];\n\n try {\n $response = $this->makeRequest(endpoint: $endpoint, queryString: $queryString);\n $responseData = $response?->toArray();\n\n foreach ($responseData['results'] as $result) {\n try {\n $owners[] = Owner::create($result);\n } catch (Throwable $e) {\n $this->log->error('[HubSpot] Failed to process owner data', [\n 'result' => $result,\n 'error' => $e->getMessage(),\n ]);\n\n continue;\n }\n }\n } catch (Throwable $e) {\n $this->log->error('HubSpot] Failed to fetch owners', [\n 'archived' => $archived,\n 'error' => $e->getMessage(),\n ]);\n\n return [];\n }\n\n return $owners;\n }\n\n public function getMeeting(string $engagementId): ObjectWithAssociations\n {\n return $this->getNewInstance()->crm()->objects()->basicApi()\n ->getById('meeting', $engagementId, null, 'contact,company,deal');\n }\n\n public function deleteEngagement(string $engagementId): void\n {\n $this->getInstance()->engagements()->delete((int) $engagementId);\n }\n\n public function getAssociationsData(array $ids, string $fromObject, string $toObject): array\n {\n $associationData = [];\n $idChunks = array_chunk($ids, self::ASSOCIATIONS_BATCH_SIZE_LIMIT);\n\n foreach ($idChunks as $idChunk) {\n try {\n $batchInput = new \\HubSpot\\Client\\Crm\\Associations\\Model\\BatchInputPublicObjectId();\n $batchInput->setInputs(array_map(function ($id) {\n $publicObjectId = new \\HubSpot\\Client\\Crm\\Associations\\Model\\PublicObjectId();\n $publicObjectId->setId($id);\n\n return $publicObjectId;\n }, $idChunk));\n\n $associatedObjectsData = $this\n ->getNewInstance()\n ->crm()\n ->associations()\n ->batchApi()\n ->read($fromObject, $toObject, $batchInput);\n\n if ($associatedObjectsData instanceof \\HubSpot\\Client\\Crm\\Associations\\Model\\BatchResponsePublicAssociationMulti) {\n foreach ($associatedObjectsData->getResults() as $association) {\n $from = $association->getFrom()->getId();\n $toAssociations = $association->getTo();\n\n if (! empty($toAssociations)) {\n $associationData[$from] = array_map(function ($item) {\n return $item->getId();\n }, $toAssociations);\n }\n }\n }\n } catch (\\Exception $e) {\n $this->log->error('[Hubspot] Failed to fetch associations', [\n 'from_object' => $fromObject,\n 'to_object' => $toObject,\n 'reason' => $e->getMessage(),\n ]);\n }\n }\n\n return $associationData;\n }\n\n /**\n * @throws \\Exception\n */\n private function getNoteAssociationType(NoteObject $noteObject): string\n {\n return match($noteObject) {\n NoteObject::Opportunity => 'note_to_deal',\n NoteObject::Lead, NoteObject::Contact => 'note_to_contact', // or 'note_to_lead' if your portal supports it\n NoteObject::Account => 'note_to_company',\n NoteObject::Call, NoteObject::Event => throw new \\Exception('Not supported'),\n };\n }\n\n /**\n * @throws \\Exception\n */\n private function getNoteObject(NoteObject $noteObject): string\n {\n return match($noteObject) {\n NoteObject::Opportunity => 'deal',\n NoteObject::Lead, NoteObject::Contact => 'contact',\n NoteObject::Account => 'company',\n NoteObject::Call, NoteObject::Event => throw new \\Exception('Not supported'),\n };\n }\n\n public function addAssociations(string $objectType, string $associationType, array $payload): Response\n {\n $endpoint = \"/crm/v4/associations/$objectType/$associationType/batch/create\";\n\n return $this->makeRequest($endpoint, 'POST', $payload);\n }\n\n public function removeAssociations(string $objectType, string $associationType, array $payload): Response\n {\n $endpoint = \"/crm/v4/associations/$objectType/$associationType/batch/archive\";\n\n return $this->makeRequest($endpoint, 'POST', $payload);\n }\n}","role_description":"text entry area","is_enabled":true,"is_focused":true,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"Project","depth":3,"on_screen":false,"role_description":"text"},{"role":"AXButton","text":"Project","depth":3,"bounds":{"left":0.011968086,"top":0.047885075,"width":0.024268618,"height":0.024740623},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"New File or Directory…","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.008643617,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Expand Selected","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.008643617,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Collapse All","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.008643617,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Options","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.008643617,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Hide","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.008643617,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false}]...
|
-3585960046888772555
|
5531537304406333540
|
click
|
accessibility
|
NULL
|
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
Execute
Explain Plan
Browse Query History
View Parameters
Open Query Execution Settings…
In-Editor Results
Tx: Auto
Cancel Running Statements
Playground
jiminny
Sync Changes
Hide This Notification
Code changed:
Hide
6
1
6
Previous Highlighted Error
Next Highlighted Error
# [PASSWORD_DOTS] HS [PASSWORD_DOTS]
select * from teams where id = 2; # 2
select * from features; # 2
select * from team_features where team_id = 2; # 2
select * from crm_configurations where id = 2; # 2
select * from users where team_id = 2; #
select * from playbooks where team_id = 2; # event 38
select * from playbook_categories where playbook_id = 38; #
SELECT * FROM activities WHERE crm_configuration_id = 2 and crm_provider_id is not null order by id desc;
[URL_WITH_CREDENTIALS] CrmFieldOption array{id:string, label:string, value?:string}
*/
class Client extends BaseClient implements HubspotClientInterface
{
public const string MIN_API_VERSION = '2';
public const string BASE_URL = '[URL_WITH_CREDENTIALS] T
* @param callable(): T $apiCall
* @return T
*
* @throws RateLimitException
*/
private function executeRequest(callable $apiCall)
{
if (! $this->rateLimiter->canMakeRequest($this->config)) {
$retryAfter = $this->rateLimiter->requestAvailableIn($this->config);
$this->log->warning('[Hubspot] Rate limit exceeded, deferring request', [
'team_id' => $this->config->team_id,
'config_id' => $this->config->getId(),
'retry_after' => $retryAfter,
]);
throw new RateLimitException(
'Hubspot rate limit reached for configuration ' . $this->config->getId(),
$retryAfter,
);
}
$this->rateLimiter->incrementRequestCount($this->config);
try {
return $apiCall();
} catch (Throwable $e) {
if ($this->isHubspotRateLimit($e)) {
$retryAfter = $this->parseRetryAfter($e);
$this->log->warning('[Hubspot] Received 429 from API', [
'team_id' => $this->config->team_id,
'config_id' => $this->config->getId(),
'retry_after' => $retryAfter,
'reason' => $e->getMessage(),
]);
throw new RateLimitException('Hubspot returned 429', $retryAfter, $e);
}
throw $e;
}
}
private function isHubspotRateLimit(Throwable $e): bool
{
return method_exists($e, 'getCode') && (int) $e->getCode() === 429;
}
private function parseRetryAfter(Throwable $e): int
{
if (method_exists($e, 'getResponseHeaders')) {
$headers = $e->getResponseHeaders() ?: [];
$value = $headers['Retry-After'] ?? $headers['retry-after'] ?? null;
if (is_array($value)) {
$value = $value[0] ?? null;
}
if (is_numeric($value)) {
return (int) $value;
}
}
return 10;
}
public function getMinimumApiVersion(): string
{
return self::MIN_API_VERSION;
}
public function getInstance(): Factory
{
return new Factory([
'key' => $this->accessToken,
'oauth2' => true,
'base_url' => $this->baseUrl,
]);
}
public function getNewInstance(): Discovery
{
return \HubSpot\Factory::createWithAccessToken($this->accessToken);
}
/**
* Secondly and daily limits for Hubspot API
*
* Product Tier: Free & Starter | Professional & Enterprise | API add-on (any tier)
* Burst: 100/10 seconds | 150/10 seconds | 200/10 seconds
* Daily: 250,000 | 500,000 | 1,000,000
*
* Official documentation states: The search endpoints are rate limited to five requests per second.
* Since with 5 RPS were still hitting secondly rate limits we lowered it to 4
*/
public function getPaginatedData(array $payload, string $type, int $offset = 0): array
{
$total = 0;
$lastId = null;
$rows = [];
foreach ($this->getPaginatedDataGenerator($payload, $type, $offset, $total, $lastId) as $row) {
$rows[] = $row;
}
return ['results' => $rows, 'total' => $total, 'last_record' => $lastId];
}
/**
* @throws HubspotException
* @throws SocialAccountTokenInvalidException
* @throws BadRequest
*/
public function getPaginatedDataGenerator(
array $payload,
string $type,
int $offset = 0,
int &$total = 0,
?string &$lastRecordId = null
): \Generator {
return $this->paginationService->getPaginatedDataGenerator(
$this,
$payload,
$type,
$offset,
$total,
$lastRecordId
);
}
/**
* @throws DealApiException
* @throws CrmException
*/
public function getOpportunityById(string $crmId, array $fields): array
{
try {
$deal = $this->executeRequest(fn () => $this->getNewInstance()->crm()->deals()->basicApi()->getById(
$crmId,
implode(',', $fields),
'companies,contacts'
));
} catch (DealApiException $e) {
$this->log->info('[Hubspot] Failed to fetch opportunity', [
'crm_id' => $crmId,
'reason' => $e->getMessage(),
]);
throw $e;
}
if (! $deal instanceof DealWithAssociations) {
throw new CrmException('Deal not found');
}
return [
'id' => $deal->getId(),
'properties' => $deal->getProperties(),
'associations' => $deal->getAssociations(),
];
}
/**
* Generic batch read method for HubSpot objects
*
* @param string $objectType The object type ('deals', 'companies', 'contacts')
* @param array<string> $crmIds Array of HubSpot object IDs (max 100)
* @param array<string> $fields Array of property names to fetch
*
* @return array<string, array> Array keyed by CRM ID with object data
*/
private function batchReadObjects(string $objectType, array $crmIds, array $fields): array
{
if (empty($crmIds)) {
return [];
}
$this->validateBatchSize($objectType, $crmIds);
$this->ensureValidToken();
try {
$batchConfig = $this->createBatchConfiguration($objectType);
$batchReadRequest = $this->prepareBatchRequest($batchConfig, $crmIds, $fields);
$response = $batchConfig['api']->read($batchReadRequest);
$this->validateApiResponse($response, $objectType);
$results = $this->processApiResults($response);
$this->logBatchResults($objectType, $crmIds, $results);
return $results;
} catch (\Throwable $e) {
$this->handleBatchError($e, $objectType, $crmIds);
}
}
private function validateBatchSize(string $objectType, array $crmIds): void
{
if (count($crmIds) > 100) {
throw new \InvalidArgumentException("Batch size cannot exceed 100 {$objectType}");
}
}
private function createBatchConfiguration(string $objectType): array
{
$configurations = [
'deals' => [
'batchReadRequest' => new \HubSpot\Client\Crm\Deals\Model\BatchReadInputSimplePublicObjectId(),
'inputClass' => \HubSpot\Client\Crm\Deals\Model\SimplePublicObjectId::class,
'api' => $this->getNewInstance()->crm()->deals()->batchApi(),
],
'companies' => [
'batchReadRequest' => new \HubSpot\Client\Crm\Companies\Model\BatchReadInputSimplePublicObjectId(),
'inputClass' => \HubSpot\Client\Crm\Companies\Model\SimplePublicObjectId::class,
'api' => $this->getNewInstance()->crm()->companies()->batchApi(),
],
'contacts' => [
'batchReadRequest' => new \HubSpot\Client\Crm\Contacts\Model\BatchReadInputSimplePublicObjectId(),
'inputClass' => \HubSpot\Client\Crm\Contacts\Model\SimplePublicObjectId::class,
'api' => $this->getNewInstance()->crm()->contacts()->batchApi(),
],
];
if (! isset($configurations[$objectType])) {
throw new \InvalidArgumentException("Unsupported object type: {$objectType}");
}
return $configurations[$objectType];
}
private function prepareBatchRequest(array $batchConfig, array $crmIds, array $fields): object
{
$batchReadRequest = $batchConfig['batchReadRequest'];
$inputClass = $batchConfig['inputClass'];
$inputs = array_map(function ($crmId) use ($inputClass) {
$input = new $inputClass();
$input->setId($crmId);
return $input;
}, $crmIds);
$batchReadRequest->setInputs($inputs);
$batchReadRequest->setProperties($fields);
return $batchReadRequest;
}
private function validateApiResponse($response, string $objectType): void
{
if (! $response) {
throw new CrmException("HubSpot API returned null response for {$objectType} batch read");
}
}
private function processApiResults($response): array
{
$results = [];
$responseResults = $response->getResults();
if ($responseResults) {
foreach ($responseResults as $object) {
if ($object && $object->getId()) {
$results[$object->getId()] = [
'id' => $object->getId(),
'properties' => $object->getProperties() ?: [],
];
}
}
}
return $results;
}
private function logBatchResults(string $objectType, array $crmIds, array $results): void
{
$this->log->info("[HubSpot] Batch fetched {$objectType}", [
'requested_count' => count($crmIds),
'returned_count' => count($results),
'crm_ids' => $crmIds,
]);
}
private function handleBatchError(\Throwable $e, string $objectType, array $crmIds): void
{
$errorMessage = $e->getMessage() ?: 'Unknown error';
$errorTrace = $e->getTraceAsString() ?: 'No trace available';
$this->log->error("[HubSpot] Failed to batch fetch {$objectType}", [
'crm_ids' => $crmIds,
'error' => $errorMessage,
'trace' => $errorTrace,
]);
throw new CrmException("Failed to batch fetch {$objectType}: " . $errorMessage);
}
/**
* Batch read multiple opportunities by their CRM IDs
*
* @param array<string> $crmIds Array of HubSpot deal IDs (max 100)
* @param array<string> $fields Array of property names to fetch
*
* @return array<string, array> Array keyed by CRM ID with opportunity data
*/
public function getOpportunitiesByIds(array $crmIds, array $fields): array
{
return $this->batchReadObjects('deals', $crmIds, $fields);
}
/**
* Batch read multiple companies by their CRM IDs
*
* @param array<string> $crmIds Array of HubSpot company IDs (max 100)
* @param array<string> $fields Array of property names to fetch
*
* @return array<string, array> Array keyed by CRM ID with company data
*/
public function getCompaniesByIds(array $crmIds, array $fields): array
{
return $this->batchReadObjects('companies', $crmIds, $fields);
}
/**
* Batch read multiple contacts by their CRM IDs
*
* @param array<string> $crmIds Array of HubSpot contact IDs (max 100)
* @param array<string> $fields Array of property names to fetch
*
* @return array<string, array> Array keyed by CRM ID with contact data
*/
public function getContactsByIds(array $crmIds, array $fields): array
{
return $this->batchReadObjects('contacts', $crmIds, $fields);
}
/**
* @throws CompanyApiException
* @throws CrmException
*/
public function getAccountById(string $crmId, array $fields): array
{
try {
$company = $this->getNewInstance()->crm()->companies()->basicApi()->getById(
$crmId,
implode(',', $fields),
);
} catch (CompanyApiException $e) {
$this->log->info('[Hubspot] Failed to fetch account', [
'crm_id' => $crmId,
'reason' => $e->getMessage(),
]);
throw $e;
}
if (! $company instanceof CompaniesWithAssociations) {
throw new CrmException('Account not found');
}
return [
'id' => $company->getId(),
'properties' => $company->getProperties(),
];
}
/**
* @throws ContactApiException
* @throws CrmException
*/
public function getContactById(string $crmId, array $fields): array
{
try {
$contact = $this->getNewInstance()->crm()->contacts()->basicApi()->getById(
$crmId,
implode(',', $fields)
);
} catch (ContactApiException $e) {
$this->log->info('[Hubspot] Failed to fetch contact', [
'crm_id' => $crmId,
'reason' => $e->getMessage(),
]);
throw $e;
}
if (! $contact instanceof ContactsWithAssociations) {
throw new CrmException('Contact not found');
}
return [
'id' => $contact->getId(),
'properties' => $contact->getProperties(),
];
}
/**
* This is email search request that Hubspot offers as GET (more generous quota)
*/
public function getContactByEmail(string $email, array $fields = []): array
{
try {
$contact = $this->getNewInstance()->crm()->contacts()->basicApi()->getById(
$email,
implode(',', $fields),
null,
false,
'email'
);
return [
'id' => $contact->getId(),
'properties' => $contact->getProperties(),
];
} catch (ContactApiException $e) {
$this->log->info('[Hubspot] Failed to fetch contact', [
'email' => $email,
'reason' => $e->getMessage(),
]);
return [];
}
}
/**
* @throws CrmException
*/
public function fetchProperty(string $objectType, string $propertyId): Property
{
$result = $this->getNewInstance()->crm()->properties()->coreApi()->getByName($objectType, $propertyId);
if (! $result instanceof Property) {
$this->log->error('[Hubspot] Failed to fetch property', [
'object_type' => $objectType,
'property_id' => $propertyId,
'reason' => $result->getMessage(),
]);
throw new CrmException('Failed to fetch property');
}
return $result;
}
/**
* @return array<CrmFieldOption>
*/
public function fetchPropertyOptions(string $objectType, string $propertyId): array
{
/** @var array<CrmFieldOption> */
return $this->fetchProperty($objectType, $propertyId)->getOptions();
}
/**
* @return array<array{id:string, label:string, deleted:bool}>
*/
public function fetchCallDispositions(): array
{
/** @var Response $response */
$response = $this->getInstance()->engagements()->getCallDispositions();
/**
* @var array<array{
* id:string,
* label:string,
* deleted: bool
* }>
*/
return $response->toArray();
}
/**
* @return array<CrmFieldOption>
*/
public function fetchOpportunityPipelineStages(): array
{
$stages = [];
$apiResponse = $this->getNewInstance()->crm()->pipelines()->pipelinesApi()->getAll('deals');
if ($apiResponse instanceof Error) {
$this->log->error('[Hubspot] Failed to fetch opportunity pipelines', [
'reason' => $apiResponse->getMessage(),
]);
return [];
}
foreach ($apiResponse->getResults() as $pipeline) {
$pipelineStages = array_map(
static function (PipelineStage $stage) {
return [
'id' => $stage->getId(),
'label' => $stage->getLabel(),
];
},
$pipeline->getStages()
);
$stages = array_merge($stages, $pipelineStages);
}
return $stages;
}
public function fetchOpportunityPipelines(): array
{
$pipelines = [];
try {
$apiResponse = $this->makeRequest('/crm/v3/pipelines/deals');
} catch (\Exception $e) {
$this->log->info('[Hubspot] Failed to fetch opportunity pipelines', [
'reason' => $e->getMessage(),
]);
return [];
}
$response = $apiResponse->toArray();
foreach ($response['results'] as $pipeline) {
$pipelines[] = [
'id' => $pipeline['id'],
'label' => $pipeline['label'],
];
}
return $pipelines;
}
/**
* @return array<CrmFieldOption>
*/
public function fetchMeetingOutcomeFieldOptions(Field $field): array
{
return $field->getCrmProviderId() === 'meetingOutcome'
? $this->fetchMeetingOutcomeTypes()
: $this->fetchCallActivityTypes();
}
public function fetchMeetingOutcomeTypes(): array
{
return $this->extractMeetingTypeOptions(
'[URL_WITH_CREDENTIALS] Response $response */
$response = $this->getInstance()
->getClient()
->request('GET', $endpoint);
/**
* @var array<array{
* value: string,
* label: string,
* displayOrder: int
* }> $optionData
*/
$optionData = $response->toArray()['options'] ?? [];
$options = [];
foreach ($optionData as $item) {
$options[] = [
'id' => $item['value'],
'value' => $item['value'],
'label' => $item['label'],
'display_order' => $item['displayOrder'],
];
}
return $options;
}
/**
* @return array<CrmFieldOption>
*/
public function fetchDispositionFieldOptions(): array
{
$options = [];
$dispositions = $this->fetchCallDispositions();
foreach ($dispositions as $disposition) {
if ($disposition['deleted'] !== false) {
continue;
}
$option['value'] = $disposition['id'];
$option['id'] = $disposition['id'];
$option['label'] = $disposition['label'];
$options[] = $option;
}
return $options;
}
/**
* @return array<CrmFieldOption>
*/
public function fetchOpportunityFieldOptions(Field $field): array
{
if ($field->isStageField()) {
return $this->fetchOpportunityPipelineStages();
}
if ($field->isPipelineField()) {
return $this->fetchOpportunityPipelines();
}
return $this->fetchPropertyOptions('deals', $field->getCrmProviderId());
}
/**
* @throws BadRequest
* @throws HubspotException
*/
public function makeRequest(string $endpoint, $method = 'GET', $payload = [], ?string $queryString = null)
{
$endpoint = self::BASE_URL . $endpoint;
if ($method === 'GET') {
$response = $this->getInstance()->getClient()?->request(
method: $method,
endpoint: $endpoint,
query_string: $queryString
);
} else {
$response = $this->getInstance()->getClient()->request($method, $endpoint, [
'json' => ($payload),
]);
}
$max = $response->getHeaderLine('X-HubSpot-RateLimit-Max'); // "110"
$remaining = $response->getHeaderLine('X-HubSpot-RateLimit-Remaining'); // "109"
$interval = $response->getHeaderLine('X-HubSpot-RateLimit-Interval-Milliseconds'); // "10000"
$body = json_decode((string) $response->getBody(), true);
\Illuminate\Support\Facades\Log::channel('custom_channel')->info('$max ' . PHP_EOL . print_r($max, true));
\Illuminate\Support\Facades\Log::channel('custom_channel')->info('$remaining ' . PHP_EOL . print_r($remaining, true));
\Illuminate\Support\Facades\Log::channel('custom_channel')->info('$interval ' . PHP_EOL . print_r($interval, true));
return $response;
}
/**
* @throws BadRequest
* @throws HubspotException
*/
public function createMeeting(array $payload): Response
{
$endpoint = '/crm/v3/objects/meetings';
return $this->makeRequest($endpoint, 'POST', $payload);
}
/**
* @throws BadRequest
* @throws HubspotException
*/
public function updateMeeting(string $meetingId, array $payload): Response
{
$endpoint = '/crm/v3/objects/meetings/' . $meetingId;
return $this->makeRequest($endpoint, 'PATCH', $payload);
}
/**
* @throws \Exception
*/
public function createNote(
string $body,
string $ownerId,
int $timestamp,
string $objectId,
NoteObject $noteObject
): ?string {
try {
$noteInput = new SimplePublicObjectInput([
'properties' => [
'hs_note_body' => $body,
'hubspot_owner_id' => $ownerId,
'hs_timestamp' => $timestamp,
],
]);
// Create note
$note = $this->getNewInstance()->crm()->objects()->basicApi()->create('note', $noteInput);
$this->getNewInstance()->crm()->objects()->associationsApi()->create(
'note',
$note->getId(),
$this->getNoteObject($noteObject),
$objectId,
$this->getNoteAssociationType($noteObject),
);
return $note->getId();
} catch (\Exception $e) {
$this->log->error('[Hubspot] Failed to create note', [
'objectId' => $objectId,
'noteObject' => $noteObject->getObjectType(),
'reason' => $e->getMessage(),
]);
\Sentry::captureException($e);
}
return null;
}
public function updateEngagement(string $objectId, array $engagement, array $metadata): void
{
$this->getInstance()->engagements()->update($objectId, $engagement, $metadata);
}
public function getEngagementData(string $engagementId): array
{
$engagement = $this->getInstance()->engagements()->get($engagementId);
return $engagement->toArray();
}
public function createEngagement(array $engagement, array $associations, array $metadata): Response
{
return $this->getInstance()
->engagements()
->create($engagement, $associations, $metadata);
}
public function isUnauthorizedException(\Exception $e): bool
{
// Check for specific HubSpot API exception types first
if ($e instanceof BadRequest) {
// BadRequest can contain 401 status codes
return $e->getCode() === 401;
}
// Check for HTTP client exceptions with status codes
if ($e instanceof \GuzzleHttp\Exception\RequestException && $e->hasResponse()) {
$response = $e->getResponse();
if ($response !== null) {
return $response->getStatusCode() === 401;
}
}
// Check for Guzzle HTTP exceptions
if ($e instanceof \GuzzleHttp\Exception\ClientException) {
return $e->getCode() === 401;
}
// Fallback to string matching as last resort, but be more specific
$message = strtolower($e->getMessage());
return str_contains($message, '401 unauthorized') ||
str_contains($message, 'http 401') ||
str_contains($message, 'status code 401') ||
(preg_match('/\b401\b/', $message) && str_contains($message, 'unauthorized'));
}
/**
* Validates and refreshes the access token if needed before API requests.
* This ensures long-running processes don't fail due to token expiration.
*
* @throws SocialAccountTokenInvalidException
*/
public function ensureValidToken(): void
{
if ($this->oauthAccount === null) {
return;
}
$newToken = $this->tokenManager->ensureValidToken($this->oauthAccount);
if ($newToken !== null) {
$this->accessToken = $newToken;
}
}
public function getConfig()
{
return $this->config;
}
// returns only active (archived=false)
public function getOwners(): array
{
return $this->getNewInstance()->crm()->owners()->getAll();
}
/**
* @param bool $archived
*
* @return array<Owner>|[]
*/
public function getOwnersArchived(bool $archived = true): array
{
$endpoint = '/crm/v3/owners';
$queryParams = [
'archived' => $archived ? 'true' : 'false',
];
$queryString = http_build_query($queryParams);
$owners = [];
try {
$response = $this->makeRequest(endpoint: $endpoint, queryString: $queryString);
$responseData = $response?->toArray();
foreach ($responseData['results'] as $result) {
try {
$owners[] = Owner::create($result);
} catch (Throwable $e) {
$this->log->error('[HubSpot] Failed to process owner data', [
'result' => $result,
'error' => $e->getMessage(),
]);
continue;
}
}
} catch (Throwable $e) {
$this->log->error('HubSpot] Failed to fetch owners', [
'archived' => $archived,
'error' => $e->getMessage(),
]);
return [];
}
return $owners;
}
public function getMeeting(string $engagementId): ObjectWithAssociations
{
return $this->getNewInstance()->crm()->objects()->basicApi()
->getById('meeting', $engagementId, null, 'contact,company,deal');
}
public function deleteEngagement(string $engagementId): void
{
$this->getInstance()->engagements()->delete((int) $engagementId);
}
public function getAssociationsData(array $ids, string $fromObject, string $toObject): array
{
$associationData = [];
$idChunks = array_chunk($ids, self::ASSOCIATIONS_BATCH_SIZE_LIMIT);
foreach ($idChunks as $idChunk) {
try {
$batchInput = new \HubSpot\Client\Crm\Associations\Model\BatchInputPublicObjectId();
$batchInput->setInputs(array_map(function ($id) {
$publicObjectId = new \HubSpot\Client\Crm\Associations\Model\PublicObjectId();
$publicObjectId->setId($id);
return $publicObjectId;
}, $idChunk));
$associatedObjectsData = $this
->getNewInstance()
->crm()
->associations()
->batchApi()
->read($fromObject, $toObject, $batchInput);
if ($associatedObjectsData instanceof \HubSpot\Client\Crm\Associations\Model\BatchResponsePublicAssociationMulti) {
foreach ($associatedObjectsData->getResults() as $association) {
$from = $association->getFrom()->getId();
$toAssociations = $association->getTo();
if (! empty($toAssociations)) {
$associationData[$from] = array_map(function ($item) {
return $item->getId();
}, $toAssociations);
}
}
}
} catch (\Exception $e) {
$this->log->error('[Hubspot] Failed to fetch associations', [
'from_object' => $fromObject,
'to_object' => $toObject,
'reason' => $e->getMessage(),
]);
}
}
return $associationData;
}
/**
* @throws \Exception
*/
private function getNoteAssociationType(NoteObject $noteObject): string
{
return match($noteObject) {
NoteObject::Opportunity => 'note_to_deal',
NoteObject::Lead, NoteObject::Contact => 'note_to_contact', // or 'note_to_lead' if your portal supports it
NoteObject::Account => 'note_to_company',
NoteObject::Call, NoteObject::Event => throw new \Exception('Not supported'),
};
}
/**
* @throws \Exception
*/
private function getNoteObject(NoteObject $noteObject): string
{
return match($noteObject) {
NoteObject::Opportunity => 'deal',
NoteObject::Lead, NoteObject::Contact => 'contact',
NoteObject::Account => 'company',
NoteObject::Call, NoteObject::Event => throw new \Exception('Not supported'),
};
}
public function addAssociations(string $objectType, string $associationType, array $payload): Response
{
$endpoint = "/crm/v4/associations/$objectType/$associationType/batch/create";
return $this->makeRequest($endpoint, 'POST', $payload);
}
public function removeAssociations(string $objectType, string $associationType, array $payload): Response
{
$endpoint = "/crm/v4/associations/$objectType/$associationType/batch/archive";
return $this->makeRequest($endpoint, 'POST', $payload);
}
}
Project
Project
New File or Directory…
Expand Selected
Collapse All
Options
Hide...
|
NULL
|
NULL
|
NULL
|
NULL
|
|
2685
|
111
|
12
|
2026-05-07T11:35:34.369081+00:00
|
/Users/lukas/.screenpipe/data/data/2026-05-07/1778 /Users/lukas/.screenpipe/data/data/2026-05-07/1778153734369_m1.jpg...
|
PhpStorm
|
faVsco.js – Hubspot/Client.php
|
True
|
NULL
|
monitor_1
|
NULL
|
NULL
|
NULL
|
NULL
|
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
Execute
Explain Plan
Browse Query History
View Parameters
Open Query Execution Settings…
In-Editor Results
Tx: Auto
Cancel Running Statements
Playground
jiminny
Sync Changes
Hide This Notification
Code changed:
Hide
6
1
6
Previous Highlighted Error
Next Highlighted Error
# [PASSWORD_DOTS] HS [PASSWORD_DOTS]
select * from teams where id = 2; # 2
select * from features; # 2
select * from team_features where team_id = 2; # 2
select * from crm_configurations where id = 2; # 2
select * from users where team_id = 2; #
select * from playbooks where team_id = 2; # event 38
select * from playbook_categories where playbook_id = 38; #
SELECT * FROM activities WHERE crm_configuration_id = 2 and crm_provider_id is not null order by id desc;
[URL_WITH_CREDENTIALS] CrmFieldOption array{id:string, label:string, value?:string}
*/
class Client extends BaseClient implements HubspotClientInterface
{
public const string MIN_API_VERSION = '2';
public const string BASE_URL = '[URL_WITH_CREDENTIALS] T
* @param callable(): T $apiCall
* @return T
*
* @throws RateLimitException
*/
private function executeRequest(callable $apiCall)
{
if (! $this->rateLimiter->canMakeRequest($this->config)) {
$retryAfter = $this->rateLimiter->requestAvailableIn($this->config);
$this->log->warning('[Hubspot] Rate limit exceeded, deferring request', [
'team_id' => $this->config->team_id,
'config_id' => $this->config->getId(),
'retry_after' => $retryAfter,
]);
throw new RateLimitException(
'Hubspot rate limit reached for configuration ' . $this->config->getId(),
$retryAfter,
);
}
$this->rateLimiter->incrementRequestCount($this->config);
try {
return $apiCall();
} catch (Throwable $e) {
if ($this->isHubspotRateLimit($e)) {
$retryAfter = $this->parseRetryAfter($e);
$this->log->warning('[Hubspot] Received 429 from API', [
'team_id' => $this->config->team_id,
'config_id' => $this->config->getId(),
'retry_after' => $retryAfter,
'reason' => $e->getMessage(),
]);
throw new RateLimitException('Hubspot returned 429', $retryAfter, $e);
}
throw $e;
}
}
private function isHubspotRateLimit(Throwable $e): bool
{
return method_exists($e, 'getCode') && (int) $e->getCode() === 429;
}
private function parseRetryAfter(Throwable $e): int
{
if (method_exists($e, 'getResponseHeaders')) {
$headers = $e->getResponseHeaders() ?: [];
$value = $headers['Retry-After'] ?? $headers['retry-after'] ?? null;
if (is_array($value)) {
$value = $value[0] ?? null;
}
if (is_numeric($value)) {
return (int) $value;
}
}
return 10;
}
public function getMinimumApiVersion(): string
{
return self::MIN_API_VERSION;
}
public function getInstance(): Factory
{
return new Factory([
'key' => $this->accessToken,
'oauth2' => true,
'base_url' => $this->baseUrl,
]);
}
public function getNewInstance(): Discovery
{
return \HubSpot\Factory::createWithAccessToken($this->accessToken);
}
/**
* Secondly and daily limits for Hubspot API
*
* Product Tier: Free & Starter | Professional & Enterprise | API add-on (any tier)
* Burst: 100/10 seconds | 150/10 seconds | 200/10 seconds
* Daily: 250,000 | 500,000 | 1,000,000
*
* Official documentation states: The search endpoints are rate limited to five requests per second.
* Since with 5 RPS were still hitting secondly rate limits we lowered it to 4
*/
public function getPaginatedData(array $payload, string $type, int $offset = 0): array
{
$total = 0;
$lastId = null;
$rows = [];
foreach ($this->getPaginatedDataGenerator($payload, $type, $offset, $total, $lastId) as $row) {
$rows[] = $row;
}
return ['results' => $rows, 'total' => $total, 'last_record' => $lastId];
}
/**
* @throws HubspotException
* @throws SocialAccountTokenInvalidException
* @throws BadRequest
*/
public function getPaginatedDataGenerator(
array $payload,
string $type,
int $offset = 0,
int &$total = 0,
?string &$lastRecordId = null
): \Generator {
return $this->paginationService->getPaginatedDataGenerator(
$this,
$payload,
$type,
$offset,
$total,
$lastRecordId
);
}
/**
* @throws DealApiException
* @throws CrmException
*/
public function getOpportunityById(string $crmId, array $fields): array
{
try {
$deal = $this->executeRequest(fn () => $this->getNewInstance()->crm()->deals()->basicApi()->getById(
$crmId,
implode(',', $fields),
'companies,contacts'
));
} catch (DealApiException $e) {
$this->log->info('[Hubspot] Failed to fetch opportunity', [
'crm_id' => $crmId,
'reason' => $e->getMessage(),
]);
throw $e;
}
if (! $deal instanceof DealWithAssociations) {
throw new CrmException('Deal not found');
}
return [
'id' => $deal->getId(),
'properties' => $deal->getProperties(),
'associations' => $deal->getAssociations(),
];
}
/**
* Generic batch read method for HubSpot objects
*
* @param string $objectType The object type ('deals', 'companies', 'contacts')
* @param array<string> $crmIds Array of HubSpot object IDs (max 100)
* @param array<string> $fields Array of property names to fetch
*
* @return array<string, array> Array keyed by CRM ID with object data
*/
private function batchReadObjects(string $objectType, array $crmIds, array $fields): array
{
if (empty($crmIds)) {
return [];
}
$this->validateBatchSize($objectType, $crmIds);
$this->ensureValidToken();
try {
$batchConfig = $this->createBatchConfiguration($objectType);
$batchReadRequest = $this->prepareBatchRequest($batchConfig, $crmIds, $fields);
$response = $batchConfig['api']->read($batchReadRequest);
$this->validateApiResponse($response, $objectType);
$results = $this->processApiResults($response);
$this->logBatchResults($objectType, $crmIds, $results);
return $results;
} catch (\Throwable $e) {
$this->handleBatchError($e, $objectType, $crmIds);
}
}
private function validateBatchSize(string $objectType, array $crmIds): void
{
if (count($crmIds) > 100) {
throw new \InvalidArgumentException("Batch size cannot exceed 100 {$objectType}");
}
}
private function createBatchConfiguration(string $objectType): array
{
$configurations = [
'deals' => [
'batchReadRequest' => new \HubSpot\Client\Crm\Deals\Model\BatchReadInputSimplePublicObjectId(),
'inputClass' => \HubSpot\Client\Crm\Deals\Model\SimplePublicObjectId::class,
'api' => $this->getNewInstance()->crm()->deals()->batchApi(),
],
'companies' => [
'batchReadRequest' => new \HubSpot\Client\Crm\Companies\Model\BatchReadInputSimplePublicObjectId(),
'inputClass' => \HubSpot\Client\Crm\Companies\Model\SimplePublicObjectId::class,
'api' => $this->getNewInstance()->crm()->companies()->batchApi(),
],
'contacts' => [
'batchReadRequest' => new \HubSpot\Client\Crm\Contacts\Model\BatchReadInputSimplePublicObjectId(),
'inputClass' => \HubSpot\Client\Crm\Contacts\Model\SimplePublicObjectId::class,
'api' => $this->getNewInstance()->crm()->contacts()->batchApi(),
],
];
if (! isset($configurations[$objectType])) {
throw new \InvalidArgumentException("Unsupported object type: {$objectType}");
}
return $configurations[$objectType];
}
private function prepareBatchRequest(array $batchConfig, array $crmIds, array $fields): object
{
$batchReadRequest = $batchConfig['batchReadRequest'];
$inputClass = $batchConfig['inputClass'];
$inputs = array_map(function ($crmId) use ($inputClass) {
$input = new $inputClass();
$input->setId($crmId);
return $input;
}, $crmIds);
$batchReadRequest->setInputs($inputs);
$batchReadRequest->setProperties($fields);
return $batchReadRequest;
}
private function validateApiResponse($response, string $objectType): void
{
if (! $response) {
throw new CrmException("HubSpot API returned null response for {$objectType} batch read");
}
}
private function processApiResults($response): array
{
$results = [];
$responseResults = $response->getResults();
if ($responseResults) {
foreach ($responseResults as $object) {
if ($object && $object->getId()) {
$results[$object->getId()] = [
'id' => $object->getId(),
'properties' => $object->getProperties() ?: [],
];
}
}
}
return $results;
}
private function logBatchResults(string $objectType, array $crmIds, array $results): void
{
$this->log->info("[HubSpot] Batch fetched {$objectType}", [
'requested_count' => count($crmIds),
'returned_count' => count($results),
'crm_ids' => $crmIds,
]);
}
private function handleBatchError(\Throwable $e, string $objectType, array $crmIds): void
{
$errorMessage = $e->getMessage() ?: 'Unknown error';
$errorTrace = $e->getTraceAsString() ?: 'No trace available';
$this->log->error("[HubSpot] Failed to batch fetch {$objectType}", [
'crm_ids' => $crmIds,
'error' => $errorMessage,
'trace' => $errorTrace,
]);
throw new CrmException("Failed to batch fetch {$objectType}: " . $errorMessage);
}
/**
* Batch read multiple opportunities by their CRM IDs
*
* @param array<string> $crmIds Array of HubSpot deal IDs (max 100)
* @param array<string> $fields Array of property names to fetch
*
* @return array<string, array> Array keyed by CRM ID with opportunity data
*/
public function getOpportunitiesByIds(array $crmIds, array $fields): array
{
return $this->batchReadObjects('deals', $crmIds, $fields);
}
/**
* Batch read multiple companies by their CRM IDs
*
* @param array<string> $crmIds Array of HubSpot company IDs (max 100)
* @param array<string> $fields Array of property names to fetch
*
* @return array<string, array> Array keyed by CRM ID with company data
*/
public function getCompaniesByIds(array $crmIds, array $fields): array
{
return $this->batchReadObjects('companies', $crmIds, $fields);
}
/**
* Batch read multiple contacts by their CRM IDs
*
* @param array<string> $crmIds Array of HubSpot contact IDs (max 100)
* @param array<string> $fields Array of property names to fetch
*
* @return array<string, array> Array keyed by CRM ID with contact data
*/
public function getContactsByIds(array $crmIds, array $fields): array
{
return $this->batchReadObjects('contacts', $crmIds, $fields);
}
/**
* @throws CompanyApiException
* @throws CrmException
*/
public function getAccountById(string $crmId, array $fields): array
{
try {
$company = $this->getNewInstance()->crm()->companies()->basicApi()->getById(
$crmId,
implode(',', $fields),
);
} catch (CompanyApiException $e) {
$this->log->info('[Hubspot] Failed to fetch account', [
'crm_id' => $crmId,
'reason' => $e->getMessage(),
]);
throw $e;
}
if (! $company instanceof CompaniesWithAssociations) {
throw new CrmException('Account not found');
}
return [
'id' => $company->getId(),
'properties' => $company->getProperties(),
];
}
/**
* @throws ContactApiException
* @throws CrmException
*/
public function getContactById(string $crmId, array $fields): array
{
try {
$contact = $this->getNewInstance()->crm()->contacts()->basicApi()->getById(
$crmId,
implode(',', $fields)
);
} catch (ContactApiException $e) {
$this->log->info('[Hubspot] Failed to fetch contact', [
'crm_id' => $crmId,
'reason' => $e->getMessage(),
]);
throw $e;
}
if (! $contact instanceof ContactsWithAssociations) {
throw new CrmException('Contact not found');
}
return [
'id' => $contact->getId(),
'properties' => $contact->getProperties(),
];
}
/**
* This is email search request that Hubspot offers as GET (more generous quota)
*/
public function getContactByEmail(string $email, array $fields = []): array
{
try {
$contact = $this->getNewInstance()->crm()->contacts()->basicApi()->getById(
$email,
implode(',', $fields),
null,
false,
'email'
);
return [
'id' => $contact->getId(),
'properties' => $contact->getProperties(),
];
} catch (ContactApiException $e) {
$this->log->info('[Hubspot] Failed to fetch contact', [
'email' => $email,
'reason' => $e->getMessage(),
]);
return [];
}
}
/**
* @throws CrmException
*/
public function fetchProperty(string $objectType, string $propertyId): Property
{
$result = $this->getNewInstance()->crm()->properties()->coreApi()->getByName($objectType, $propertyId);
if (! $result instanceof Property) {
$this->log->error('[Hubspot] Failed to fetch property', [
'object_type' => $objectType,
'property_id' => $propertyId,
'reason' => $result->getMessage(),
]);
throw new CrmException('Failed to fetch property');
}
return $result;
}
/**
* @return array<CrmFieldOption>
*/
public function fetchPropertyOptions(string $objectType, string $propertyId): array
{
/** @var array<CrmFieldOption> */
return $this->fetchProperty($objectType, $propertyId)->getOptions();
}
/**
* @return array<array{id:string, label:string, deleted:bool}>
*/
public function fetchCallDispositions(): array
{
/** @var Response $response */
$response = $this->getInstance()->engagements()->getCallDispositions();
/**
* @var array<array{
* id:string,
* label:string,
* deleted: bool
* }>
*/
return $response->toArray();
}
/**
* @return array<CrmFieldOption>
*/
public function fetchOpportunityPipelineStages(): array
{
$stages = [];
$apiResponse = $this->getNewInstance()->crm()->pipelines()->pipelinesApi()->getAll('deals');
if ($apiResponse instanceof Error) {
$this->log->error('[Hubspot] Failed to fetch opportunity pipelines', [
'reason' => $apiResponse->getMessage(),
]);
return [];
}
foreach ($apiResponse->getResults() as $pipeline) {
$pipelineStages = array_map(
static function (PipelineStage $stage) {
return [
'id' => $stage->getId(),
'label' => $stage->getLabel(),
];
},
$pipeline->getStages()
);
$stages = array_merge($stages, $pipelineStages);
}
return $stages;
}
public function fetchOpportunityPipelines(): array
{
$pipelines = [];
try {
$apiResponse = $this->makeRequest('/crm/v3/pipelines/deals');
} catch (\Exception $e) {
$this->log->info('[Hubspot] Failed to fetch opportunity pipelines', [
'reason' => $e->getMessage(),
]);
return [];
}
$response = $apiResponse->toArray();
foreach ($response['results'] as $pipeline) {
$pipelines[] = [
'id' => $pipeline['id'],
'label' => $pipeline['label'],
];
}
return $pipelines;
}
/**
* @return array<CrmFieldOption>
*/
public function fetchMeetingOutcomeFieldOptions(Field $field): array
{
return $field->getCrmProviderId() === 'meetingOutcome'
? $this->fetchMeetingOutcomeTypes()
: $this->fetchCallActivityTypes();
}
public function fetchMeetingOutcomeTypes(): array
{
return $this->extractMeetingTypeOptions(
'[URL_WITH_CREDENTIALS] Response $response */
$response = $this->getInstance()
->getClient()
->request('GET', $endpoint);
/**
* @var array<array{
* value: string,
* label: string,
* displayOrder: int
* }> $optionData
*/
$optionData = $response->toArray()['options'] ?? [];
$options = [];
foreach ($optionData as $item) {
$options[] = [
'id' => $item['value'],
'value' => $item['value'],
'label' => $item['label'],
'display_order' => $item['displayOrder'],
];
}
return $options;
}
/**
* @return array<CrmFieldOption>
*/
public function fetchDispositionFieldOptions(): array
{
$options = [];
$dispositions = $this->fetchCallDispositions();
foreach ($dispositions as $disposition) {
if ($disposition['deleted'] !== false) {
continue;
}
$option['value'] = $disposition['id'];
$option['id'] = $disposition['id'];
$option['label'] = $disposition['label'];
$options[] = $option;
}
return $options;
}
/**
* @return array<CrmFieldOption>
*/
public function fetchOpportunityFieldOptions(Field $field): array
{
if ($field->isStageField()) {
return $this->fetchOpportunityPipelineStages();
}
if ($field->isPipelineField()) {
return $this->fetchOpportunityPipelines();
}
return $this->fetchPropertyOptions('deals', $field->getCrmProviderId());
}
/**
* @throws BadRequest
* @throws HubspotException
*/
public function makeRequest(string $endpoint, $method = 'GET', $payload = [], ?string $queryString = null)
{
$endpoint = self::BASE_URL . $endpoint;
if ($method === 'GET') {
$response = $this->getInstance()->getClient()?->request(
method: $method,
endpoint: $endpoint,
query_string: $queryString
);
} else {
$response = $this->getInstance()->getClient()->request($method, $endpoint, [
'json' => ($payload),
]);
}
$max = $response->getHeaderLine('X-HubSpot-RateLimit-Max'); // "110"
$remaining = $response->getHeaderLine('X-HubSpot-RateLimit-Remaining'); // "109"
$interval = $response->getHeaderLine('X-HubSpot-RateLimit-Interval-Milliseconds'); // "10000"
$body = json_decode((string) $response->getBody(), true);
\Illuminate\Support\Facades\Log::channel('custom_channel')->info('$max ' . PHP_EOL . print_r($max, true));
\Illuminate\Support\Facades\Log::channel('custom_channel')->info('$remaining ' . PHP_EOL . print_r($remaining, true));
\Illuminate\Support\Facades\Log::channel('custom_channel')->info('$interval ' . PHP_EOL . print_r($interval, true));
\Illuminate\Support\Facades\Log::channel('custom_channel')->info('$interval ' . PHP_EOL . print_r($interval, true));
return $response;
}
/**
* @throws BadRequest
* @throws HubspotException
*/
public function createMeeting(array $payload): Response
{
$endpoint = '/crm/v3/objects/meetings';
return $this->makeRequest($endpoint, 'POST', $payload);
}
/**
* @throws BadRequest
* @throws HubspotException
*/
public function updateMeeting(string $meetingId, array $payload): Response
{
$endpoint = '/crm/v3/objects/meetings/' . $meetingId;
return $this->makeRequest($endpoint, 'PATCH', $payload);
}
/**
* @throws \Exception
*/
public function createNote(
string $body,
string $ownerId,
int $timestamp,
string $objectId,
NoteObject $noteObject
): ?string {
try {
$noteInput = new SimplePublicObjectInput([
'properties' => [
'hs_note_body' => $body,
'hubspot_owner_id' => $ownerId,
'hs_timestamp' => $timestamp,
],
]);
// Create note
$note = $this->getNewInstance()->crm()->objects()->basicApi()->create('note', $noteInput);
$this->getNewInstance()->crm()->objects()->associationsApi()->create(
'note',
$note->getId(),
$this->getNoteObject($noteObject),
$objectId,
$this->getNoteAssociationType($noteObject),
);
return $note->getId();
} catch (\Exception $e) {
$this->log->error('[Hubspot] Failed to create note', [
'objectId' => $objectId,
'noteObject' => $noteObject->getObjectType(),
'reason' => $e->getMessage(),
]);
\Sentry::captureException($e);
}
return null;
}
public function updateEngagement(string $objectId, array $engagement, array $metadata): void
{
$this->getInstance()->engagements()->update($objectId, $engagement, $metadata);
}
public function getEngagementData(string $engagementId): array
{
$engagement = $this->getInstance()->engagements()->get($engagementId);
return $engagement->toArray();
}
public function createEngagement(array $engagement, array $associations, array $metadata): Response
{
return $this->getInstance()
->engagements()
->create($engagement, $associations, $metadata);
}
public function isUnauthorizedException(\Exception $e): bool
{
// Check for specific HubSpot API exception types first
if ($e instanceof BadRequest) {
// BadRequest can contain 401 status codes
return $e->getCode() === 401;
}
// Check for HTTP client exceptions with status codes
if ($e instanceof \GuzzleHttp\Exception\RequestException && $e->hasResponse()) {
$response = $e->getResponse();
if ($response !== null) {
return $response->getStatusCode() === 401;
}
}
// Check for Guzzle HTTP exceptions
if ($e instanceof \GuzzleHttp\Exception\ClientException) {
return $e->getCode() === 401;
}
// Fallback to string matching as last resort, but be more specific
$message = strtolower($e->getMessage());
return str_contains($message, '401 unauthorized') ||
str_contains($message, 'http 401') ||
str_contains($message, 'status code 401') ||
(preg_match('/\b401\b/', $message) && str_contains($message, 'unauthorized'));
}
/**
* Validates and refreshes the access token if needed before API requests.
* This ensures long-running processes don't fail due to token expiration.
*
* @throws SocialAccountTokenInvalidException
*/
public function ensureValidToken(): void
{
if ($this->oauthAccount === null) {
return;
}
$newToken = $this->tokenManager->ensureValidToken($this->oauthAccount);
if ($newToken !== null) {
$this->accessToken = $newToken;
}
}
public function getConfig()
{
return $this->config;
}
// returns only active (archived=false)
public function getOwners(): array
{
return $this->getNewInstance()->crm()->owners()->getAll();
}
/**
* @param bool $archived
*
* @return array<Owner>|[]
*/
public function getOwnersArchived(bool $archived = true): array
{
$endpoint = '/crm/v3/owners';
$queryParams = [
'archived' => $archived ? 'true' : 'false',
];
$queryString = http_build_query($queryParams);
$owners = [];
try {
$response = $this->makeRequest(endpoint: $endpoint, queryString: $queryString);
$responseData = $response?->toArray();
foreach ($responseData['results'] as $result) {
try {
$owners[] = Owner::create($result);
} catch (Throwable $e) {
$this->log->error('[HubSpot] Failed to process owner data', [
'result' => $result,
'error' => $e->getMessage(),
]);
continue;
}
}
} catch (Throwable $e) {
$this->log->error('HubSpot] Failed to fetch owners', [
'archived' => $archived,
'error' => $e->getMessage(),
]);
return [];
}
return $owners;
}
public function getMeeting(string $engagementId): ObjectWithAssociations
{
return $this->getNewInstance()->crm()->objects()->basicApi()
->getById('meeting', $engagementId, null, 'contact,company,deal');
}
public function deleteEngagement(string $engagementId): void
{
$this->getInstance()->engagements()->delete((int) $engagementId);
}
public function getAssociationsData(array $ids, string $fromObject, string $toObject): array
{
$associationData = [];
$idChunks = array_chunk($ids, self::ASSOCIATIONS_BATCH_SIZE_LIMIT);
foreach ($idChunks as $idChunk) {
try {
$batchInput = new \HubSpot\Client\Crm\Associations\Model\BatchInputPublicObjectId();
$batchInput->setInputs(array_map(function ($id) {
$publicObjectId = new \HubSpot\Client\Crm\Associations\Model\PublicObjectId();
$publicObjectId->setId($id);
return $publicObjectId;
}, $idChunk));
$associatedObjectsData = $this
->getNewInstance()
->crm()
->associations()
->batchApi()
->read($fromObject, $toObject, $batchInput);
if ($associatedObjectsData instanceof \HubSpot\Client\Crm\Associations\Model\BatchResponsePublicAssociationMulti) {
foreach ($associatedObjectsData->getResults() as $association) {
$from = $association->getFrom()->getId();
$toAssociations = $association->getTo();
if (! empty($toAssociations)) {
$associationData[$from] = array_map(function ($item) {
return $item->getId();
}, $toAssociations);
}
}
}
} catch (\Exception $e) {
$this->log->error('[Hubspot] Failed to fetch associations', [
'from_object' => $fromObject,
'to_object' => $toObject,
'reason' => $e->getMessage(),
]);
}
}
return $associationData;
}
/**
* @throws \Exception
*/
private function getNoteAssociationType(NoteObject $noteObject): string
{
return match($noteObject) {
NoteObject::Opportunity => 'note_to_deal',
NoteObject::Lead, NoteObject::Contact => 'note_to_contact', // or 'note_to_lead' if your portal supports it
NoteObject::Account => 'note_to_company',
NoteObject::Call, NoteObject::Event => throw new \Exception('Not supported'),
};
}
/**
* @throws \Exception
*/
private function getNoteObject(NoteObject $noteObject): string
{
return match($noteObject) {
NoteObject::Opportunity => 'deal',
NoteObject::Lead, NoteObject::Contact => 'contact',
NoteObject::Account => 'company',
NoteObject::Call, NoteObject::Event => throw new \Exception('Not supported'),
};
}
public function addAssociations(string $objectType, string $associationType, array $payload): Response
{
$endpoint = "/crm/v4/associations/$objectType/$associationType/batch/create";
return $this->makeRequest($endpoint, 'POST', $payload);
}
public function removeAssociations(string $objectType, string $associationType, array $payload): Response
{
$endpoint = "/crm/v4/associations/$objectType/$associationType/batch/archive";
return $this->makeRequest($endpoint, 'POST', $payload);
}
}
Project
Project
New File or Directory…
Expand Selected
Collapse All
Options
Hide...
|
[{"role":"AXButton","text" [{"role":"AXButton","text":"Project: faVsco.js, menu","depth":5,"on_screen":true,"help_text":"~/jiminny/app","role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"master, menu","depth":5,"on_screen":true,"help_text":"Git Branch: master","role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Start Listening for PHP Debug Connections","depth":5,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"AskJiminnyReportActivityServiceTest","depth":6,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Run 'AskJiminnyReportActivityServiceTest'","depth":6,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Debug 'AskJiminnyReportActivityServiceTest'","depth":6,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"More Actions","depth":6,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"JetBrains AI","depth":5,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Search Everywhere","depth":5,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"IDE and Project Settings","depth":5,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Execute","depth":4,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Explain Plan","depth":4,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Browse Query History","depth":4,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"View Parameters","depth":4,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Open Query Execution Settings…","depth":4,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"In-Editor Results","depth":4,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Tx: Auto","depth":4,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Cancel Running Statements","depth":4,"on_screen":true,"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Playground","depth":4,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"jiminny","depth":4,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Sync Changes","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.018055556,"height":0.026666667},"on_screen":false,"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Hide This Notification","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.018055556,"height":0.026666667},"on_screen":false,"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Code changed:","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.088194445,"height":0.027777778},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Hide","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.018055556,"height":0.026666667},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"6","depth":4,"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"1","depth":4,"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"6","depth":4,"on_screen":true,"role_description":"text"},{"role":"AXButton","text":"Previous Highlighted Error","depth":4,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Next Highlighted Error","depth":4,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXTextArea","text":"# **************************** HS **************************************\n\nselect * from teams where id = 2; # 2\nselect * from features; # 2\nselect * from team_features where team_id = 2; # 2\nselect * from crm_configurations where id = 2; # 2\nselect * from users where team_id = 2; #\nselect * from playbooks where team_id = 2; # event 38\nselect * from playbook_categories where playbook_id = 38; #\n\nSELECT * FROM activities WHERE crm_configuration_id = 2 and crm_provider_id is not null order by id desc;\nhttps://app.hubspot.com/contacts/4392066/deal/16964514951/?engagement=96069102624\n https://app.staging.jiminny.com/playback/d5df34dc-bd66-4ff5-a7b3-8d3be30322a0\n\nSELECT * FROM activities WHERE uuid_to_bin('04fdcd0d-818f-4c53-92dc-6f18bc753ffd') = uuid;\n# 609126 softphone tr. 11241\n\nSELECT * FROM activities WHERE uuid_to_bin('6521bfcd-5a30-46e5-9f74-5440fd48befd') = uuid;\n# 608874 conference tr. 11226 crmId: 103422236596\n\nselect * from ai_prompts where transcription_id IN (11241, 11226);\nselect * from activity_summary_logs where activity_id = 608874;\n\nselect * from sidekick_settings;\nselect * from default_activity_types;\n\nselect * from crm_field_data where activity_id = 1223;\n\nselect * from crm_layouts where crm_configuration_id = 2;\nSELECT * FROM crm_layout_entities WHERE crm_layout_id IN (554);\nselect * from crm_fields where crm_configuration_id = 11 and object_type = 'event';\nSELECT * FROM crm_field_values WHERE crm_field_id IN (1455,1450);\n\nSELECT * FROM crm_field_data WHERE crm_layout_entity_id = 971;\nSELECT * FROM crm_field_data WHERE crm_layout_entity_id IN (6494,6495,6496,6497,6498,6499);\n\nSELECT\n CONCAT(u.id, CASE WHEN u.id = t.owner_id THEN ' (owner)' ELSE '' END) AS user_id,\n u.email,\n sa.*,\n t.owner_id FROM social_accounts sa\nJOIN users u\n on u.id = sa.sociable_id\nJOIN teams t on t.id = u.team_id\nWHERE u.team_id = 2 and sa.provider = 'hubspot';\n\nselect * from social_accounts where id = 1499;\n\nselect * from opportunities where team_id = 2\nand crm_provider_id IN ('51317301383');\n\nselect * from contacts where id = 85;\n\nselect * from opportunities where team_id = 2 order by id desc;\nselect * from opportunities where team_id = 2 and crm_provider_id = '51317301383'; # 5112\nselect * from opportunities where team_id = 2 and crm_provider_id = '55976759904'; # 5112\nselect * from opportunity_contacts where opportunity_id = 5117;\nselect * from crm_field_data where object_id = 1365;\nSELECT * FROM crm_fields WHERE id IN (1405, 1407, 1972, 2128);\n\nselect * from features;\nselect * from team_features where team_id IN (1);\nselect * from team_features where feature_id IN (36);\n\nSHOW CREATE TABLE opportunity_contacts;\nSELECT * FROM opportunity_contacts WHERE crm_provider_id = '111751';\n\n# $slug = 'HUBSPOT_WEBHOOK_SYNC';\n# $team = Jiminny\\Models\\Team::find(2);\n# $feature = Feature::query()->where('slug', $slug)->first();\n# TeamFeature::query()->create(['feature_id' => $feature->getId(),'team_id' => $team->getId()]);\n\n# hubspot_webhook_metrics\n\nselect * from opportunities where team_id = 2 and crm_provider_id IN ('374720564','14527423589','49908861993','50435771779'); # 1365\nSELECT * FROM opportunity_contacts WHERE opportunity_id = '414';\nSELECT * FROM opportunity_contacts WHERE crm_provider_id = '131501';\nselect * from contacts where id in (414, 464);\n\nselect * from activities where crm_configuration_id = 2;\n\nselect settings from crm_configurations where id = 11;\n\nselect * from teams; # 1, 2\nselect * from users;\nselect * from crm_configurations where id = 39;\nselect * from team_features where team_id = 2;\nselect * from features;\n# SELECT * FROM opportunities WHERE crm_configuration_id = 2\n# order by id desc;\n# and crm_provider_id = '49908861993';\n\n\nselect * from activity_providers where id IN (443, 202, 203, 227);\n\nselect * from activity_imports where id = 795889;\n\nselect c.id, c.provider, c.settings, t.* from teams t join crm_configurations c on t.id = c.team_id\nwhere c.provider = 'hubspot';\n\nselect * from crm_configurations crm JOIN teams t on crm.team_id = t.id\nwhere provider = 'hubspot';\nSELECT * FROM teams WHERE id = 31;\nSELECT * FROM users WHERE id = 257;\nSELECT * FROM opportunities WHERE team_id = 2;\n\nselect * from opportunity_contacts where opportunity_id = 5124;\nselect * from contacts where id IN (3850,3853,3851,4073,4140,4155,4480,4530,4623,5986,513,687,1806,1523,3613)\n\nselect * from activities where crm_configuration_id = 13;\n\nSELECT * FROM activities WHERE uuid_to_bin('826619ce-ec8e-4e59-8467-a01f5f6ad71e') = uuid; # 418141\n\n\nselect id, team_id, crm_provider_id from crm_configurations where provider = 'hubspot' and crm_provider_id IS NOT NULL;\nSELECT * FROM accounts WHERE team_id = 2 and crm_provider_id = '1212213464' order by id desc;\nSELECT * FROM contacts WHERE team_id = 2 and account_id = 5189 order by id desc;\nSELECT * FROM contacts WHERE team_id = 2 order by id desc;\nselect * from opportunity_contacts where contact_id = 6223;\nSELECT * FROM opportunities WHERE team_id = 2 and account_id = 5189 order by id desc;\n\nselect * from crm_profiles where crm_configuration_id = 2;\n\nselect * from activities where account_id = 46;","depth":4,"on_screen":true,"value":"# **************************** HS **************************************\n\nselect * from teams where id = 2; # 2\nselect * from features; # 2\nselect * from team_features where team_id = 2; # 2\nselect * from crm_configurations where id = 2; # 2\nselect * from users where team_id = 2; #\nselect * from playbooks where team_id = 2; # event 38\nselect * from playbook_categories where playbook_id = 38; #\n\nSELECT * FROM activities WHERE crm_configuration_id = 2 and crm_provider_id is not null order by id desc;\nhttps://app.hubspot.com/contacts/4392066/deal/16964514951/?engagement=96069102624\n https://app.staging.jiminny.com/playback/d5df34dc-bd66-4ff5-a7b3-8d3be30322a0\n\nSELECT * FROM activities WHERE uuid_to_bin('04fdcd0d-818f-4c53-92dc-6f18bc753ffd') = uuid;\n# 609126 softphone tr. 11241\n\nSELECT * FROM activities WHERE uuid_to_bin('6521bfcd-5a30-46e5-9f74-5440fd48befd') = uuid;\n# 608874 conference tr. 11226 crmId: 103422236596\n\nselect * from ai_prompts where transcription_id IN (11241, 11226);\nselect * from activity_summary_logs where activity_id = 608874;\n\nselect * from sidekick_settings;\nselect * from default_activity_types;\n\nselect * from crm_field_data where activity_id = 1223;\n\nselect * from crm_layouts where crm_configuration_id = 2;\nSELECT * FROM crm_layout_entities WHERE crm_layout_id IN (554);\nselect * from crm_fields where crm_configuration_id = 11 and object_type = 'event';\nSELECT * FROM crm_field_values WHERE crm_field_id IN (1455,1450);\n\nSELECT * FROM crm_field_data WHERE crm_layout_entity_id = 971;\nSELECT * FROM crm_field_data WHERE crm_layout_entity_id IN (6494,6495,6496,6497,6498,6499);\n\nSELECT\n CONCAT(u.id, CASE WHEN u.id = t.owner_id THEN ' (owner)' ELSE '' END) AS user_id,\n u.email,\n sa.*,\n t.owner_id FROM social_accounts sa\nJOIN users u\n on u.id = sa.sociable_id\nJOIN teams t on t.id = u.team_id\nWHERE u.team_id = 2 and sa.provider = 'hubspot';\n\nselect * from social_accounts where id = 1499;\n\nselect * from opportunities where team_id = 2\nand crm_provider_id IN ('51317301383');\n\nselect * from contacts where id = 85;\n\nselect * from opportunities where team_id = 2 order by id desc;\nselect * from opportunities where team_id = 2 and crm_provider_id = '51317301383'; # 5112\nselect * from opportunities where team_id = 2 and crm_provider_id = '55976759904'; # 5112\nselect * from opportunity_contacts where opportunity_id = 5117;\nselect * from crm_field_data where object_id = 1365;\nSELECT * FROM crm_fields WHERE id IN (1405, 1407, 1972, 2128);\n\nselect * from features;\nselect * from team_features where team_id IN (1);\nselect * from team_features where feature_id IN (36);\n\nSHOW CREATE TABLE opportunity_contacts;\nSELECT * FROM opportunity_contacts WHERE crm_provider_id = '111751';\n\n# $slug = 'HUBSPOT_WEBHOOK_SYNC';\n# $team = Jiminny\\Models\\Team::find(2);\n# $feature = Feature::query()->where('slug', $slug)->first();\n# TeamFeature::query()->create(['feature_id' => $feature->getId(),'team_id' => $team->getId()]);\n\n# hubspot_webhook_metrics\n\nselect * from opportunities where team_id = 2 and crm_provider_id IN ('374720564','14527423589','49908861993','50435771779'); # 1365\nSELECT * FROM opportunity_contacts WHERE opportunity_id = '414';\nSELECT * FROM opportunity_contacts WHERE crm_provider_id = '131501';\nselect * from contacts where id in (414, 464);\n\nselect * from activities where crm_configuration_id = 2;\n\nselect settings from crm_configurations where id = 11;\n\nselect * from teams; # 1, 2\nselect * from users;\nselect * from crm_configurations where id = 39;\nselect * from team_features where team_id = 2;\nselect * from features;\n# SELECT * FROM opportunities WHERE crm_configuration_id = 2\n# order by id desc;\n# and crm_provider_id = '49908861993';\n\n\nselect * from activity_providers where id IN (443, 202, 203, 227);\n\nselect * from activity_imports where id = 795889;\n\nselect c.id, c.provider, c.settings, t.* from teams t join crm_configurations c on t.id = c.team_id\nwhere c.provider = 'hubspot';\n\nselect * from crm_configurations crm JOIN teams t on crm.team_id = t.id\nwhere provider = 'hubspot';\nSELECT * FROM teams WHERE id = 31;\nSELECT * FROM users WHERE id = 257;\nSELECT * FROM opportunities WHERE team_id = 2;\n\nselect * from opportunity_contacts where opportunity_id = 5124;\nselect * from contacts where id IN (3850,3853,3851,4073,4140,4155,4480,4530,4623,5986,513,687,1806,1523,3613)\n\nselect * from activities where crm_configuration_id = 13;\n\nSELECT * FROM activities WHERE uuid_to_bin('826619ce-ec8e-4e59-8467-a01f5f6ad71e') = uuid; # 418141\n\n\nselect id, team_id, crm_provider_id from crm_configurations where provider = 'hubspot' and crm_provider_id IS NOT NULL;\nSELECT * FROM accounts WHERE team_id = 2 and crm_provider_id = '1212213464' order by id desc;\nSELECT * FROM contacts WHERE team_id = 2 and account_id = 5189 order by id desc;\nSELECT * FROM contacts WHERE team_id = 2 order by id desc;\nselect * from opportunity_contacts where contact_id = 6223;\nSELECT * FROM opportunities WHERE team_id = 2 and account_id = 5189 order by id desc;\n\nselect * from crm_profiles where crm_configuration_id = 2;\n\nselect * from activities where account_id = 46;","role_description":"text entry area","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Sync Changes","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.018055556,"height":0.026666667},"on_screen":false,"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Hide This Notification","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.018055556,"height":0.026666667},"on_screen":false,"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Code changed:","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.088194445,"height":0.027777778},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Hide","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.018055556,"height":0.026666667},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"2","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.016666668,"height":0.02111111},"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"60","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.021527778,"height":0.02111111},"on_screen":false,"role_description":"text"},{"role":"AXButton","text":"Previous Highlighted Error","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.015277778,"height":0.025555555},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Next Highlighted Error","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.014583333,"height":0.025555555},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXTextArea","text":"<?php\n\ndeclare(strict_types=1);\n\nnamespace Jiminny\\Services\\Crm\\Hubspot;\n\nuse HubSpot\\Client\\Crm\\Deals\\ApiException as DealApiException;\nuse HubSpot\\Client\\Crm\\Contacts\\ApiException as ContactApiException;\nuse HubSpot\\Client\\Crm\\Companies\\ApiException as CompanyApiException;\nuse HubSpot\\Client\\Crm\\Contacts\\Model\\SimplePublicObjectWithAssociations as ContactsWithAssociations;\nuse HubSpot\\Client\\Crm\\Companies\\Model\\SimplePublicObjectWithAssociations as CompaniesWithAssociations;\nuse HubSpot\\Client\\Crm\\Deals\\Model\\SimplePublicObjectWithAssociations as DealWithAssociations;\nuse HubSpot\\Client\\Crm\\Objects\\Model\\SimplePublicObjectInput;\nuse HubSpot\\Client\\Crm\\Objects\\Model\\SimplePublicObjectWithAssociations as ObjectWithAssociations;\nuse HubSpot\\Client\\Crm\\Pipelines\\Model\\Error;\nuse HubSpot\\Client\\Crm\\Pipelines\\Model\\PipelineStage;\nuse HubSpot\\Client\\Crm\\Properties\\Model\\Property;\nuse HubSpot\\Discovery\\Discovery;\nuse Jiminny\\Component\\Utility\\Service\\ProviderRateLimiter;\nuse Jiminny\\Exceptions\\CrmException;\nuse Jiminny\\Exceptions\\RateLimitException;\nuse Jiminny\\Exceptions\\SocialAccountTokenInvalidException;\nuse Jiminny\\Jobs\\Crm\\NoteObject;\nuse Jiminny\\Models\\Crm\\Field;\nuse Jiminny\\Services\\Crm\\BaseClient;\nuse Jiminny\\Services\\Crm\\Hubspot\\DTO\\Response\\Owner;\nuse Jiminny\\Services\\SocialAccountService;\nuse SevenShores\\Hubspot\\Exceptions\\BadRequest;\nuse SevenShores\\Hubspot\\Exceptions\\HubspotException;\nuse SevenShores\\Hubspot\\Factory;\nuse SevenShores\\Hubspot\\Http\\Response;\nuse Jiminny\\Services\\Crm\\Hubspot\\Pagination\\HubspotPaginationService;\nuse Throwable;\n\n/**\n * @phpstan-type CrmFieldOption array{id:string, label:string, value?:string}\n */\nclass Client extends BaseClient implements HubspotClientInterface\n{\n public const string MIN_API_VERSION = '2';\n\n public const string BASE_URL = 'https://api.hubapi.com';\n\n public const int ASSOCIATIONS_BATCH_SIZE_LIMIT = 1000;\n\n private HubspotPaginationService $paginationService;\n private HubspotTokenManager $tokenManager;\n private ProviderRateLimiter $rateLimiter;\n\n public function __construct(\n SocialAccountService $socialAccountService,\n HubspotPaginationService $paginationService,\n HubspotTokenManager $tokenManager,\n ProviderRateLimiter $rateLimiter,\n ) {\n parent::__construct($socialAccountService);\n $this->paginationService = $paginationService;\n $this->tokenManager = $tokenManager;\n $this->rateLimiter = $rateLimiter;\n\n $this->setBaseUrl(self::BASE_URL);\n $this->setVersion(self::MIN_API_VERSION);\n }\n\n /**\n * Single entry point for every HubSpot API call. Enforces the per-portal\n * rate limit configured in the rate_limits table (morphed to the current\n * Configuration) and reacts to a real 429 from HubSpot by translating it\n * into a RateLimitException carrying Retry-After.\n *\n * Wrap any outbound HubSpot call (SDK or raw HTTP) like:\n *\n * $this->executeRequest(fn () => $this->getNewInstance()->crm()->...);\n *\n * @template T\n * @param callable(): T $apiCall\n * @return T\n *\n * @throws RateLimitException\n */\n private function executeRequest(callable $apiCall)\n {\n if (! $this->rateLimiter->canMakeRequest($this->config)) {\n $retryAfter = $this->rateLimiter->requestAvailableIn($this->config);\n\n $this->log->warning('[Hubspot] Rate limit exceeded, deferring request', [\n 'team_id' => $this->config->team_id,\n 'config_id' => $this->config->getId(),\n 'retry_after' => $retryAfter,\n ]);\n\n throw new RateLimitException(\n 'Hubspot rate limit reached for configuration ' . $this->config->getId(),\n $retryAfter,\n );\n }\n\n $this->rateLimiter->incrementRequestCount($this->config);\n\n try {\n return $apiCall();\n } catch (Throwable $e) {\n if ($this->isHubspotRateLimit($e)) {\n $retryAfter = $this->parseRetryAfter($e);\n\n $this->log->warning('[Hubspot] Received 429 from API', [\n 'team_id' => $this->config->team_id,\n 'config_id' => $this->config->getId(),\n 'retry_after' => $retryAfter,\n 'reason' => $e->getMessage(),\n ]);\n\n throw new RateLimitException('Hubspot returned 429', $retryAfter, $e);\n }\n\n throw $e;\n }\n }\n\n private function isHubspotRateLimit(Throwable $e): bool\n {\n return method_exists($e, 'getCode') && (int) $e->getCode() === 429;\n }\n\n private function parseRetryAfter(Throwable $e): int\n {\n if (method_exists($e, 'getResponseHeaders')) {\n $headers = $e->getResponseHeaders() ?: [];\n $value = $headers['Retry-After'] ?? $headers['retry-after'] ?? null;\n if (is_array($value)) {\n $value = $value[0] ?? null;\n }\n if (is_numeric($value)) {\n return (int) $value;\n }\n }\n\n return 10;\n }\n\n public function getMinimumApiVersion(): string\n {\n return self::MIN_API_VERSION;\n }\n\n public function getInstance(): Factory\n {\n return new Factory([\n 'key' => $this->accessToken,\n 'oauth2' => true,\n 'base_url' => $this->baseUrl,\n ]);\n }\n\n public function getNewInstance(): Discovery\n {\n return \\HubSpot\\Factory::createWithAccessToken($this->accessToken);\n }\n\n /**\n * Secondly and daily limits for Hubspot API\n *\n * Product Tier: Free & Starter | Professional & Enterprise | API add-on (any tier)\n * Burst: 100/10 seconds | 150/10 seconds | 200/10 seconds\n * Daily: 250,000 | 500,000 | 1,000,000\n *\n * Official documentation states: The search endpoints are rate limited to five requests per second.\n * Since with 5 RPS were still hitting secondly rate limits we lowered it to 4\n */\n public function getPaginatedData(array $payload, string $type, int $offset = 0): array\n {\n $total = 0;\n $lastId = null;\n $rows = [];\n foreach ($this->getPaginatedDataGenerator($payload, $type, $offset, $total, $lastId) as $row) {\n $rows[] = $row;\n }\n\n return ['results' => $rows, 'total' => $total, 'last_record' => $lastId];\n }\n\n /**\n * @throws HubspotException\n * @throws SocialAccountTokenInvalidException\n * @throws BadRequest\n */\n public function getPaginatedDataGenerator(\n array $payload,\n string $type,\n int $offset = 0,\n int &$total = 0,\n ?string &$lastRecordId = null\n ): \\Generator {\n return $this->paginationService->getPaginatedDataGenerator(\n $this,\n $payload,\n $type,\n $offset,\n $total,\n $lastRecordId\n );\n }\n\n /**\n * @throws DealApiException\n * @throws CrmException\n */\n public function getOpportunityById(string $crmId, array $fields): array\n {\n try {\n $deal = $this->executeRequest(fn () => $this->getNewInstance()->crm()->deals()->basicApi()->getById(\n $crmId,\n implode(',', $fields),\n 'companies,contacts'\n ));\n } catch (DealApiException $e) {\n $this->log->info('[Hubspot] Failed to fetch opportunity', [\n 'crm_id' => $crmId,\n 'reason' => $e->getMessage(),\n ]);\n\n throw $e;\n }\n\n if (! $deal instanceof DealWithAssociations) {\n throw new CrmException('Deal not found');\n }\n\n return [\n 'id' => $deal->getId(),\n 'properties' => $deal->getProperties(),\n 'associations' => $deal->getAssociations(),\n ];\n }\n\n /**\n * Generic batch read method for HubSpot objects\n *\n * @param string $objectType The object type ('deals', 'companies', 'contacts')\n * @param array<string> $crmIds Array of HubSpot object IDs (max 100)\n * @param array<string> $fields Array of property names to fetch\n *\n * @return array<string, array> Array keyed by CRM ID with object data\n */\n private function batchReadObjects(string $objectType, array $crmIds, array $fields): array\n {\n if (empty($crmIds)) {\n return [];\n }\n\n $this->validateBatchSize($objectType, $crmIds);\n $this->ensureValidToken();\n\n try {\n $batchConfig = $this->createBatchConfiguration($objectType);\n $batchReadRequest = $this->prepareBatchRequest($batchConfig, $crmIds, $fields);\n $response = $batchConfig['api']->read($batchReadRequest);\n\n $this->validateApiResponse($response, $objectType);\n\n $results = $this->processApiResults($response);\n $this->logBatchResults($objectType, $crmIds, $results);\n\n return $results;\n } catch (\\Throwable $e) {\n $this->handleBatchError($e, $objectType, $crmIds);\n }\n }\n\n private function validateBatchSize(string $objectType, array $crmIds): void\n {\n if (count($crmIds) > 100) {\n throw new \\InvalidArgumentException(\"Batch size cannot exceed 100 {$objectType}\");\n }\n }\n\n private function createBatchConfiguration(string $objectType): array\n {\n $configurations = [\n 'deals' => [\n 'batchReadRequest' => new \\HubSpot\\Client\\Crm\\Deals\\Model\\BatchReadInputSimplePublicObjectId(),\n 'inputClass' => \\HubSpot\\Client\\Crm\\Deals\\Model\\SimplePublicObjectId::class,\n 'api' => $this->getNewInstance()->crm()->deals()->batchApi(),\n ],\n 'companies' => [\n 'batchReadRequest' => new \\HubSpot\\Client\\Crm\\Companies\\Model\\BatchReadInputSimplePublicObjectId(),\n 'inputClass' => \\HubSpot\\Client\\Crm\\Companies\\Model\\SimplePublicObjectId::class,\n 'api' => $this->getNewInstance()->crm()->companies()->batchApi(),\n ],\n 'contacts' => [\n 'batchReadRequest' => new \\HubSpot\\Client\\Crm\\Contacts\\Model\\BatchReadInputSimplePublicObjectId(),\n 'inputClass' => \\HubSpot\\Client\\Crm\\Contacts\\Model\\SimplePublicObjectId::class,\n 'api' => $this->getNewInstance()->crm()->contacts()->batchApi(),\n ],\n ];\n\n if (! isset($configurations[$objectType])) {\n throw new \\InvalidArgumentException(\"Unsupported object type: {$objectType}\");\n }\n\n return $configurations[$objectType];\n }\n\n private function prepareBatchRequest(array $batchConfig, array $crmIds, array $fields): object\n {\n $batchReadRequest = $batchConfig['batchReadRequest'];\n $inputClass = $batchConfig['inputClass'];\n\n $inputs = array_map(function ($crmId) use ($inputClass) {\n $input = new $inputClass();\n $input->setId($crmId);\n\n return $input;\n }, $crmIds);\n\n $batchReadRequest->setInputs($inputs);\n $batchReadRequest->setProperties($fields);\n\n return $batchReadRequest;\n }\n\n private function validateApiResponse($response, string $objectType): void\n {\n if (! $response) {\n throw new CrmException(\"HubSpot API returned null response for {$objectType} batch read\");\n }\n }\n\n private function processApiResults($response): array\n {\n $results = [];\n $responseResults = $response->getResults();\n\n if ($responseResults) {\n foreach ($responseResults as $object) {\n if ($object && $object->getId()) {\n $results[$object->getId()] = [\n 'id' => $object->getId(),\n 'properties' => $object->getProperties() ?: [],\n ];\n }\n }\n }\n\n return $results;\n }\n\n private function logBatchResults(string $objectType, array $crmIds, array $results): void\n {\n $this->log->info(\"[HubSpot] Batch fetched {$objectType}\", [\n 'requested_count' => count($crmIds),\n 'returned_count' => count($results),\n 'crm_ids' => $crmIds,\n ]);\n }\n\n private function handleBatchError(\\Throwable $e, string $objectType, array $crmIds): void\n {\n $errorMessage = $e->getMessage() ?: 'Unknown error';\n $errorTrace = $e->getTraceAsString() ?: 'No trace available';\n\n $this->log->error(\"[HubSpot] Failed to batch fetch {$objectType}\", [\n 'crm_ids' => $crmIds,\n 'error' => $errorMessage,\n 'trace' => $errorTrace,\n ]);\n\n throw new CrmException(\"Failed to batch fetch {$objectType}: \" . $errorMessage);\n }\n\n /**\n * Batch read multiple opportunities by their CRM IDs\n *\n * @param array<string> $crmIds Array of HubSpot deal IDs (max 100)\n * @param array<string> $fields Array of property names to fetch\n *\n * @return array<string, array> Array keyed by CRM ID with opportunity data\n */\n public function getOpportunitiesByIds(array $crmIds, array $fields): array\n {\n return $this->batchReadObjects('deals', $crmIds, $fields);\n }\n\n /**\n * Batch read multiple companies by their CRM IDs\n *\n * @param array<string> $crmIds Array of HubSpot company IDs (max 100)\n * @param array<string> $fields Array of property names to fetch\n *\n * @return array<string, array> Array keyed by CRM ID with company data\n */\n public function getCompaniesByIds(array $crmIds, array $fields): array\n {\n return $this->batchReadObjects('companies', $crmIds, $fields);\n }\n\n /**\n * Batch read multiple contacts by their CRM IDs\n *\n * @param array<string> $crmIds Array of HubSpot contact IDs (max 100)\n * @param array<string> $fields Array of property names to fetch\n *\n * @return array<string, array> Array keyed by CRM ID with contact data\n */\n public function getContactsByIds(array $crmIds, array $fields): array\n {\n return $this->batchReadObjects('contacts', $crmIds, $fields);\n }\n\n /**\n * @throws CompanyApiException\n * @throws CrmException\n */\n public function getAccountById(string $crmId, array $fields): array\n {\n try {\n $company = $this->getNewInstance()->crm()->companies()->basicApi()->getById(\n $crmId,\n implode(',', $fields),\n );\n } catch (CompanyApiException $e) {\n $this->log->info('[Hubspot] Failed to fetch account', [\n 'crm_id' => $crmId,\n 'reason' => $e->getMessage(),\n ]);\n\n throw $e;\n }\n\n if (! $company instanceof CompaniesWithAssociations) {\n throw new CrmException('Account not found');\n }\n\n return [\n 'id' => $company->getId(),\n 'properties' => $company->getProperties(),\n ];\n }\n\n /**\n * @throws ContactApiException\n * @throws CrmException\n */\n public function getContactById(string $crmId, array $fields): array\n {\n try {\n $contact = $this->getNewInstance()->crm()->contacts()->basicApi()->getById(\n $crmId,\n implode(',', $fields)\n );\n } catch (ContactApiException $e) {\n $this->log->info('[Hubspot] Failed to fetch contact', [\n 'crm_id' => $crmId,\n 'reason' => $e->getMessage(),\n ]);\n\n throw $e;\n }\n\n if (! $contact instanceof ContactsWithAssociations) {\n throw new CrmException('Contact not found');\n }\n\n return [\n 'id' => $contact->getId(),\n 'properties' => $contact->getProperties(),\n ];\n }\n\n /**\n * This is email search request that Hubspot offers as GET (more generous quota)\n */\n public function getContactByEmail(string $email, array $fields = []): array\n {\n try {\n $contact = $this->getNewInstance()->crm()->contacts()->basicApi()->getById(\n $email,\n implode(',', $fields),\n null,\n false,\n 'email'\n );\n\n return [\n 'id' => $contact->getId(),\n 'properties' => $contact->getProperties(),\n ];\n } catch (ContactApiException $e) {\n $this->log->info('[Hubspot] Failed to fetch contact', [\n 'email' => $email,\n 'reason' => $e->getMessage(),\n ]);\n\n return [];\n }\n }\n\n /**\n * @throws CrmException\n */\n public function fetchProperty(string $objectType, string $propertyId): Property\n {\n $result = $this->getNewInstance()->crm()->properties()->coreApi()->getByName($objectType, $propertyId);\n\n if (! $result instanceof Property) {\n $this->log->error('[Hubspot] Failed to fetch property', [\n 'object_type' => $objectType,\n 'property_id' => $propertyId,\n 'reason' => $result->getMessage(),\n ]);\n\n throw new CrmException('Failed to fetch property');\n }\n\n return $result;\n }\n\n /**\n * @return array<CrmFieldOption>\n */\n public function fetchPropertyOptions(string $objectType, string $propertyId): array\n {\n /** @var array<CrmFieldOption> */\n return $this->fetchProperty($objectType, $propertyId)->getOptions();\n }\n\n /**\n * @return array<array{id:string, label:string, deleted:bool}>\n */\n public function fetchCallDispositions(): array\n {\n /** @var Response $response */\n $response = $this->getInstance()->engagements()->getCallDispositions();\n\n /**\n * @var array<array{\n * id:string,\n * label:string,\n * deleted: bool\n * }>\n */\n return $response->toArray();\n }\n\n /**\n * @return array<CrmFieldOption>\n */\n public function fetchOpportunityPipelineStages(): array\n {\n $stages = [];\n $apiResponse = $this->getNewInstance()->crm()->pipelines()->pipelinesApi()->getAll('deals');\n\n if ($apiResponse instanceof Error) {\n $this->log->error('[Hubspot] Failed to fetch opportunity pipelines', [\n 'reason' => $apiResponse->getMessage(),\n ]);\n\n return [];\n }\n\n foreach ($apiResponse->getResults() as $pipeline) {\n $pipelineStages = array_map(\n static function (PipelineStage $stage) {\n return [\n 'id' => $stage->getId(),\n 'label' => $stage->getLabel(),\n ];\n },\n $pipeline->getStages()\n );\n\n $stages = array_merge($stages, $pipelineStages);\n }\n\n return $stages;\n }\n\n public function fetchOpportunityPipelines(): array\n {\n $pipelines = [];\n\n try {\n $apiResponse = $this->makeRequest('/crm/v3/pipelines/deals');\n } catch (\\Exception $e) {\n $this->log->info('[Hubspot] Failed to fetch opportunity pipelines', [\n 'reason' => $e->getMessage(),\n ]);\n\n return [];\n }\n\n $response = $apiResponse->toArray();\n\n foreach ($response['results'] as $pipeline) {\n $pipelines[] = [\n 'id' => $pipeline['id'],\n 'label' => $pipeline['label'],\n ];\n }\n\n return $pipelines;\n }\n\n /**\n * @return array<CrmFieldOption>\n */\n public function fetchMeetingOutcomeFieldOptions(Field $field): array\n {\n return $field->getCrmProviderId() === 'meetingOutcome'\n ? $this->fetchMeetingOutcomeTypes()\n : $this->fetchCallActivityTypes();\n }\n\n public function fetchMeetingOutcomeTypes(): array\n {\n return $this->extractMeetingTypeOptions(\n 'https://api.hubapi.com/crm/v3/properties/meeting/hs_meeting_outcome'\n );\n }\n\n public function fetchCallActivityTypes(): array\n {\n return $this->extractMeetingTypeOptions(\n 'https://api.hubapi.com/crm/v3/properties/call/hs_activity_type'\n );\n }\n\n private function extractMeetingTypeOptions(string $endpoint): array\n {\n /** @var Response $response */\n $response = $this->getInstance()\n ->getClient()\n ->request('GET', $endpoint);\n\n /**\n * @var array<array{\n * value: string,\n * label: string,\n * displayOrder: int\n * }> $optionData\n */\n $optionData = $response->toArray()['options'] ?? [];\n\n $options = [];\n foreach ($optionData as $item) {\n $options[] = [\n 'id' => $item['value'],\n 'value' => $item['value'],\n 'label' => $item['label'],\n 'display_order' => $item['displayOrder'],\n ];\n }\n\n return $options;\n }\n\n /**\n * @return array<CrmFieldOption>\n */\n public function fetchDispositionFieldOptions(): array\n {\n $options = [];\n\n $dispositions = $this->fetchCallDispositions();\n\n foreach ($dispositions as $disposition) {\n if ($disposition['deleted'] !== false) {\n continue;\n }\n\n $option['value'] = $disposition['id'];\n $option['id'] = $disposition['id'];\n $option['label'] = $disposition['label'];\n\n $options[] = $option;\n }\n\n return $options;\n }\n\n /**\n * @return array<CrmFieldOption>\n */\n public function fetchOpportunityFieldOptions(Field $field): array\n {\n if ($field->isStageField()) {\n return $this->fetchOpportunityPipelineStages();\n }\n\n if ($field->isPipelineField()) {\n return $this->fetchOpportunityPipelines();\n }\n\n return $this->fetchPropertyOptions('deals', $field->getCrmProviderId());\n }\n\n /**\n * @throws BadRequest\n * @throws HubspotException\n */\n public function makeRequest(string $endpoint, $method = 'GET', $payload = [], ?string $queryString = null)\n {\n $endpoint = self::BASE_URL . $endpoint;\n\n if ($method === 'GET') {\n $response = $this->getInstance()->getClient()?->request(\n method: $method,\n endpoint: $endpoint,\n query_string: $queryString\n );\n } else {\n $response = $this->getInstance()->getClient()->request($method, $endpoint, [\n 'json' => ($payload),\n ]);\n }\n \n $max = $response->getHeaderLine('X-HubSpot-RateLimit-Max'); // \"110\"\n $remaining = $response->getHeaderLine('X-HubSpot-RateLimit-Remaining'); // \"109\"\n $interval = $response->getHeaderLine('X-HubSpot-RateLimit-Interval-Milliseconds'); // \"10000\"\n $body = json_decode((string) $response->getBody(), true);\n \n \\Illuminate\\Support\\Facades\\Log::channel('custom_channel')->info('$max ' . PHP_EOL . print_r($max, true));\n \\Illuminate\\Support\\Facades\\Log::channel('custom_channel')->info('$remaining ' . PHP_EOL . print_r($remaining, true));\n \\Illuminate\\Support\\Facades\\Log::channel('custom_channel')->info('$interval ' . PHP_EOL . print_r($interval, true));\n \\Illuminate\\Support\\Facades\\Log::channel('custom_channel')->info('$interval ' . PHP_EOL . print_r($interval, true));\n \n return $response;\n }\n\n /**\n * @throws BadRequest\n * @throws HubspotException\n */\n public function createMeeting(array $payload): Response\n {\n $endpoint = '/crm/v3/objects/meetings';\n\n return $this->makeRequest($endpoint, 'POST', $payload);\n }\n\n /**\n * @throws BadRequest\n * @throws HubspotException\n */\n public function updateMeeting(string $meetingId, array $payload): Response\n {\n $endpoint = '/crm/v3/objects/meetings/' . $meetingId;\n\n return $this->makeRequest($endpoint, 'PATCH', $payload);\n }\n\n /**\n * @throws \\Exception\n */\n public function createNote(\n string $body,\n string $ownerId,\n int $timestamp,\n string $objectId,\n NoteObject $noteObject\n ): ?string {\n try {\n $noteInput = new SimplePublicObjectInput([\n 'properties' => [\n 'hs_note_body' => $body,\n 'hubspot_owner_id' => $ownerId,\n 'hs_timestamp' => $timestamp,\n ],\n ]);\n\n // Create note\n $note = $this->getNewInstance()->crm()->objects()->basicApi()->create('note', $noteInput);\n\n $this->getNewInstance()->crm()->objects()->associationsApi()->create(\n 'note',\n $note->getId(),\n $this->getNoteObject($noteObject),\n $objectId,\n $this->getNoteAssociationType($noteObject),\n );\n\n return $note->getId();\n } catch (\\Exception $e) {\n $this->log->error('[Hubspot] Failed to create note', [\n 'objectId' => $objectId,\n 'noteObject' => $noteObject->getObjectType(),\n 'reason' => $e->getMessage(),\n ]);\n\n \\Sentry::captureException($e);\n }\n\n return null;\n }\n\n public function updateEngagement(string $objectId, array $engagement, array $metadata): void\n {\n $this->getInstance()->engagements()->update($objectId, $engagement, $metadata);\n }\n\n public function getEngagementData(string $engagementId): array\n {\n $engagement = $this->getInstance()->engagements()->get($engagementId);\n\n return $engagement->toArray();\n }\n\n public function createEngagement(array $engagement, array $associations, array $metadata): Response\n {\n return $this->getInstance()\n ->engagements()\n ->create($engagement, $associations, $metadata);\n }\n\n public function isUnauthorizedException(\\Exception $e): bool\n {\n // Check for specific HubSpot API exception types first\n if ($e instanceof BadRequest) {\n // BadRequest can contain 401 status codes\n return $e->getCode() === 401;\n }\n\n // Check for HTTP client exceptions with status codes\n if ($e instanceof \\GuzzleHttp\\Exception\\RequestException && $e->hasResponse()) {\n $response = $e->getResponse();\n if ($response !== null) {\n return $response->getStatusCode() === 401;\n }\n }\n\n // Check for Guzzle HTTP exceptions\n if ($e instanceof \\GuzzleHttp\\Exception\\ClientException) {\n return $e->getCode() === 401;\n }\n\n // Fallback to string matching as last resort, but be more specific\n $message = strtolower($e->getMessage());\n\n return str_contains($message, '401 unauthorized') ||\n str_contains($message, 'http 401') ||\n str_contains($message, 'status code 401') ||\n (preg_match('/\\b401\\b/', $message) && str_contains($message, 'unauthorized'));\n }\n\n /**\n * Validates and refreshes the access token if needed before API requests.\n * This ensures long-running processes don't fail due to token expiration.\n *\n * @throws SocialAccountTokenInvalidException\n */\n public function ensureValidToken(): void\n {\n if ($this->oauthAccount === null) {\n return;\n }\n\n $newToken = $this->tokenManager->ensureValidToken($this->oauthAccount);\n if ($newToken !== null) {\n $this->accessToken = $newToken;\n }\n }\n\n public function getConfig()\n {\n return $this->config;\n }\n\n // returns only active (archived=false)\n public function getOwners(): array\n {\n return $this->getNewInstance()->crm()->owners()->getAll();\n }\n\n /**\n * @param bool $archived\n *\n * @return array<Owner>|[]\n */\n public function getOwnersArchived(bool $archived = true): array\n {\n $endpoint = '/crm/v3/owners';\n $queryParams = [\n 'archived' => $archived ? 'true' : 'false',\n ];\n $queryString = http_build_query($queryParams);\n\n $owners = [];\n\n try {\n $response = $this->makeRequest(endpoint: $endpoint, queryString: $queryString);\n $responseData = $response?->toArray();\n\n foreach ($responseData['results'] as $result) {\n try {\n $owners[] = Owner::create($result);\n } catch (Throwable $e) {\n $this->log->error('[HubSpot] Failed to process owner data', [\n 'result' => $result,\n 'error' => $e->getMessage(),\n ]);\n\n continue;\n }\n }\n } catch (Throwable $e) {\n $this->log->error('HubSpot] Failed to fetch owners', [\n 'archived' => $archived,\n 'error' => $e->getMessage(),\n ]);\n\n return [];\n }\n\n return $owners;\n }\n\n public function getMeeting(string $engagementId): ObjectWithAssociations\n {\n return $this->getNewInstance()->crm()->objects()->basicApi()\n ->getById('meeting', $engagementId, null, 'contact,company,deal');\n }\n\n public function deleteEngagement(string $engagementId): void\n {\n $this->getInstance()->engagements()->delete((int) $engagementId);\n }\n\n public function getAssociationsData(array $ids, string $fromObject, string $toObject): array\n {\n $associationData = [];\n $idChunks = array_chunk($ids, self::ASSOCIATIONS_BATCH_SIZE_LIMIT);\n\n foreach ($idChunks as $idChunk) {\n try {\n $batchInput = new \\HubSpot\\Client\\Crm\\Associations\\Model\\BatchInputPublicObjectId();\n $batchInput->setInputs(array_map(function ($id) {\n $publicObjectId = new \\HubSpot\\Client\\Crm\\Associations\\Model\\PublicObjectId();\n $publicObjectId->setId($id);\n\n return $publicObjectId;\n }, $idChunk));\n\n $associatedObjectsData = $this\n ->getNewInstance()\n ->crm()\n ->associations()\n ->batchApi()\n ->read($fromObject, $toObject, $batchInput);\n\n if ($associatedObjectsData instanceof \\HubSpot\\Client\\Crm\\Associations\\Model\\BatchResponsePublicAssociationMulti) {\n foreach ($associatedObjectsData->getResults() as $association) {\n $from = $association->getFrom()->getId();\n $toAssociations = $association->getTo();\n\n if (! empty($toAssociations)) {\n $associationData[$from] = array_map(function ($item) {\n return $item->getId();\n }, $toAssociations);\n }\n }\n }\n } catch (\\Exception $e) {\n $this->log->error('[Hubspot] Failed to fetch associations', [\n 'from_object' => $fromObject,\n 'to_object' => $toObject,\n 'reason' => $e->getMessage(),\n ]);\n }\n }\n\n return $associationData;\n }\n\n /**\n * @throws \\Exception\n */\n private function getNoteAssociationType(NoteObject $noteObject): string\n {\n return match($noteObject) {\n NoteObject::Opportunity => 'note_to_deal',\n NoteObject::Lead, NoteObject::Contact => 'note_to_contact', // or 'note_to_lead' if your portal supports it\n NoteObject::Account => 'note_to_company',\n NoteObject::Call, NoteObject::Event => throw new \\Exception('Not supported'),\n };\n }\n\n /**\n * @throws \\Exception\n */\n private function getNoteObject(NoteObject $noteObject): string\n {\n return match($noteObject) {\n NoteObject::Opportunity => 'deal',\n NoteObject::Lead, NoteObject::Contact => 'contact',\n NoteObject::Account => 'company',\n NoteObject::Call, NoteObject::Event => throw new \\Exception('Not supported'),\n };\n }\n\n public function addAssociations(string $objectType, string $associationType, array $payload): Response\n {\n $endpoint = \"/crm/v4/associations/$objectType/$associationType/batch/create\";\n\n return $this->makeRequest($endpoint, 'POST', $payload);\n }\n\n public function removeAssociations(string $objectType, string $associationType, array $payload): Response\n {\n $endpoint = \"/crm/v4/associations/$objectType/$associationType/batch/archive\";\n\n return $this->makeRequest($endpoint, 'POST', $payload);\n }\n}","depth":4,"on_screen":true,"value":"<?php\n\ndeclare(strict_types=1);\n\nnamespace Jiminny\\Services\\Crm\\Hubspot;\n\nuse HubSpot\\Client\\Crm\\Deals\\ApiException as DealApiException;\nuse HubSpot\\Client\\Crm\\Contacts\\ApiException as ContactApiException;\nuse HubSpot\\Client\\Crm\\Companies\\ApiException as CompanyApiException;\nuse HubSpot\\Client\\Crm\\Contacts\\Model\\SimplePublicObjectWithAssociations as ContactsWithAssociations;\nuse HubSpot\\Client\\Crm\\Companies\\Model\\SimplePublicObjectWithAssociations as CompaniesWithAssociations;\nuse HubSpot\\Client\\Crm\\Deals\\Model\\SimplePublicObjectWithAssociations as DealWithAssociations;\nuse HubSpot\\Client\\Crm\\Objects\\Model\\SimplePublicObjectInput;\nuse HubSpot\\Client\\Crm\\Objects\\Model\\SimplePublicObjectWithAssociations as ObjectWithAssociations;\nuse HubSpot\\Client\\Crm\\Pipelines\\Model\\Error;\nuse HubSpot\\Client\\Crm\\Pipelines\\Model\\PipelineStage;\nuse HubSpot\\Client\\Crm\\Properties\\Model\\Property;\nuse HubSpot\\Discovery\\Discovery;\nuse Jiminny\\Component\\Utility\\Service\\ProviderRateLimiter;\nuse Jiminny\\Exceptions\\CrmException;\nuse Jiminny\\Exceptions\\RateLimitException;\nuse Jiminny\\Exceptions\\SocialAccountTokenInvalidException;\nuse Jiminny\\Jobs\\Crm\\NoteObject;\nuse Jiminny\\Models\\Crm\\Field;\nuse Jiminny\\Services\\Crm\\BaseClient;\nuse Jiminny\\Services\\Crm\\Hubspot\\DTO\\Response\\Owner;\nuse Jiminny\\Services\\SocialAccountService;\nuse SevenShores\\Hubspot\\Exceptions\\BadRequest;\nuse SevenShores\\Hubspot\\Exceptions\\HubspotException;\nuse SevenShores\\Hubspot\\Factory;\nuse SevenShores\\Hubspot\\Http\\Response;\nuse Jiminny\\Services\\Crm\\Hubspot\\Pagination\\HubspotPaginationService;\nuse Throwable;\n\n/**\n * @phpstan-type CrmFieldOption array{id:string, label:string, value?:string}\n */\nclass Client extends BaseClient implements HubspotClientInterface\n{\n public const string MIN_API_VERSION = '2';\n\n public const string BASE_URL = 'https://api.hubapi.com';\n\n public const int ASSOCIATIONS_BATCH_SIZE_LIMIT = 1000;\n\n private HubspotPaginationService $paginationService;\n private HubspotTokenManager $tokenManager;\n private ProviderRateLimiter $rateLimiter;\n\n public function __construct(\n SocialAccountService $socialAccountService,\n HubspotPaginationService $paginationService,\n HubspotTokenManager $tokenManager,\n ProviderRateLimiter $rateLimiter,\n ) {\n parent::__construct($socialAccountService);\n $this->paginationService = $paginationService;\n $this->tokenManager = $tokenManager;\n $this->rateLimiter = $rateLimiter;\n\n $this->setBaseUrl(self::BASE_URL);\n $this->setVersion(self::MIN_API_VERSION);\n }\n\n /**\n * Single entry point for every HubSpot API call. Enforces the per-portal\n * rate limit configured in the rate_limits table (morphed to the current\n * Configuration) and reacts to a real 429 from HubSpot by translating it\n * into a RateLimitException carrying Retry-After.\n *\n * Wrap any outbound HubSpot call (SDK or raw HTTP) like:\n *\n * $this->executeRequest(fn () => $this->getNewInstance()->crm()->...);\n *\n * @template T\n * @param callable(): T $apiCall\n * @return T\n *\n * @throws RateLimitException\n */\n private function executeRequest(callable $apiCall)\n {\n if (! $this->rateLimiter->canMakeRequest($this->config)) {\n $retryAfter = $this->rateLimiter->requestAvailableIn($this->config);\n\n $this->log->warning('[Hubspot] Rate limit exceeded, deferring request', [\n 'team_id' => $this->config->team_id,\n 'config_id' => $this->config->getId(),\n 'retry_after' => $retryAfter,\n ]);\n\n throw new RateLimitException(\n 'Hubspot rate limit reached for configuration ' . $this->config->getId(),\n $retryAfter,\n );\n }\n\n $this->rateLimiter->incrementRequestCount($this->config);\n\n try {\n return $apiCall();\n } catch (Throwable $e) {\n if ($this->isHubspotRateLimit($e)) {\n $retryAfter = $this->parseRetryAfter($e);\n\n $this->log->warning('[Hubspot] Received 429 from API', [\n 'team_id' => $this->config->team_id,\n 'config_id' => $this->config->getId(),\n 'retry_after' => $retryAfter,\n 'reason' => $e->getMessage(),\n ]);\n\n throw new RateLimitException('Hubspot returned 429', $retryAfter, $e);\n }\n\n throw $e;\n }\n }\n\n private function isHubspotRateLimit(Throwable $e): bool\n {\n return method_exists($e, 'getCode') && (int) $e->getCode() === 429;\n }\n\n private function parseRetryAfter(Throwable $e): int\n {\n if (method_exists($e, 'getResponseHeaders')) {\n $headers = $e->getResponseHeaders() ?: [];\n $value = $headers['Retry-After'] ?? $headers['retry-after'] ?? null;\n if (is_array($value)) {\n $value = $value[0] ?? null;\n }\n if (is_numeric($value)) {\n return (int) $value;\n }\n }\n\n return 10;\n }\n\n public function getMinimumApiVersion(): string\n {\n return self::MIN_API_VERSION;\n }\n\n public function getInstance(): Factory\n {\n return new Factory([\n 'key' => $this->accessToken,\n 'oauth2' => true,\n 'base_url' => $this->baseUrl,\n ]);\n }\n\n public function getNewInstance(): Discovery\n {\n return \\HubSpot\\Factory::createWithAccessToken($this->accessToken);\n }\n\n /**\n * Secondly and daily limits for Hubspot API\n *\n * Product Tier: Free & Starter | Professional & Enterprise | API add-on (any tier)\n * Burst: 100/10 seconds | 150/10 seconds | 200/10 seconds\n * Daily: 250,000 | 500,000 | 1,000,000\n *\n * Official documentation states: The search endpoints are rate limited to five requests per second.\n * Since with 5 RPS were still hitting secondly rate limits we lowered it to 4\n */\n public function getPaginatedData(array $payload, string $type, int $offset = 0): array\n {\n $total = 0;\n $lastId = null;\n $rows = [];\n foreach ($this->getPaginatedDataGenerator($payload, $type, $offset, $total, $lastId) as $row) {\n $rows[] = $row;\n }\n\n return ['results' => $rows, 'total' => $total, 'last_record' => $lastId];\n }\n\n /**\n * @throws HubspotException\n * @throws SocialAccountTokenInvalidException\n * @throws BadRequest\n */\n public function getPaginatedDataGenerator(\n array $payload,\n string $type,\n int $offset = 0,\n int &$total = 0,\n ?string &$lastRecordId = null\n ): \\Generator {\n return $this->paginationService->getPaginatedDataGenerator(\n $this,\n $payload,\n $type,\n $offset,\n $total,\n $lastRecordId\n );\n }\n\n /**\n * @throws DealApiException\n * @throws CrmException\n */\n public function getOpportunityById(string $crmId, array $fields): array\n {\n try {\n $deal = $this->executeRequest(fn () => $this->getNewInstance()->crm()->deals()->basicApi()->getById(\n $crmId,\n implode(',', $fields),\n 'companies,contacts'\n ));\n } catch (DealApiException $e) {\n $this->log->info('[Hubspot] Failed to fetch opportunity', [\n 'crm_id' => $crmId,\n 'reason' => $e->getMessage(),\n ]);\n\n throw $e;\n }\n\n if (! $deal instanceof DealWithAssociations) {\n throw new CrmException('Deal not found');\n }\n\n return [\n 'id' => $deal->getId(),\n 'properties' => $deal->getProperties(),\n 'associations' => $deal->getAssociations(),\n ];\n }\n\n /**\n * Generic batch read method for HubSpot objects\n *\n * @param string $objectType The object type ('deals', 'companies', 'contacts')\n * @param array<string> $crmIds Array of HubSpot object IDs (max 100)\n * @param array<string> $fields Array of property names to fetch\n *\n * @return array<string, array> Array keyed by CRM ID with object data\n */\n private function batchReadObjects(string $objectType, array $crmIds, array $fields): array\n {\n if (empty($crmIds)) {\n return [];\n }\n\n $this->validateBatchSize($objectType, $crmIds);\n $this->ensureValidToken();\n\n try {\n $batchConfig = $this->createBatchConfiguration($objectType);\n $batchReadRequest = $this->prepareBatchRequest($batchConfig, $crmIds, $fields);\n $response = $batchConfig['api']->read($batchReadRequest);\n\n $this->validateApiResponse($response, $objectType);\n\n $results = $this->processApiResults($response);\n $this->logBatchResults($objectType, $crmIds, $results);\n\n return $results;\n } catch (\\Throwable $e) {\n $this->handleBatchError($e, $objectType, $crmIds);\n }\n }\n\n private function validateBatchSize(string $objectType, array $crmIds): void\n {\n if (count($crmIds) > 100) {\n throw new \\InvalidArgumentException(\"Batch size cannot exceed 100 {$objectType}\");\n }\n }\n\n private function createBatchConfiguration(string $objectType): array\n {\n $configurations = [\n 'deals' => [\n 'batchReadRequest' => new \\HubSpot\\Client\\Crm\\Deals\\Model\\BatchReadInputSimplePublicObjectId(),\n 'inputClass' => \\HubSpot\\Client\\Crm\\Deals\\Model\\SimplePublicObjectId::class,\n 'api' => $this->getNewInstance()->crm()->deals()->batchApi(),\n ],\n 'companies' => [\n 'batchReadRequest' => new \\HubSpot\\Client\\Crm\\Companies\\Model\\BatchReadInputSimplePublicObjectId(),\n 'inputClass' => \\HubSpot\\Client\\Crm\\Companies\\Model\\SimplePublicObjectId::class,\n 'api' => $this->getNewInstance()->crm()->companies()->batchApi(),\n ],\n 'contacts' => [\n 'batchReadRequest' => new \\HubSpot\\Client\\Crm\\Contacts\\Model\\BatchReadInputSimplePublicObjectId(),\n 'inputClass' => \\HubSpot\\Client\\Crm\\Contacts\\Model\\SimplePublicObjectId::class,\n 'api' => $this->getNewInstance()->crm()->contacts()->batchApi(),\n ],\n ];\n\n if (! isset($configurations[$objectType])) {\n throw new \\InvalidArgumentException(\"Unsupported object type: {$objectType}\");\n }\n\n return $configurations[$objectType];\n }\n\n private function prepareBatchRequest(array $batchConfig, array $crmIds, array $fields): object\n {\n $batchReadRequest = $batchConfig['batchReadRequest'];\n $inputClass = $batchConfig['inputClass'];\n\n $inputs = array_map(function ($crmId) use ($inputClass) {\n $input = new $inputClass();\n $input->setId($crmId);\n\n return $input;\n }, $crmIds);\n\n $batchReadRequest->setInputs($inputs);\n $batchReadRequest->setProperties($fields);\n\n return $batchReadRequest;\n }\n\n private function validateApiResponse($response, string $objectType): void\n {\n if (! $response) {\n throw new CrmException(\"HubSpot API returned null response for {$objectType} batch read\");\n }\n }\n\n private function processApiResults($response): array\n {\n $results = [];\n $responseResults = $response->getResults();\n\n if ($responseResults) {\n foreach ($responseResults as $object) {\n if ($object && $object->getId()) {\n $results[$object->getId()] = [\n 'id' => $object->getId(),\n 'properties' => $object->getProperties() ?: [],\n ];\n }\n }\n }\n\n return $results;\n }\n\n private function logBatchResults(string $objectType, array $crmIds, array $results): void\n {\n $this->log->info(\"[HubSpot] Batch fetched {$objectType}\", [\n 'requested_count' => count($crmIds),\n 'returned_count' => count($results),\n 'crm_ids' => $crmIds,\n ]);\n }\n\n private function handleBatchError(\\Throwable $e, string $objectType, array $crmIds): void\n {\n $errorMessage = $e->getMessage() ?: 'Unknown error';\n $errorTrace = $e->getTraceAsString() ?: 'No trace available';\n\n $this->log->error(\"[HubSpot] Failed to batch fetch {$objectType}\", [\n 'crm_ids' => $crmIds,\n 'error' => $errorMessage,\n 'trace' => $errorTrace,\n ]);\n\n throw new CrmException(\"Failed to batch fetch {$objectType}: \" . $errorMessage);\n }\n\n /**\n * Batch read multiple opportunities by their CRM IDs\n *\n * @param array<string> $crmIds Array of HubSpot deal IDs (max 100)\n * @param array<string> $fields Array of property names to fetch\n *\n * @return array<string, array> Array keyed by CRM ID with opportunity data\n */\n public function getOpportunitiesByIds(array $crmIds, array $fields): array\n {\n return $this->batchReadObjects('deals', $crmIds, $fields);\n }\n\n /**\n * Batch read multiple companies by their CRM IDs\n *\n * @param array<string> $crmIds Array of HubSpot company IDs (max 100)\n * @param array<string> $fields Array of property names to fetch\n *\n * @return array<string, array> Array keyed by CRM ID with company data\n */\n public function getCompaniesByIds(array $crmIds, array $fields): array\n {\n return $this->batchReadObjects('companies', $crmIds, $fields);\n }\n\n /**\n * Batch read multiple contacts by their CRM IDs\n *\n * @param array<string> $crmIds Array of HubSpot contact IDs (max 100)\n * @param array<string> $fields Array of property names to fetch\n *\n * @return array<string, array> Array keyed by CRM ID with contact data\n */\n public function getContactsByIds(array $crmIds, array $fields): array\n {\n return $this->batchReadObjects('contacts', $crmIds, $fields);\n }\n\n /**\n * @throws CompanyApiException\n * @throws CrmException\n */\n public function getAccountById(string $crmId, array $fields): array\n {\n try {\n $company = $this->getNewInstance()->crm()->companies()->basicApi()->getById(\n $crmId,\n implode(',', $fields),\n );\n } catch (CompanyApiException $e) {\n $this->log->info('[Hubspot] Failed to fetch account', [\n 'crm_id' => $crmId,\n 'reason' => $e->getMessage(),\n ]);\n\n throw $e;\n }\n\n if (! $company instanceof CompaniesWithAssociations) {\n throw new CrmException('Account not found');\n }\n\n return [\n 'id' => $company->getId(),\n 'properties' => $company->getProperties(),\n ];\n }\n\n /**\n * @throws ContactApiException\n * @throws CrmException\n */\n public function getContactById(string $crmId, array $fields): array\n {\n try {\n $contact = $this->getNewInstance()->crm()->contacts()->basicApi()->getById(\n $crmId,\n implode(',', $fields)\n );\n } catch (ContactApiException $e) {\n $this->log->info('[Hubspot] Failed to fetch contact', [\n 'crm_id' => $crmId,\n 'reason' => $e->getMessage(),\n ]);\n\n throw $e;\n }\n\n if (! $contact instanceof ContactsWithAssociations) {\n throw new CrmException('Contact not found');\n }\n\n return [\n 'id' => $contact->getId(),\n 'properties' => $contact->getProperties(),\n ];\n }\n\n /**\n * This is email search request that Hubspot offers as GET (more generous quota)\n */\n public function getContactByEmail(string $email, array $fields = []): array\n {\n try {\n $contact = $this->getNewInstance()->crm()->contacts()->basicApi()->getById(\n $email,\n implode(',', $fields),\n null,\n false,\n 'email'\n );\n\n return [\n 'id' => $contact->getId(),\n 'properties' => $contact->getProperties(),\n ];\n } catch (ContactApiException $e) {\n $this->log->info('[Hubspot] Failed to fetch contact', [\n 'email' => $email,\n 'reason' => $e->getMessage(),\n ]);\n\n return [];\n }\n }\n\n /**\n * @throws CrmException\n */\n public function fetchProperty(string $objectType, string $propertyId): Property\n {\n $result = $this->getNewInstance()->crm()->properties()->coreApi()->getByName($objectType, $propertyId);\n\n if (! $result instanceof Property) {\n $this->log->error('[Hubspot] Failed to fetch property', [\n 'object_type' => $objectType,\n 'property_id' => $propertyId,\n 'reason' => $result->getMessage(),\n ]);\n\n throw new CrmException('Failed to fetch property');\n }\n\n return $result;\n }\n\n /**\n * @return array<CrmFieldOption>\n */\n public function fetchPropertyOptions(string $objectType, string $propertyId): array\n {\n /** @var array<CrmFieldOption> */\n return $this->fetchProperty($objectType, $propertyId)->getOptions();\n }\n\n /**\n * @return array<array{id:string, label:string, deleted:bool}>\n */\n public function fetchCallDispositions(): array\n {\n /** @var Response $response */\n $response = $this->getInstance()->engagements()->getCallDispositions();\n\n /**\n * @var array<array{\n * id:string,\n * label:string,\n * deleted: bool\n * }>\n */\n return $response->toArray();\n }\n\n /**\n * @return array<CrmFieldOption>\n */\n public function fetchOpportunityPipelineStages(): array\n {\n $stages = [];\n $apiResponse = $this->getNewInstance()->crm()->pipelines()->pipelinesApi()->getAll('deals');\n\n if ($apiResponse instanceof Error) {\n $this->log->error('[Hubspot] Failed to fetch opportunity pipelines', [\n 'reason' => $apiResponse->getMessage(),\n ]);\n\n return [];\n }\n\n foreach ($apiResponse->getResults() as $pipeline) {\n $pipelineStages = array_map(\n static function (PipelineStage $stage) {\n return [\n 'id' => $stage->getId(),\n 'label' => $stage->getLabel(),\n ];\n },\n $pipeline->getStages()\n );\n\n $stages = array_merge($stages, $pipelineStages);\n }\n\n return $stages;\n }\n\n public function fetchOpportunityPipelines(): array\n {\n $pipelines = [];\n\n try {\n $apiResponse = $this->makeRequest('/crm/v3/pipelines/deals');\n } catch (\\Exception $e) {\n $this->log->info('[Hubspot] Failed to fetch opportunity pipelines', [\n 'reason' => $e->getMessage(),\n ]);\n\n return [];\n }\n\n $response = $apiResponse->toArray();\n\n foreach ($response['results'] as $pipeline) {\n $pipelines[] = [\n 'id' => $pipeline['id'],\n 'label' => $pipeline['label'],\n ];\n }\n\n return $pipelines;\n }\n\n /**\n * @return array<CrmFieldOption>\n */\n public function fetchMeetingOutcomeFieldOptions(Field $field): array\n {\n return $field->getCrmProviderId() === 'meetingOutcome'\n ? $this->fetchMeetingOutcomeTypes()\n : $this->fetchCallActivityTypes();\n }\n\n public function fetchMeetingOutcomeTypes(): array\n {\n return $this->extractMeetingTypeOptions(\n 'https://api.hubapi.com/crm/v3/properties/meeting/hs_meeting_outcome'\n );\n }\n\n public function fetchCallActivityTypes(): array\n {\n return $this->extractMeetingTypeOptions(\n 'https://api.hubapi.com/crm/v3/properties/call/hs_activity_type'\n );\n }\n\n private function extractMeetingTypeOptions(string $endpoint): array\n {\n /** @var Response $response */\n $response = $this->getInstance()\n ->getClient()\n ->request('GET', $endpoint);\n\n /**\n * @var array<array{\n * value: string,\n * label: string,\n * displayOrder: int\n * }> $optionData\n */\n $optionData = $response->toArray()['options'] ?? [];\n\n $options = [];\n foreach ($optionData as $item) {\n $options[] = [\n 'id' => $item['value'],\n 'value' => $item['value'],\n 'label' => $item['label'],\n 'display_order' => $item['displayOrder'],\n ];\n }\n\n return $options;\n }\n\n /**\n * @return array<CrmFieldOption>\n */\n public function fetchDispositionFieldOptions(): array\n {\n $options = [];\n\n $dispositions = $this->fetchCallDispositions();\n\n foreach ($dispositions as $disposition) {\n if ($disposition['deleted'] !== false) {\n continue;\n }\n\n $option['value'] = $disposition['id'];\n $option['id'] = $disposition['id'];\n $option['label'] = $disposition['label'];\n\n $options[] = $option;\n }\n\n return $options;\n }\n\n /**\n * @return array<CrmFieldOption>\n */\n public function fetchOpportunityFieldOptions(Field $field): array\n {\n if ($field->isStageField()) {\n return $this->fetchOpportunityPipelineStages();\n }\n\n if ($field->isPipelineField()) {\n return $this->fetchOpportunityPipelines();\n }\n\n return $this->fetchPropertyOptions('deals', $field->getCrmProviderId());\n }\n\n /**\n * @throws BadRequest\n * @throws HubspotException\n */\n public function makeRequest(string $endpoint, $method = 'GET', $payload = [], ?string $queryString = null)\n {\n $endpoint = self::BASE_URL . $endpoint;\n\n if ($method === 'GET') {\n $response = $this->getInstance()->getClient()?->request(\n method: $method,\n endpoint: $endpoint,\n query_string: $queryString\n );\n } else {\n $response = $this->getInstance()->getClient()->request($method, $endpoint, [\n 'json' => ($payload),\n ]);\n }\n \n $max = $response->getHeaderLine('X-HubSpot-RateLimit-Max'); // \"110\"\n $remaining = $response->getHeaderLine('X-HubSpot-RateLimit-Remaining'); // \"109\"\n $interval = $response->getHeaderLine('X-HubSpot-RateLimit-Interval-Milliseconds'); // \"10000\"\n $body = json_decode((string) $response->getBody(), true);\n \n \\Illuminate\\Support\\Facades\\Log::channel('custom_channel')->info('$max ' . PHP_EOL . print_r($max, true));\n \\Illuminate\\Support\\Facades\\Log::channel('custom_channel')->info('$remaining ' . PHP_EOL . print_r($remaining, true));\n \\Illuminate\\Support\\Facades\\Log::channel('custom_channel')->info('$interval ' . PHP_EOL . print_r($interval, true));\n \\Illuminate\\Support\\Facades\\Log::channel('custom_channel')->info('$interval ' . PHP_EOL . print_r($interval, true));\n \n return $response;\n }\n\n /**\n * @throws BadRequest\n * @throws HubspotException\n */\n public function createMeeting(array $payload): Response\n {\n $endpoint = '/crm/v3/objects/meetings';\n\n return $this->makeRequest($endpoint, 'POST', $payload);\n }\n\n /**\n * @throws BadRequest\n * @throws HubspotException\n */\n public function updateMeeting(string $meetingId, array $payload): Response\n {\n $endpoint = '/crm/v3/objects/meetings/' . $meetingId;\n\n return $this->makeRequest($endpoint, 'PATCH', $payload);\n }\n\n /**\n * @throws \\Exception\n */\n public function createNote(\n string $body,\n string $ownerId,\n int $timestamp,\n string $objectId,\n NoteObject $noteObject\n ): ?string {\n try {\n $noteInput = new SimplePublicObjectInput([\n 'properties' => [\n 'hs_note_body' => $body,\n 'hubspot_owner_id' => $ownerId,\n 'hs_timestamp' => $timestamp,\n ],\n ]);\n\n // Create note\n $note = $this->getNewInstance()->crm()->objects()->basicApi()->create('note', $noteInput);\n\n $this->getNewInstance()->crm()->objects()->associationsApi()->create(\n 'note',\n $note->getId(),\n $this->getNoteObject($noteObject),\n $objectId,\n $this->getNoteAssociationType($noteObject),\n );\n\n return $note->getId();\n } catch (\\Exception $e) {\n $this->log->error('[Hubspot] Failed to create note', [\n 'objectId' => $objectId,\n 'noteObject' => $noteObject->getObjectType(),\n 'reason' => $e->getMessage(),\n ]);\n\n \\Sentry::captureException($e);\n }\n\n return null;\n }\n\n public function updateEngagement(string $objectId, array $engagement, array $metadata): void\n {\n $this->getInstance()->engagements()->update($objectId, $engagement, $metadata);\n }\n\n public function getEngagementData(string $engagementId): array\n {\n $engagement = $this->getInstance()->engagements()->get($engagementId);\n\n return $engagement->toArray();\n }\n\n public function createEngagement(array $engagement, array $associations, array $metadata): Response\n {\n return $this->getInstance()\n ->engagements()\n ->create($engagement, $associations, $metadata);\n }\n\n public function isUnauthorizedException(\\Exception $e): bool\n {\n // Check for specific HubSpot API exception types first\n if ($e instanceof BadRequest) {\n // BadRequest can contain 401 status codes\n return $e->getCode() === 401;\n }\n\n // Check for HTTP client exceptions with status codes\n if ($e instanceof \\GuzzleHttp\\Exception\\RequestException && $e->hasResponse()) {\n $response = $e->getResponse();\n if ($response !== null) {\n return $response->getStatusCode() === 401;\n }\n }\n\n // Check for Guzzle HTTP exceptions\n if ($e instanceof \\GuzzleHttp\\Exception\\ClientException) {\n return $e->getCode() === 401;\n }\n\n // Fallback to string matching as last resort, but be more specific\n $message = strtolower($e->getMessage());\n\n return str_contains($message, '401 unauthorized') ||\n str_contains($message, 'http 401') ||\n str_contains($message, 'status code 401') ||\n (preg_match('/\\b401\\b/', $message) && str_contains($message, 'unauthorized'));\n }\n\n /**\n * Validates and refreshes the access token if needed before API requests.\n * This ensures long-running processes don't fail due to token expiration.\n *\n * @throws SocialAccountTokenInvalidException\n */\n public function ensureValidToken(): void\n {\n if ($this->oauthAccount === null) {\n return;\n }\n\n $newToken = $this->tokenManager->ensureValidToken($this->oauthAccount);\n if ($newToken !== null) {\n $this->accessToken = $newToken;\n }\n }\n\n public function getConfig()\n {\n return $this->config;\n }\n\n // returns only active (archived=false)\n public function getOwners(): array\n {\n return $this->getNewInstance()->crm()->owners()->getAll();\n }\n\n /**\n * @param bool $archived\n *\n * @return array<Owner>|[]\n */\n public function getOwnersArchived(bool $archived = true): array\n {\n $endpoint = '/crm/v3/owners';\n $queryParams = [\n 'archived' => $archived ? 'true' : 'false',\n ];\n $queryString = http_build_query($queryParams);\n\n $owners = [];\n\n try {\n $response = $this->makeRequest(endpoint: $endpoint, queryString: $queryString);\n $responseData = $response?->toArray();\n\n foreach ($responseData['results'] as $result) {\n try {\n $owners[] = Owner::create($result);\n } catch (Throwable $e) {\n $this->log->error('[HubSpot] Failed to process owner data', [\n 'result' => $result,\n 'error' => $e->getMessage(),\n ]);\n\n continue;\n }\n }\n } catch (Throwable $e) {\n $this->log->error('HubSpot] Failed to fetch owners', [\n 'archived' => $archived,\n 'error' => $e->getMessage(),\n ]);\n\n return [];\n }\n\n return $owners;\n }\n\n public function getMeeting(string $engagementId): ObjectWithAssociations\n {\n return $this->getNewInstance()->crm()->objects()->basicApi()\n ->getById('meeting', $engagementId, null, 'contact,company,deal');\n }\n\n public function deleteEngagement(string $engagementId): void\n {\n $this->getInstance()->engagements()->delete((int) $engagementId);\n }\n\n public function getAssociationsData(array $ids, string $fromObject, string $toObject): array\n {\n $associationData = [];\n $idChunks = array_chunk($ids, self::ASSOCIATIONS_BATCH_SIZE_LIMIT);\n\n foreach ($idChunks as $idChunk) {\n try {\n $batchInput = new \\HubSpot\\Client\\Crm\\Associations\\Model\\BatchInputPublicObjectId();\n $batchInput->setInputs(array_map(function ($id) {\n $publicObjectId = new \\HubSpot\\Client\\Crm\\Associations\\Model\\PublicObjectId();\n $publicObjectId->setId($id);\n\n return $publicObjectId;\n }, $idChunk));\n\n $associatedObjectsData = $this\n ->getNewInstance()\n ->crm()\n ->associations()\n ->batchApi()\n ->read($fromObject, $toObject, $batchInput);\n\n if ($associatedObjectsData instanceof \\HubSpot\\Client\\Crm\\Associations\\Model\\BatchResponsePublicAssociationMulti) {\n foreach ($associatedObjectsData->getResults() as $association) {\n $from = $association->getFrom()->getId();\n $toAssociations = $association->getTo();\n\n if (! empty($toAssociations)) {\n $associationData[$from] = array_map(function ($item) {\n return $item->getId();\n }, $toAssociations);\n }\n }\n }\n } catch (\\Exception $e) {\n $this->log->error('[Hubspot] Failed to fetch associations', [\n 'from_object' => $fromObject,\n 'to_object' => $toObject,\n 'reason' => $e->getMessage(),\n ]);\n }\n }\n\n return $associationData;\n }\n\n /**\n * @throws \\Exception\n */\n private function getNoteAssociationType(NoteObject $noteObject): string\n {\n return match($noteObject) {\n NoteObject::Opportunity => 'note_to_deal',\n NoteObject::Lead, NoteObject::Contact => 'note_to_contact', // or 'note_to_lead' if your portal supports it\n NoteObject::Account => 'note_to_company',\n NoteObject::Call, NoteObject::Event => throw new \\Exception('Not supported'),\n };\n }\n\n /**\n * @throws \\Exception\n */\n private function getNoteObject(NoteObject $noteObject): string\n {\n return match($noteObject) {\n NoteObject::Opportunity => 'deal',\n NoteObject::Lead, NoteObject::Contact => 'contact',\n NoteObject::Account => 'company',\n NoteObject::Call, NoteObject::Event => throw new \\Exception('Not supported'),\n };\n }\n\n public function addAssociations(string $objectType, string $associationType, array $payload): Response\n {\n $endpoint = \"/crm/v4/associations/$objectType/$associationType/batch/create\";\n\n return $this->makeRequest($endpoint, 'POST', $payload);\n }\n\n public function removeAssociations(string $objectType, string $associationType, array $payload): Response\n {\n $endpoint = \"/crm/v4/associations/$objectType/$associationType/batch/archive\";\n\n return $this->makeRequest($endpoint, 'POST', $payload);\n }\n}","role_description":"text entry area","is_enabled":true,"is_focused":true,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"Project","depth":3,"on_screen":false,"role_description":"text"},{"role":"AXButton","text":"Project","depth":3,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"New File or Directory…","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.018055556,"height":0.026666667},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Expand Selected","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.018055556,"height":0.026666667},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Collapse All","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.018055556,"height":0.026666667},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Options","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.018055556,"height":0.026666667},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Hide","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.018055556,"height":0.026666667},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false}]...
|
6582720202572488457
|
5531537304406333540
|
click
|
accessibility
|
NULL
|
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
Execute
Explain Plan
Browse Query History
View Parameters
Open Query Execution Settings…
In-Editor Results
Tx: Auto
Cancel Running Statements
Playground
jiminny
Sync Changes
Hide This Notification
Code changed:
Hide
6
1
6
Previous Highlighted Error
Next Highlighted Error
# [PASSWORD_DOTS] HS [PASSWORD_DOTS]
select * from teams where id = 2; # 2
select * from features; # 2
select * from team_features where team_id = 2; # 2
select * from crm_configurations where id = 2; # 2
select * from users where team_id = 2; #
select * from playbooks where team_id = 2; # event 38
select * from playbook_categories where playbook_id = 38; #
SELECT * FROM activities WHERE crm_configuration_id = 2 and crm_provider_id is not null order by id desc;
[URL_WITH_CREDENTIALS] CrmFieldOption array{id:string, label:string, value?:string}
*/
class Client extends BaseClient implements HubspotClientInterface
{
public const string MIN_API_VERSION = '2';
public const string BASE_URL = '[URL_WITH_CREDENTIALS] T
* @param callable(): T $apiCall
* @return T
*
* @throws RateLimitException
*/
private function executeRequest(callable $apiCall)
{
if (! $this->rateLimiter->canMakeRequest($this->config)) {
$retryAfter = $this->rateLimiter->requestAvailableIn($this->config);
$this->log->warning('[Hubspot] Rate limit exceeded, deferring request', [
'team_id' => $this->config->team_id,
'config_id' => $this->config->getId(),
'retry_after' => $retryAfter,
]);
throw new RateLimitException(
'Hubspot rate limit reached for configuration ' . $this->config->getId(),
$retryAfter,
);
}
$this->rateLimiter->incrementRequestCount($this->config);
try {
return $apiCall();
} catch (Throwable $e) {
if ($this->isHubspotRateLimit($e)) {
$retryAfter = $this->parseRetryAfter($e);
$this->log->warning('[Hubspot] Received 429 from API', [
'team_id' => $this->config->team_id,
'config_id' => $this->config->getId(),
'retry_after' => $retryAfter,
'reason' => $e->getMessage(),
]);
throw new RateLimitException('Hubspot returned 429', $retryAfter, $e);
}
throw $e;
}
}
private function isHubspotRateLimit(Throwable $e): bool
{
return method_exists($e, 'getCode') && (int) $e->getCode() === 429;
}
private function parseRetryAfter(Throwable $e): int
{
if (method_exists($e, 'getResponseHeaders')) {
$headers = $e->getResponseHeaders() ?: [];
$value = $headers['Retry-After'] ?? $headers['retry-after'] ?? null;
if (is_array($value)) {
$value = $value[0] ?? null;
}
if (is_numeric($value)) {
return (int) $value;
}
}
return 10;
}
public function getMinimumApiVersion(): string
{
return self::MIN_API_VERSION;
}
public function getInstance(): Factory
{
return new Factory([
'key' => $this->accessToken,
'oauth2' => true,
'base_url' => $this->baseUrl,
]);
}
public function getNewInstance(): Discovery
{
return \HubSpot\Factory::createWithAccessToken($this->accessToken);
}
/**
* Secondly and daily limits for Hubspot API
*
* Product Tier: Free & Starter | Professional & Enterprise | API add-on (any tier)
* Burst: 100/10 seconds | 150/10 seconds | 200/10 seconds
* Daily: 250,000 | 500,000 | 1,000,000
*
* Official documentation states: The search endpoints are rate limited to five requests per second.
* Since with 5 RPS were still hitting secondly rate limits we lowered it to 4
*/
public function getPaginatedData(array $payload, string $type, int $offset = 0): array
{
$total = 0;
$lastId = null;
$rows = [];
foreach ($this->getPaginatedDataGenerator($payload, $type, $offset, $total, $lastId) as $row) {
$rows[] = $row;
}
return ['results' => $rows, 'total' => $total, 'last_record' => $lastId];
}
/**
* @throws HubspotException
* @throws SocialAccountTokenInvalidException
* @throws BadRequest
*/
public function getPaginatedDataGenerator(
array $payload,
string $type,
int $offset = 0,
int &$total = 0,
?string &$lastRecordId = null
): \Generator {
return $this->paginationService->getPaginatedDataGenerator(
$this,
$payload,
$type,
$offset,
$total,
$lastRecordId
);
}
/**
* @throws DealApiException
* @throws CrmException
*/
public function getOpportunityById(string $crmId, array $fields): array
{
try {
$deal = $this->executeRequest(fn () => $this->getNewInstance()->crm()->deals()->basicApi()->getById(
$crmId,
implode(',', $fields),
'companies,contacts'
));
} catch (DealApiException $e) {
$this->log->info('[Hubspot] Failed to fetch opportunity', [
'crm_id' => $crmId,
'reason' => $e->getMessage(),
]);
throw $e;
}
if (! $deal instanceof DealWithAssociations) {
throw new CrmException('Deal not found');
}
return [
'id' => $deal->getId(),
'properties' => $deal->getProperties(),
'associations' => $deal->getAssociations(),
];
}
/**
* Generic batch read method for HubSpot objects
*
* @param string $objectType The object type ('deals', 'companies', 'contacts')
* @param array<string> $crmIds Array of HubSpot object IDs (max 100)
* @param array<string> $fields Array of property names to fetch
*
* @return array<string, array> Array keyed by CRM ID with object data
*/
private function batchReadObjects(string $objectType, array $crmIds, array $fields): array
{
if (empty($crmIds)) {
return [];
}
$this->validateBatchSize($objectType, $crmIds);
$this->ensureValidToken();
try {
$batchConfig = $this->createBatchConfiguration($objectType);
$batchReadRequest = $this->prepareBatchRequest($batchConfig, $crmIds, $fields);
$response = $batchConfig['api']->read($batchReadRequest);
$this->validateApiResponse($response, $objectType);
$results = $this->processApiResults($response);
$this->logBatchResults($objectType, $crmIds, $results);
return $results;
} catch (\Throwable $e) {
$this->handleBatchError($e, $objectType, $crmIds);
}
}
private function validateBatchSize(string $objectType, array $crmIds): void
{
if (count($crmIds) > 100) {
throw new \InvalidArgumentException("Batch size cannot exceed 100 {$objectType}");
}
}
private function createBatchConfiguration(string $objectType): array
{
$configurations = [
'deals' => [
'batchReadRequest' => new \HubSpot\Client\Crm\Deals\Model\BatchReadInputSimplePublicObjectId(),
'inputClass' => \HubSpot\Client\Crm\Deals\Model\SimplePublicObjectId::class,
'api' => $this->getNewInstance()->crm()->deals()->batchApi(),
],
'companies' => [
'batchReadRequest' => new \HubSpot\Client\Crm\Companies\Model\BatchReadInputSimplePublicObjectId(),
'inputClass' => \HubSpot\Client\Crm\Companies\Model\SimplePublicObjectId::class,
'api' => $this->getNewInstance()->crm()->companies()->batchApi(),
],
'contacts' => [
'batchReadRequest' => new \HubSpot\Client\Crm\Contacts\Model\BatchReadInputSimplePublicObjectId(),
'inputClass' => \HubSpot\Client\Crm\Contacts\Model\SimplePublicObjectId::class,
'api' => $this->getNewInstance()->crm()->contacts()->batchApi(),
],
];
if (! isset($configurations[$objectType])) {
throw new \InvalidArgumentException("Unsupported object type: {$objectType}");
}
return $configurations[$objectType];
}
private function prepareBatchRequest(array $batchConfig, array $crmIds, array $fields): object
{
$batchReadRequest = $batchConfig['batchReadRequest'];
$inputClass = $batchConfig['inputClass'];
$inputs = array_map(function ($crmId) use ($inputClass) {
$input = new $inputClass();
$input->setId($crmId);
return $input;
}, $crmIds);
$batchReadRequest->setInputs($inputs);
$batchReadRequest->setProperties($fields);
return $batchReadRequest;
}
private function validateApiResponse($response, string $objectType): void
{
if (! $response) {
throw new CrmException("HubSpot API returned null response for {$objectType} batch read");
}
}
private function processApiResults($response): array
{
$results = [];
$responseResults = $response->getResults();
if ($responseResults) {
foreach ($responseResults as $object) {
if ($object && $object->getId()) {
$results[$object->getId()] = [
'id' => $object->getId(),
'properties' => $object->getProperties() ?: [],
];
}
}
}
return $results;
}
private function logBatchResults(string $objectType, array $crmIds, array $results): void
{
$this->log->info("[HubSpot] Batch fetched {$objectType}", [
'requested_count' => count($crmIds),
'returned_count' => count($results),
'crm_ids' => $crmIds,
]);
}
private function handleBatchError(\Throwable $e, string $objectType, array $crmIds): void
{
$errorMessage = $e->getMessage() ?: 'Unknown error';
$errorTrace = $e->getTraceAsString() ?: 'No trace available';
$this->log->error("[HubSpot] Failed to batch fetch {$objectType}", [
'crm_ids' => $crmIds,
'error' => $errorMessage,
'trace' => $errorTrace,
]);
throw new CrmException("Failed to batch fetch {$objectType}: " . $errorMessage);
}
/**
* Batch read multiple opportunities by their CRM IDs
*
* @param array<string> $crmIds Array of HubSpot deal IDs (max 100)
* @param array<string> $fields Array of property names to fetch
*
* @return array<string, array> Array keyed by CRM ID with opportunity data
*/
public function getOpportunitiesByIds(array $crmIds, array $fields): array
{
return $this->batchReadObjects('deals', $crmIds, $fields);
}
/**
* Batch read multiple companies by their CRM IDs
*
* @param array<string> $crmIds Array of HubSpot company IDs (max 100)
* @param array<string> $fields Array of property names to fetch
*
* @return array<string, array> Array keyed by CRM ID with company data
*/
public function getCompaniesByIds(array $crmIds, array $fields): array
{
return $this->batchReadObjects('companies', $crmIds, $fields);
}
/**
* Batch read multiple contacts by their CRM IDs
*
* @param array<string> $crmIds Array of HubSpot contact IDs (max 100)
* @param array<string> $fields Array of property names to fetch
*
* @return array<string, array> Array keyed by CRM ID with contact data
*/
public function getContactsByIds(array $crmIds, array $fields): array
{
return $this->batchReadObjects('contacts', $crmIds, $fields);
}
/**
* @throws CompanyApiException
* @throws CrmException
*/
public function getAccountById(string $crmId, array $fields): array
{
try {
$company = $this->getNewInstance()->crm()->companies()->basicApi()->getById(
$crmId,
implode(',', $fields),
);
} catch (CompanyApiException $e) {
$this->log->info('[Hubspot] Failed to fetch account', [
'crm_id' => $crmId,
'reason' => $e->getMessage(),
]);
throw $e;
}
if (! $company instanceof CompaniesWithAssociations) {
throw new CrmException('Account not found');
}
return [
'id' => $company->getId(),
'properties' => $company->getProperties(),
];
}
/**
* @throws ContactApiException
* @throws CrmException
*/
public function getContactById(string $crmId, array $fields): array
{
try {
$contact = $this->getNewInstance()->crm()->contacts()->basicApi()->getById(
$crmId,
implode(',', $fields)
);
} catch (ContactApiException $e) {
$this->log->info('[Hubspot] Failed to fetch contact', [
'crm_id' => $crmId,
'reason' => $e->getMessage(),
]);
throw $e;
}
if (! $contact instanceof ContactsWithAssociations) {
throw new CrmException('Contact not found');
}
return [
'id' => $contact->getId(),
'properties' => $contact->getProperties(),
];
}
/**
* This is email search request that Hubspot offers as GET (more generous quota)
*/
public function getContactByEmail(string $email, array $fields = []): array
{
try {
$contact = $this->getNewInstance()->crm()->contacts()->basicApi()->getById(
$email,
implode(',', $fields),
null,
false,
'email'
);
return [
'id' => $contact->getId(),
'properties' => $contact->getProperties(),
];
} catch (ContactApiException $e) {
$this->log->info('[Hubspot] Failed to fetch contact', [
'email' => $email,
'reason' => $e->getMessage(),
]);
return [];
}
}
/**
* @throws CrmException
*/
public function fetchProperty(string $objectType, string $propertyId): Property
{
$result = $this->getNewInstance()->crm()->properties()->coreApi()->getByName($objectType, $propertyId);
if (! $result instanceof Property) {
$this->log->error('[Hubspot] Failed to fetch property', [
'object_type' => $objectType,
'property_id' => $propertyId,
'reason' => $result->getMessage(),
]);
throw new CrmException('Failed to fetch property');
}
return $result;
}
/**
* @return array<CrmFieldOption>
*/
public function fetchPropertyOptions(string $objectType, string $propertyId): array
{
/** @var array<CrmFieldOption> */
return $this->fetchProperty($objectType, $propertyId)->getOptions();
}
/**
* @return array<array{id:string, label:string, deleted:bool}>
*/
public function fetchCallDispositions(): array
{
/** @var Response $response */
$response = $this->getInstance()->engagements()->getCallDispositions();
/**
* @var array<array{
* id:string,
* label:string,
* deleted: bool
* }>
*/
return $response->toArray();
}
/**
* @return array<CrmFieldOption>
*/
public function fetchOpportunityPipelineStages(): array
{
$stages = [];
$apiResponse = $this->getNewInstance()->crm()->pipelines()->pipelinesApi()->getAll('deals');
if ($apiResponse instanceof Error) {
$this->log->error('[Hubspot] Failed to fetch opportunity pipelines', [
'reason' => $apiResponse->getMessage(),
]);
return [];
}
foreach ($apiResponse->getResults() as $pipeline) {
$pipelineStages = array_map(
static function (PipelineStage $stage) {
return [
'id' => $stage->getId(),
'label' => $stage->getLabel(),
];
},
$pipeline->getStages()
);
$stages = array_merge($stages, $pipelineStages);
}
return $stages;
}
public function fetchOpportunityPipelines(): array
{
$pipelines = [];
try {
$apiResponse = $this->makeRequest('/crm/v3/pipelines/deals');
} catch (\Exception $e) {
$this->log->info('[Hubspot] Failed to fetch opportunity pipelines', [
'reason' => $e->getMessage(),
]);
return [];
}
$response = $apiResponse->toArray();
foreach ($response['results'] as $pipeline) {
$pipelines[] = [
'id' => $pipeline['id'],
'label' => $pipeline['label'],
];
}
return $pipelines;
}
/**
* @return array<CrmFieldOption>
*/
public function fetchMeetingOutcomeFieldOptions(Field $field): array
{
return $field->getCrmProviderId() === 'meetingOutcome'
? $this->fetchMeetingOutcomeTypes()
: $this->fetchCallActivityTypes();
}
public function fetchMeetingOutcomeTypes(): array
{
return $this->extractMeetingTypeOptions(
'[URL_WITH_CREDENTIALS] Response $response */
$response = $this->getInstance()
->getClient()
->request('GET', $endpoint);
/**
* @var array<array{
* value: string,
* label: string,
* displayOrder: int
* }> $optionData
*/
$optionData = $response->toArray()['options'] ?? [];
$options = [];
foreach ($optionData as $item) {
$options[] = [
'id' => $item['value'],
'value' => $item['value'],
'label' => $item['label'],
'display_order' => $item['displayOrder'],
];
}
return $options;
}
/**
* @return array<CrmFieldOption>
*/
public function fetchDispositionFieldOptions(): array
{
$options = [];
$dispositions = $this->fetchCallDispositions();
foreach ($dispositions as $disposition) {
if ($disposition['deleted'] !== false) {
continue;
}
$option['value'] = $disposition['id'];
$option['id'] = $disposition['id'];
$option['label'] = $disposition['label'];
$options[] = $option;
}
return $options;
}
/**
* @return array<CrmFieldOption>
*/
public function fetchOpportunityFieldOptions(Field $field): array
{
if ($field->isStageField()) {
return $this->fetchOpportunityPipelineStages();
}
if ($field->isPipelineField()) {
return $this->fetchOpportunityPipelines();
}
return $this->fetchPropertyOptions('deals', $field->getCrmProviderId());
}
/**
* @throws BadRequest
* @throws HubspotException
*/
public function makeRequest(string $endpoint, $method = 'GET', $payload = [], ?string $queryString = null)
{
$endpoint = self::BASE_URL . $endpoint;
if ($method === 'GET') {
$response = $this->getInstance()->getClient()?->request(
method: $method,
endpoint: $endpoint,
query_string: $queryString
);
} else {
$response = $this->getInstance()->getClient()->request($method, $endpoint, [
'json' => ($payload),
]);
}
$max = $response->getHeaderLine('X-HubSpot-RateLimit-Max'); // "110"
$remaining = $response->getHeaderLine('X-HubSpot-RateLimit-Remaining'); // "109"
$interval = $response->getHeaderLine('X-HubSpot-RateLimit-Interval-Milliseconds'); // "10000"
$body = json_decode((string) $response->getBody(), true);
\Illuminate\Support\Facades\Log::channel('custom_channel')->info('$max ' . PHP_EOL . print_r($max, true));
\Illuminate\Support\Facades\Log::channel('custom_channel')->info('$remaining ' . PHP_EOL . print_r($remaining, true));
\Illuminate\Support\Facades\Log::channel('custom_channel')->info('$interval ' . PHP_EOL . print_r($interval, true));
\Illuminate\Support\Facades\Log::channel('custom_channel')->info('$interval ' . PHP_EOL . print_r($interval, true));
return $response;
}
/**
* @throws BadRequest
* @throws HubspotException
*/
public function createMeeting(array $payload): Response
{
$endpoint = '/crm/v3/objects/meetings';
return $this->makeRequest($endpoint, 'POST', $payload);
}
/**
* @throws BadRequest
* @throws HubspotException
*/
public function updateMeeting(string $meetingId, array $payload): Response
{
$endpoint = '/crm/v3/objects/meetings/' . $meetingId;
return $this->makeRequest($endpoint, 'PATCH', $payload);
}
/**
* @throws \Exception
*/
public function createNote(
string $body,
string $ownerId,
int $timestamp,
string $objectId,
NoteObject $noteObject
): ?string {
try {
$noteInput = new SimplePublicObjectInput([
'properties' => [
'hs_note_body' => $body,
'hubspot_owner_id' => $ownerId,
'hs_timestamp' => $timestamp,
],
]);
// Create note
$note = $this->getNewInstance()->crm()->objects()->basicApi()->create('note', $noteInput);
$this->getNewInstance()->crm()->objects()->associationsApi()->create(
'note',
$note->getId(),
$this->getNoteObject($noteObject),
$objectId,
$this->getNoteAssociationType($noteObject),
);
return $note->getId();
} catch (\Exception $e) {
$this->log->error('[Hubspot] Failed to create note', [
'objectId' => $objectId,
'noteObject' => $noteObject->getObjectType(),
'reason' => $e->getMessage(),
]);
\Sentry::captureException($e);
}
return null;
}
public function updateEngagement(string $objectId, array $engagement, array $metadata): void
{
$this->getInstance()->engagements()->update($objectId, $engagement, $metadata);
}
public function getEngagementData(string $engagementId): array
{
$engagement = $this->getInstance()->engagements()->get($engagementId);
return $engagement->toArray();
}
public function createEngagement(array $engagement, array $associations, array $metadata): Response
{
return $this->getInstance()
->engagements()
->create($engagement, $associations, $metadata);
}
public function isUnauthorizedException(\Exception $e): bool
{
// Check for specific HubSpot API exception types first
if ($e instanceof BadRequest) {
// BadRequest can contain 401 status codes
return $e->getCode() === 401;
}
// Check for HTTP client exceptions with status codes
if ($e instanceof \GuzzleHttp\Exception\RequestException && $e->hasResponse()) {
$response = $e->getResponse();
if ($response !== null) {
return $response->getStatusCode() === 401;
}
}
// Check for Guzzle HTTP exceptions
if ($e instanceof \GuzzleHttp\Exception\ClientException) {
return $e->getCode() === 401;
}
// Fallback to string matching as last resort, but be more specific
$message = strtolower($e->getMessage());
return str_contains($message, '401 unauthorized') ||
str_contains($message, 'http 401') ||
str_contains($message, 'status code 401') ||
(preg_match('/\b401\b/', $message) && str_contains($message, 'unauthorized'));
}
/**
* Validates and refreshes the access token if needed before API requests.
* This ensures long-running processes don't fail due to token expiration.
*
* @throws SocialAccountTokenInvalidException
*/
public function ensureValidToken(): void
{
if ($this->oauthAccount === null) {
return;
}
$newToken = $this->tokenManager->ensureValidToken($this->oauthAccount);
if ($newToken !== null) {
$this->accessToken = $newToken;
}
}
public function getConfig()
{
return $this->config;
}
// returns only active (archived=false)
public function getOwners(): array
{
return $this->getNewInstance()->crm()->owners()->getAll();
}
/**
* @param bool $archived
*
* @return array<Owner>|[]
*/
public function getOwnersArchived(bool $archived = true): array
{
$endpoint = '/crm/v3/owners';
$queryParams = [
'archived' => $archived ? 'true' : 'false',
];
$queryString = http_build_query($queryParams);
$owners = [];
try {
$response = $this->makeRequest(endpoint: $endpoint, queryString: $queryString);
$responseData = $response?->toArray();
foreach ($responseData['results'] as $result) {
try {
$owners[] = Owner::create($result);
} catch (Throwable $e) {
$this->log->error('[HubSpot] Failed to process owner data', [
'result' => $result,
'error' => $e->getMessage(),
]);
continue;
}
}
} catch (Throwable $e) {
$this->log->error('HubSpot] Failed to fetch owners', [
'archived' => $archived,
'error' => $e->getMessage(),
]);
return [];
}
return $owners;
}
public function getMeeting(string $engagementId): ObjectWithAssociations
{
return $this->getNewInstance()->crm()->objects()->basicApi()
->getById('meeting', $engagementId, null, 'contact,company,deal');
}
public function deleteEngagement(string $engagementId): void
{
$this->getInstance()->engagements()->delete((int) $engagementId);
}
public function getAssociationsData(array $ids, string $fromObject, string $toObject): array
{
$associationData = [];
$idChunks = array_chunk($ids, self::ASSOCIATIONS_BATCH_SIZE_LIMIT);
foreach ($idChunks as $idChunk) {
try {
$batchInput = new \HubSpot\Client\Crm\Associations\Model\BatchInputPublicObjectId();
$batchInput->setInputs(array_map(function ($id) {
$publicObjectId = new \HubSpot\Client\Crm\Associations\Model\PublicObjectId();
$publicObjectId->setId($id);
return $publicObjectId;
}, $idChunk));
$associatedObjectsData = $this
->getNewInstance()
->crm()
->associations()
->batchApi()
->read($fromObject, $toObject, $batchInput);
if ($associatedObjectsData instanceof \HubSpot\Client\Crm\Associations\Model\BatchResponsePublicAssociationMulti) {
foreach ($associatedObjectsData->getResults() as $association) {
$from = $association->getFrom()->getId();
$toAssociations = $association->getTo();
if (! empty($toAssociations)) {
$associationData[$from] = array_map(function ($item) {
return $item->getId();
}, $toAssociations);
}
}
}
} catch (\Exception $e) {
$this->log->error('[Hubspot] Failed to fetch associations', [
'from_object' => $fromObject,
'to_object' => $toObject,
'reason' => $e->getMessage(),
]);
}
}
return $associationData;
}
/**
* @throws \Exception
*/
private function getNoteAssociationType(NoteObject $noteObject): string
{
return match($noteObject) {
NoteObject::Opportunity => 'note_to_deal',
NoteObject::Lead, NoteObject::Contact => 'note_to_contact', // or 'note_to_lead' if your portal supports it
NoteObject::Account => 'note_to_company',
NoteObject::Call, NoteObject::Event => throw new \Exception('Not supported'),
};
}
/**
* @throws \Exception
*/
private function getNoteObject(NoteObject $noteObject): string
{
return match($noteObject) {
NoteObject::Opportunity => 'deal',
NoteObject::Lead, NoteObject::Contact => 'contact',
NoteObject::Account => 'company',
NoteObject::Call, NoteObject::Event => throw new \Exception('Not supported'),
};
}
public function addAssociations(string $objectType, string $associationType, array $payload): Response
{
$endpoint = "/crm/v4/associations/$objectType/$associationType/batch/create";
return $this->makeRequest($endpoint, 'POST', $payload);
}
public function removeAssociations(string $objectType, string $associationType, array $payload): Response
{
$endpoint = "/crm/v4/associations/$objectType/$associationType/batch/archive";
return $this->makeRequest($endpoint, 'POST', $payload);
}
}
Project
Project
New File or Directory…
Expand Selected
Collapse All
Options
Hide...
|
2683
|
NULL
|
NULL
|
NULL
|
|
2686
|
112
|
16
|
2026-05-07T11:35:34.463534+00:00
|
/Users/lukas/.screenpipe/data/data/2026-05-07/1778 /Users/lukas/.screenpipe/data/data/2026-05-07/1778153734463_m2.jpg...
|
PhpStorm
|
faVsco.js – Hubspot/Client.php
|
True
|
NULL
|
monitor_2
|
NULL
|
NULL
|
NULL
|
NULL
|
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
Execute
Explain Plan
Browse Query History
View Parameters
Open Query Execution Settings…
In-Editor Results
Tx: Auto
Cancel Running Statements
Playground
jiminny
Sync Changes
Hide This Notification
Code changed:
Hide
6
1
6
Previous Highlighted Error
Next Highlighted Error
# [PASSWORD_DOTS] HS [PASSWORD_DOTS]
select * from teams where id = 2; # 2
select * from features; # 2
select * from team_features where team_id = 2; # 2
select * from crm_configurations where id = 2; # 2
select * from users where team_id = 2; #
select * from playbooks where team_id = 2; # event 38
select * from playbook_categories where playbook_id = 38; #
SELECT * FROM activities WHERE crm_configuration_id = 2 and crm_provider_id is not null order by id desc;
[URL_WITH_CREDENTIALS] CrmFieldOption array{id:string, label:string, value?:string}
*/
class Client extends BaseClient implements HubspotClientInterface
{
public const string MIN_API_VERSION = '2';
public const string BASE_URL = '[URL_WITH_CREDENTIALS] T
* @param callable(): T $apiCall
* @return T
*
* @throws RateLimitException
*/
private function executeRequest(callable $apiCall)
{
if (! $this->rateLimiter->canMakeRequest($this->config)) {
$retryAfter = $this->rateLimiter->requestAvailableIn($this->config);
$this->log->warning('[Hubspot] Rate limit exceeded, deferring request', [
'team_id' => $this->config->team_id,
'config_id' => $this->config->getId(),
'retry_after' => $retryAfter,
]);
throw new RateLimitException(
'Hubspot rate limit reached for configuration ' . $this->config->getId(),
$retryAfter,
);
}
$this->rateLimiter->incrementRequestCount($this->config);
try {
return $apiCall();
} catch (Throwable $e) {
if ($this->isHubspotRateLimit($e)) {
$retryAfter = $this->parseRetryAfter($e);
$this->log->warning('[Hubspot] Received 429 from API', [
'team_id' => $this->config->team_id,
'config_id' => $this->config->getId(),
'retry_after' => $retryAfter,
'reason' => $e->getMessage(),
]);
throw new RateLimitException('Hubspot returned 429', $retryAfter, $e);
}
throw $e;
}
}
private function isHubspotRateLimit(Throwable $e): bool
{
return method_exists($e, 'getCode') && (int) $e->getCode() === 429;
}
private function parseRetryAfter(Throwable $e): int
{
if (method_exists($e, 'getResponseHeaders')) {
$headers = $e->getResponseHeaders() ?: [];
$value = $headers['Retry-After'] ?? $headers['retry-after'] ?? null;
if (is_array($value)) {
$value = $value[0] ?? null;
}
if (is_numeric($value)) {
return (int) $value;
}
}
return 10;
}
public function getMinimumApiVersion(): string
{
return self::MIN_API_VERSION;
}
public function getInstance(): Factory
{
return new Factory([
'key' => $this->accessToken,
'oauth2' => true,
'base_url' => $this->baseUrl,
]);
}
public function getNewInstance(): Discovery
{
return \HubSpot\Factory::createWithAccessToken($this->accessToken);
}
/**
* Secondly and daily limits for Hubspot API
*
* Product Tier: Free & Starter | Professional & Enterprise | API add-on (any tier)
* Burst: 100/10 seconds | 150/10 seconds | 200/10 seconds
* Daily: 250,000 | 500,000 | 1,000,000
*
* Official documentation states: The search endpoints are rate limited to five requests per second.
* Since with 5 RPS were still hitting secondly rate limits we lowered it to 4
*/
public function getPaginatedData(array $payload, string $type, int $offset = 0): array
{
$total = 0;
$lastId = null;
$rows = [];
foreach ($this->getPaginatedDataGenerator($payload, $type, $offset, $total, $lastId) as $row) {
$rows[] = $row;
}
return ['results' => $rows, 'total' => $total, 'last_record' => $lastId];
}
/**
* @throws HubspotException
* @throws SocialAccountTokenInvalidException
* @throws BadRequest
*/
public function getPaginatedDataGenerator(
array $payload,
string $type,
int $offset = 0,
int &$total = 0,
?string &$lastRecordId = null
): \Generator {
return $this->paginationService->getPaginatedDataGenerator(
$this,
$payload,
$type,
$offset,
$total,
$lastRecordId
);
}
/**
* @throws DealApiException
* @throws CrmException
*/
public function getOpportunityById(string $crmId, array $fields): array
{
try {
$deal = $this->executeRequest(fn () => $this->getNewInstance()->crm()->deals()->basicApi()->getById(
$crmId,
implode(',', $fields),
'companies,contacts'
));
} catch (DealApiException $e) {
$this->log->info('[Hubspot] Failed to fetch opportunity', [
'crm_id' => $crmId,
'reason' => $e->getMessage(),
]);
throw $e;
}
if (! $deal instanceof DealWithAssociations) {
throw new CrmException('Deal not found');
}
return [
'id' => $deal->getId(),
'properties' => $deal->getProperties(),
'associations' => $deal->getAssociations(),
];
}
/**
* Generic batch read method for HubSpot objects
*
* @param string $objectType The object type ('deals', 'companies', 'contacts')
* @param array<string> $crmIds Array of HubSpot object IDs (max 100)
* @param array<string> $fields Array of property names to fetch
*
* @return array<string, array> Array keyed by CRM ID with object data
*/
private function batchReadObjects(string $objectType, array $crmIds, array $fields): array
{
if (empty($crmIds)) {
return [];
}
$this->validateBatchSize($objectType, $crmIds);
$this->ensureValidToken();
try {
$batchConfig = $this->createBatchConfiguration($objectType);
$batchReadRequest = $this->prepareBatchRequest($batchConfig, $crmIds, $fields);
$response = $batchConfig['api']->read($batchReadRequest);
$this->validateApiResponse($response, $objectType);
$results = $this->processApiResults($response);
$this->logBatchResults($objectType, $crmIds, $results);
return $results;
} catch (\Throwable $e) {
$this->handleBatchError($e, $objectType, $crmIds);
}
}
private function validateBatchSize(string $objectType, array $crmIds): void
{
if (count($crmIds) > 100) {
throw new \InvalidArgumentException("Batch size cannot exceed 100 {$objectType}");
}
}
private function createBatchConfiguration(string $objectType): array
{
$configurations = [
'deals' => [
'batchReadRequest' => new \HubSpot\Client\Crm\Deals\Model\BatchReadInputSimplePublicObjectId(),
'inputClass' => \HubSpot\Client\Crm\Deals\Model\SimplePublicObjectId::class,
'api' => $this->getNewInstance()->crm()->deals()->batchApi(),
],
'companies' => [
'batchReadRequest' => new \HubSpot\Client\Crm\Companies\Model\BatchReadInputSimplePublicObjectId(),
'inputClass' => \HubSpot\Client\Crm\Companies\Model\SimplePublicObjectId::class,
'api' => $this->getNewInstance()->crm()->companies()->batchApi(),
],
'contacts' => [
'batchReadRequest' => new \HubSpot\Client\Crm\Contacts\Model\BatchReadInputSimplePublicObjectId(),
'inputClass' => \HubSpot\Client\Crm\Contacts\Model\SimplePublicObjectId::class,
'api' => $this->getNewInstance()->crm()->contacts()->batchApi(),
],
];
if (! isset($configurations[$objectType])) {
throw new \InvalidArgumentException("Unsupported object type: {$objectType}");
}
return $configurations[$objectType];
}
private function prepareBatchRequest(array $batchConfig, array $crmIds, array $fields): object
{
$batchReadRequest = $batchConfig['batchReadRequest'];
$inputClass = $batchConfig['inputClass'];
$inputs = array_map(function ($crmId) use ($inputClass) {
$input = new $inputClass();
$input->setId($crmId);
return $input;
}, $crmIds);
$batchReadRequest->setInputs($inputs);
$batchReadRequest->setProperties($fields);
return $batchReadRequest;
}
private function validateApiResponse($response, string $objectType): void
{
if (! $response) {
throw new CrmException("HubSpot API returned null response for {$objectType} batch read");
}
}
private function processApiResults($response): array
{
$results = [];
$responseResults = $response->getResults();
if ($responseResults) {
foreach ($responseResults as $object) {
if ($object && $object->getId()) {
$results[$object->getId()] = [
'id' => $object->getId(),
'properties' => $object->getProperties() ?: [],
];
}
}
}
return $results;
}
private function logBatchResults(string $objectType, array $crmIds, array $results): void
{
$this->log->info("[HubSpot] Batch fetched {$objectType}", [
'requested_count' => count($crmIds),
'returned_count' => count($results),
'crm_ids' => $crmIds,
]);
}
private function handleBatchError(\Throwable $e, string $objectType, array $crmIds): void
{
$errorMessage = $e->getMessage() ?: 'Unknown error';
$errorTrace = $e->getTraceAsString() ?: 'No trace available';
$this->log->error("[HubSpot] Failed to batch fetch {$objectType}", [
'crm_ids' => $crmIds,
'error' => $errorMessage,
'trace' => $errorTrace,
]);
throw new CrmException("Failed to batch fetch {$objectType}: " . $errorMessage);
}
/**
* Batch read multiple opportunities by their CRM IDs
*
* @param array<string> $crmIds Array of HubSpot deal IDs (max 100)
* @param array<string> $fields Array of property names to fetch
*
* @return array<string, array> Array keyed by CRM ID with opportunity data
*/
public function getOpportunitiesByIds(array $crmIds, array $fields): array
{
return $this->batchReadObjects('deals', $crmIds, $fields);
}
/**
* Batch read multiple companies by their CRM IDs
*
* @param array<string> $crmIds Array of HubSpot company IDs (max 100)
* @param array<string> $fields Array of property names to fetch
*
* @return array<string, array> Array keyed by CRM ID with company data
*/
public function getCompaniesByIds(array $crmIds, array $fields): array
{
return $this->batchReadObjects('companies', $crmIds, $fields);
}
/**
* Batch read multiple contacts by their CRM IDs
*
* @param array<string> $crmIds Array of HubSpot contact IDs (max 100)
* @param array<string> $fields Array of property names to fetch
*
* @return array<string, array> Array keyed by CRM ID with contact data
*/
public function getContactsByIds(array $crmIds, array $fields): array
{
return $this->batchReadObjects('contacts', $crmIds, $fields);
}
/**
* @throws CompanyApiException
* @throws CrmException
*/
public function getAccountById(string $crmId, array $fields): array
{
try {
$company = $this->getNewInstance()->crm()->companies()->basicApi()->getById(
$crmId,
implode(',', $fields),
);
} catch (CompanyApiException $e) {
$this->log->info('[Hubspot] Failed to fetch account', [
'crm_id' => $crmId,
'reason' => $e->getMessage(),
]);
throw $e;
}
if (! $company instanceof CompaniesWithAssociations) {
throw new CrmException('Account not found');
}
return [
'id' => $company->getId(),
'properties' => $company->getProperties(),
];
}
/**
* @throws ContactApiException
* @throws CrmException
*/
public function getContactById(string $crmId, array $fields): array
{
try {
$contact = $this->getNewInstance()->crm()->contacts()->basicApi()->getById(
$crmId,
implode(',', $fields)
);
} catch (ContactApiException $e) {
$this->log->info('[Hubspot] Failed to fetch contact', [
'crm_id' => $crmId,
'reason' => $e->getMessage(),
]);
throw $e;
}
if (! $contact instanceof ContactsWithAssociations) {
throw new CrmException('Contact not found');
}
return [
'id' => $contact->getId(),
'properties' => $contact->getProperties(),
];
}
/**
* This is email search request that Hubspot offers as GET (more generous quota)
*/
public function getContactByEmail(string $email, array $fields = []): array
{
try {
$contact = $this->getNewInstance()->crm()->contacts()->basicApi()->getById(
$email,
implode(',', $fields),
null,
false,
'email'
);
return [
'id' => $contact->getId(),
'properties' => $contact->getProperties(),
];
} catch (ContactApiException $e) {
$this->log->info('[Hubspot] Failed to fetch contact', [
'email' => $email,
'reason' => $e->getMessage(),
]);
return [];
}
}
/**
* @throws CrmException
*/
public function fetchProperty(string $objectType, string $propertyId): Property
{
$result = $this->getNewInstance()->crm()->properties()->coreApi()->getByName($objectType, $propertyId);
if (! $result instanceof Property) {
$this->log->error('[Hubspot] Failed to fetch property', [
'object_type' => $objectType,
'property_id' => $propertyId,
'reason' => $result->getMessage(),
]);
throw new CrmException('Failed to fetch property');
}
return $result;
}
/**
* @return array<CrmFieldOption>
*/
public function fetchPropertyOptions(string $objectType, string $propertyId): array
{
/** @var array<CrmFieldOption> */
return $this->fetchProperty($objectType, $propertyId)->getOptions();
}
/**
* @return array<array{id:string, label:string, deleted:bool}>
*/
public function fetchCallDispositions(): array
{
/** @var Response $response */
$response = $this->getInstance()->engagements()->getCallDispositions();
/**
* @var array<array{
* id:string,
* label:string,
* deleted: bool
* }>
*/
return $response->toArray();
}
/**
* @return array<CrmFieldOption>
*/
public function fetchOpportunityPipelineStages(): array
{
$stages = [];
$apiResponse = $this->getNewInstance()->crm()->pipelines()->pipelinesApi()->getAll('deals');
if ($apiResponse instanceof Error) {
$this->log->error('[Hubspot] Failed to fetch opportunity pipelines', [
'reason' => $apiResponse->getMessage(),
]);
return [];
}
foreach ($apiResponse->getResults() as $pipeline) {
$pipelineStages = array_map(
static function (PipelineStage $stage) {
return [
'id' => $stage->getId(),
'label' => $stage->getLabel(),
];
},
$pipeline->getStages()
);
$stages = array_merge($stages, $pipelineStages);
}
return $stages;
}
public function fetchOpportunityPipelines(): array
{
$pipelines = [];
try {
$apiResponse = $this->makeRequest('/crm/v3/pipelines/deals');
} catch (\Exception $e) {
$this->log->info('[Hubspot] Failed to fetch opportunity pipelines', [
'reason' => $e->getMessage(),
]);
return [];
}
$response = $apiResponse->toArray();
foreach ($response['results'] as $pipeline) {
$pipelines[] = [
'id' => $pipeline['id'],
'label' => $pipeline['label'],
];
}
return $pipelines;
}
/**
* @return array<CrmFieldOption>
*/
public function fetchMeetingOutcomeFieldOptions(Field $field): array
{
return $field->getCrmProviderId() === 'meetingOutcome'
? $this->fetchMeetingOutcomeTypes()
: $this->fetchCallActivityTypes();
}
public function fetchMeetingOutcomeTypes(): array
{
return $this->extractMeetingTypeOptions(
'[URL_WITH_CREDENTIALS] Response $response */
$response = $this->getInstance()
->getClient()
->request('GET', $endpoint);
/**
* @var array<array{
* value: string,
* label: string,
* displayOrder: int
* }> $optionData
*/
$optionData = $response->toArray()['options'] ?? [];
$options = [];
foreach ($optionData as $item) {
$options[] = [
'id' => $item['value'],
'value' => $item['value'],
'label' => $item['label'],
'display_order' => $item['displayOrder'],
];
}
return $options;
}
/**
* @return array<CrmFieldOption>
*/
public function fetchDispositionFieldOptions(): array
{
$options = [];
$dispositions = $this->fetchCallDispositions();
foreach ($dispositions as $disposition) {
if ($disposition['deleted'] !== false) {
continue;
}
$option['value'] = $disposition['id'];
$option['id'] = $disposition['id'];
$option['label'] = $disposition['label'];
$options[] = $option;
}
return $options;
}
/**
* @return array<CrmFieldOption>
*/
public function fetchOpportunityFieldOptions(Field $field): array
{
if ($field->isStageField()) {
return $this->fetchOpportunityPipelineStages();
}
if ($field->isPipelineField()) {
return $this->fetchOpportunityPipelines();
}
return $this->fetchPropertyOptions('deals', $field->getCrmProviderId());
}
/**
* @throws BadRequest
* @throws HubspotException
*/
public function makeRequest(string $endpoint, $method = 'GET', $payload = [], ?string $queryString = null)
{
$endpoint = self::BASE_URL . $endpoint;
if ($method === 'GET') {
$response = $this->getInstance()->getClient()?->request(
method: $method,
endpoint: $endpoint,
query_string: $queryString
);
} else {
$response = $this->getInstance()->getClient()->request($method, $endpoint, [
'json' => ($payload),
]);
}
$max = $response->getHeaderLine('X-HubSpot-RateLimit-Max'); // "110"
$remaining = $response->getHeaderLine('X-HubSpot-RateLimit-Remaining'); // "109"
$interval = $response->getHeaderLine('X-HubSpot-RateLimit-Interval-Milliseconds'); // "10000"
$body = json_decode((string) $response->getBody(), true);
\Illuminate\Support\Facades\Log::channel('custom_channel')->info('$max ' . PHP_EOL . print_r($max, true));
\Illuminate\Support\Facades\Log::channel('custom_channel')->info('$remaining ' . PHP_EOL . print_r($remaining, true));
\Illuminate\Support\Facades\Log::channel('custom_channel')->info('$interval ' . PHP_EOL . print_r($interval, true));
\Illuminate\Support\Facades\Log::channel('custom_channel')->info('$interval ' . PHP_EOL . print_r($interval, true));
return $response;
}
/**
* @throws BadRequest
* @throws HubspotException
*/
public function createMeeting(array $payload): Response
{
$endpoint = '/crm/v3/objects/meetings';
return $this->makeRequest($endpoint, 'POST', $payload);
}
/**
* @throws BadRequest
* @throws HubspotException
*/
public function updateMeeting(string $meetingId, array $payload): Response
{
$endpoint = '/crm/v3/objects/meetings/' . $meetingId;
return $this->makeRequest($endpoint, 'PATCH', $payload);
}
/**
* @throws \Exception
*/
public function createNote(
string $body,
string $ownerId,
int $timestamp,
string $objectId,
NoteObject $noteObject
): ?string {
try {
$noteInput = new SimplePublicObjectInput([
'properties' => [
'hs_note_body' => $body,
'hubspot_owner_id' => $ownerId,
'hs_timestamp' => $timestamp,
],
]);
// Create note
$note = $this->getNewInstance()->crm()->objects()->basicApi()->create('note', $noteInput);
$this->getNewInstance()->crm()->objects()->associationsApi()->create(
'note',
$note->getId(),
$this->getNoteObject($noteObject),
$objectId,
$this->getNoteAssociationType($noteObject),
);
return $note->getId();
} catch (\Exception $e) {
$this->log->error('[Hubspot] Failed to create note', [
'objectId' => $objectId,
'noteObject' => $noteObject->getObjectType(),
'reason' => $e->getMessage(),
]);
\Sentry::captureException($e);
}
return null;
}
public function updateEngagement(string $objectId, array $engagement, array $metadata): void
{
$this->getInstance()->engagements()->update($objectId, $engagement, $metadata);
}
public function getEngagementData(string $engagementId): array
{
$engagement = $this->getInstance()->engagements()->get($engagementId);
return $engagement->toArray();
}
public function createEngagement(array $engagement, array $associations, array $metadata): Response
{
return $this->getInstance()
->engagements()
->create($engagement, $associations, $metadata);
}
public function isUnauthorizedException(\Exception $e): bool
{
// Check for specific HubSpot API exception types first
if ($e instanceof BadRequest) {
// BadRequest can contain 401 status codes
return $e->getCode() === 401;
}
// Check for HTTP client exceptions with status codes
if ($e instanceof \GuzzleHttp\Exception\RequestException && $e->hasResponse()) {
$response = $e->getResponse();
if ($response !== null) {
return $response->getStatusCode() === 401;
}
}
// Check for Guzzle HTTP exceptions
if ($e instanceof \GuzzleHttp\Exception\ClientException) {
return $e->getCode() === 401;
}
// Fallback to string matching as last resort, but be more specific
$message = strtolower($e->getMessage());
return str_contains($message, '401 unauthorized') ||
str_contains($message, 'http 401') ||
str_contains($message, 'status code 401') ||
(preg_match('/\b401\b/', $message) && str_contains($message, 'unauthorized'));
}
/**
* Validates and refreshes the access token if needed before API requests.
* This ensures long-running processes don't fail due to token expiration.
*
* @throws SocialAccountTokenInvalidException
*/
public function ensureValidToken(): void
{
if ($this->oauthAccount === null) {
return;
}
$newToken = $this->tokenManager->ensureValidToken($this->oauthAccount);
if ($newToken !== null) {
$this->accessToken = $newToken;
}
}
public function getConfig()
{
return $this->config;
}
// returns only active (archived=false)
public function getOwners(): array
{
return $this->getNewInstance()->crm()->owners()->getAll();
}
/**
* @param bool $archived
*
* @return array<Owner>|[]
*/
public function getOwnersArchived(bool $archived = true): array
{
$endpoint = '/crm/v3/owners';
$queryParams = [
'archived' => $archived ? 'true' : 'false',
];
$queryString = http_build_query($queryParams);
$owners = [];
try {
$response = $this->makeRequest(endpoint: $endpoint, queryString: $queryString);
$responseData = $response?->toArray();
foreach ($responseData['results'] as $result) {
try {
$owners[] = Owner::create($result);
} catch (Throwable $e) {
$this->log->error('[HubSpot] Failed to process owner data', [
'result' => $result,
'error' => $e->getMessage(),
]);
continue;
}
}
} catch (Throwable $e) {
$this->log->error('HubSpot] Failed to fetch owners', [
'archived' => $archived,
'error' => $e->getMessage(),
]);
return [];
}
return $owners;
}
public function getMeeting(string $engagementId): ObjectWithAssociations
{
return $this->getNewInstance()->crm()->objects()->basicApi()
->getById('meeting', $engagementId, null, 'contact,company,deal');
}
public function deleteEngagement(string $engagementId): void
{
$this->getInstance()->engagements()->delete((int) $engagementId);
}
public function getAssociationsData(array $ids, string $fromObject, string $toObject): array
{
$associationData = [];
$idChunks = array_chunk($ids, self::ASSOCIATIONS_BATCH_SIZE_LIMIT);
foreach ($idChunks as $idChunk) {
try {
$batchInput = new \HubSpot\Client\Crm\Associations\Model\BatchInputPublicObjectId();
$batchInput->setInputs(array_map(function ($id) {
$publicObjectId = new \HubSpot\Client\Crm\Associations\Model\PublicObjectId();
$publicObjectId->setId($id);
return $publicObjectId;
}, $idChunk));
$associatedObjectsData = $this
->getNewInstance()
->crm()
->associations()
->batchApi()
->read($fromObject, $toObject, $batchInput);
if ($associatedObjectsData instanceof \HubSpot\Client\Crm\Associations\Model\BatchResponsePublicAssociationMulti) {
foreach ($associatedObjectsData->getResults() as $association) {
$from = $association->getFrom()->getId();
$toAssociations = $association->getTo();
if (! empty($toAssociations)) {
$associationData[$from] = array_map(function ($item) {
return $item->getId();
}, $toAssociations);
}
}
}
} catch (\Exception $e) {
$this->log->error('[Hubspot] Failed to fetch associations', [
'from_object' => $fromObject,
'to_object' => $toObject,
'reason' => $e->getMessage(),
]);
}
}
return $associationData;
}
/**
* @throws \Exception
*/
private function getNoteAssociationType(NoteObject $noteObject): string
{
return match($noteObject) {
NoteObject::Opportunity => 'note_to_deal',
NoteObject::Lead, NoteObject::Contact => 'note_to_contact', // or 'note_to_lead' if your portal supports it
NoteObject::Account => 'note_to_company',
NoteObject::Call, NoteObject::Event => throw new \Exception('Not supported'),
};
}
/**
* @throws \Exception
*/
private function getNoteObject(NoteObject $noteObject): string
{
return match($noteObject) {
NoteObject::Opportunity => 'deal',
NoteObject::Lead, NoteObject::Contact => 'contact',
NoteObject::Account => 'company',
NoteObject::Call, NoteObject::Event => throw new \Exception('Not supported'),
};
}
public function addAssociations(string $objectType, string $associationType, array $payload): Response
{
$endpoint = "/crm/v4/associations/$objectType/$associationType/batch/create";
return $this->makeRequest($endpoint, 'POST', $payload);
}
public function removeAssociations(string $objectType, string $associationType, array $payload): Response
{
$endpoint = "/crm/v4/associations/$objectType/$associationType/batch/archive";
return $this->makeRequest($endpoint, 'POST', $payload);
}
}
Project
Project
New File or Directory…
Expand Selected
Collapse All
Options
Hide...
|
[{"role":"AXButton","text" [{"role":"AXButton","text":"Project: faVsco.js, menu","depth":5,"bounds":{"left":0.025930852,"top":0.019952115,"width":0.03856383,"height":0.025538707},"on_screen":true,"help_text":"~/jiminny/app","role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"master, menu","depth":5,"bounds":{"left":0.064494684,"top":0.019952115,"width":0.034242023,"height":0.025538707},"on_screen":true,"help_text":"Git Branch: master","role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Start Listening for PHP Debug Connections","depth":5,"bounds":{"left":0.8081782,"top":0.019952115,"width":0.011303191,"height":0.025538707},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"AskJiminnyReportActivityServiceTest","depth":6,"bounds":{"left":0.8234708,"top":0.019952115,"width":0.09208777,"height":0.025538707},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Run 'AskJiminnyReportActivityServiceTest'","depth":6,"bounds":{"left":0.9155585,"top":0.019952115,"width":0.011303191,"height":0.025538707},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Debug 'AskJiminnyReportActivityServiceTest'","depth":6,"bounds":{"left":0.9268617,"top":0.019952115,"width":0.011303191,"height":0.025538707},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"More Actions","depth":6,"bounds":{"left":0.9381649,"top":0.019952115,"width":0.011303191,"height":0.025538707},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"JetBrains AI","depth":5,"bounds":{"left":0.96609044,"top":0.019952115,"width":0.011303191,"height":0.025538707},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Search Everywhere","depth":5,"bounds":{"left":0.9773936,"top":0.019952115,"width":0.011303191,"height":0.025538707},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"IDE and Project Settings","depth":5,"bounds":{"left":0.9886968,"top":0.019952115,"width":0.011303186,"height":0.025538707},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Execute","depth":4,"bounds":{"left":0.38297874,"top":0.09896249,"width":0.008643617,"height":0.01915403},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Explain Plan","depth":4,"bounds":{"left":0.39162233,"top":0.09896249,"width":0.008643617,"height":0.01915403},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Browse Query History","depth":4,"bounds":{"left":0.40259308,"top":0.09896249,"width":0.008643617,"height":0.01915403},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"View Parameters","depth":4,"bounds":{"left":0.4112367,"top":0.09896249,"width":0.008643617,"height":0.01915403},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Open Query Execution Settings…","depth":4,"bounds":{"left":0.41988033,"top":0.09896249,"width":0.008643617,"height":0.01915403},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"In-Editor Results","depth":4,"bounds":{"left":0.43085107,"top":0.09896249,"width":0.008643617,"height":0.01915403},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Tx: Auto","depth":4,"bounds":{"left":0.4418218,"top":0.09896249,"width":0.024268618,"height":0.01915403},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Cancel Running Statements","depth":4,"bounds":{"left":0.46841756,"top":0.09896249,"width":0.008643617,"height":0.01915403},"on_screen":true,"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Playground","depth":4,"bounds":{"left":0.4793883,"top":0.09896249,"width":0.029587766,"height":0.01915403},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"jiminny","depth":4,"bounds":{"left":0.65359044,"top":0.09896249,"width":0.02825798,"height":0.01915403},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Sync Changes","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.008643617,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Hide This Notification","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.008643617,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Code changed:","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.042220745,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Hide","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.008643617,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"6","depth":4,"bounds":{"left":0.6399601,"top":0.123703115,"width":0.007978723,"height":0.015163607},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"1","depth":4,"bounds":{"left":0.6499335,"top":0.123703115,"width":0.00731383,"height":0.015163607},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"6","depth":4,"bounds":{"left":0.65924203,"top":0.123703115,"width":0.007978723,"height":0.015163607},"on_screen":true,"role_description":"text"},{"role":"AXButton","text":"Previous Highlighted Error","depth":4,"bounds":{"left":0.66888297,"top":0.12210695,"width":0.00731383,"height":0.018355945},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Next Highlighted Error","depth":4,"bounds":{"left":0.6761968,"top":0.12210695,"width":0.006981383,"height":0.018355945},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXTextArea","text":"# **************************** HS **************************************\n\nselect * from teams where id = 2; # 2\nselect * from features; # 2\nselect * from team_features where team_id = 2; # 2\nselect * from crm_configurations where id = 2; # 2\nselect * from users where team_id = 2; #\nselect * from playbooks where team_id = 2; # event 38\nselect * from playbook_categories where playbook_id = 38; #\n\nSELECT * FROM activities WHERE crm_configuration_id = 2 and crm_provider_id is not null order by id desc;\nhttps://app.hubspot.com/contacts/4392066/deal/16964514951/?engagement=96069102624\n https://app.staging.jiminny.com/playback/d5df34dc-bd66-4ff5-a7b3-8d3be30322a0\n\nSELECT * FROM activities WHERE uuid_to_bin('04fdcd0d-818f-4c53-92dc-6f18bc753ffd') = uuid;\n# 609126 softphone tr. 11241\n\nSELECT * FROM activities WHERE uuid_to_bin('6521bfcd-5a30-46e5-9f74-5440fd48befd') = uuid;\n# 608874 conference tr. 11226 crmId: 103422236596\n\nselect * from ai_prompts where transcription_id IN (11241, 11226);\nselect * from activity_summary_logs where activity_id = 608874;\n\nselect * from sidekick_settings;\nselect * from default_activity_types;\n\nselect * from crm_field_data where activity_id = 1223;\n\nselect * from crm_layouts where crm_configuration_id = 2;\nSELECT * FROM crm_layout_entities WHERE crm_layout_id IN (554);\nselect * from crm_fields where crm_configuration_id = 11 and object_type = 'event';\nSELECT * FROM crm_field_values WHERE crm_field_id IN (1455,1450);\n\nSELECT * FROM crm_field_data WHERE crm_layout_entity_id = 971;\nSELECT * FROM crm_field_data WHERE crm_layout_entity_id IN (6494,6495,6496,6497,6498,6499);\n\nSELECT\n CONCAT(u.id, CASE WHEN u.id = t.owner_id THEN ' (owner)' ELSE '' END) AS user_id,\n u.email,\n sa.*,\n t.owner_id FROM social_accounts sa\nJOIN users u\n on u.id = sa.sociable_id\nJOIN teams t on t.id = u.team_id\nWHERE u.team_id = 2 and sa.provider = 'hubspot';\n\nselect * from social_accounts where id = 1499;\n\nselect * from opportunities where team_id = 2\nand crm_provider_id IN ('51317301383');\n\nselect * from contacts where id = 85;\n\nselect * from opportunities where team_id = 2 order by id desc;\nselect * from opportunities where team_id = 2 and crm_provider_id = '51317301383'; # 5112\nselect * from opportunities where team_id = 2 and crm_provider_id = '55976759904'; # 5112\nselect * from opportunity_contacts where opportunity_id = 5117;\nselect * from crm_field_data where object_id = 1365;\nSELECT * FROM crm_fields WHERE id IN (1405, 1407, 1972, 2128);\n\nselect * from features;\nselect * from team_features where team_id IN (1);\nselect * from team_features where feature_id IN (36);\n\nSHOW CREATE TABLE opportunity_contacts;\nSELECT * FROM opportunity_contacts WHERE crm_provider_id = '111751';\n\n# $slug = 'HUBSPOT_WEBHOOK_SYNC';\n# $team = Jiminny\\Models\\Team::find(2);\n# $feature = Feature::query()->where('slug', $slug)->first();\n# TeamFeature::query()->create(['feature_id' => $feature->getId(),'team_id' => $team->getId()]);\n\n# hubspot_webhook_metrics\n\nselect * from opportunities where team_id = 2 and crm_provider_id IN ('374720564','14527423589','49908861993','50435771779'); # 1365\nSELECT * FROM opportunity_contacts WHERE opportunity_id = '414';\nSELECT * FROM opportunity_contacts WHERE crm_provider_id = '131501';\nselect * from contacts where id in (414, 464);\n\nselect * from activities where crm_configuration_id = 2;\n\nselect settings from crm_configurations where id = 11;\n\nselect * from teams; # 1, 2\nselect * from users;\nselect * from crm_configurations where id = 39;\nselect * from team_features where team_id = 2;\nselect * from features;\n# SELECT * FROM opportunities WHERE crm_configuration_id = 2\n# order by id desc;\n# and crm_provider_id = '49908861993';\n\n\nselect * from activity_providers where id IN (443, 202, 203, 227);\n\nselect * from activity_imports where id = 795889;\n\nselect c.id, c.provider, c.settings, t.* from teams t join crm_configurations c on t.id = c.team_id\nwhere c.provider = 'hubspot';\n\nselect * from crm_configurations crm JOIN teams t on crm.team_id = t.id\nwhere provider = 'hubspot';\nSELECT * FROM teams WHERE id = 31;\nSELECT * FROM users WHERE id = 257;\nSELECT * FROM opportunities WHERE team_id = 2;\n\nselect * from opportunity_contacts where opportunity_id = 5124;\nselect * from contacts where id IN (3850,3853,3851,4073,4140,4155,4480,4530,4623,5986,513,687,1806,1523,3613)\n\nselect * from activities where crm_configuration_id = 13;\n\nSELECT * FROM activities WHERE uuid_to_bin('826619ce-ec8e-4e59-8467-a01f5f6ad71e') = uuid; # 418141\n\n\nselect id, team_id, crm_provider_id from crm_configurations where provider = 'hubspot' and crm_provider_id IS NOT NULL;\nSELECT * FROM accounts WHERE team_id = 2 and crm_provider_id = '1212213464' order by id desc;\nSELECT * FROM contacts WHERE team_id = 2 and account_id = 5189 order by id desc;\nSELECT * FROM contacts WHERE team_id = 2 order by id desc;\nselect * from opportunity_contacts where contact_id = 6223;\nSELECT * FROM opportunities WHERE team_id = 2 and account_id = 5189 order by id desc;\n\nselect * from crm_profiles where crm_configuration_id = 2;\n\nselect * from activities where account_id = 46;","depth":4,"on_screen":true,"value":"# **************************** HS **************************************\n\nselect * from teams where id = 2; # 2\nselect * from features; # 2\nselect * from team_features where team_id = 2; # 2\nselect * from crm_configurations where id = 2; # 2\nselect * from users where team_id = 2; #\nselect * from playbooks where team_id = 2; # event 38\nselect * from playbook_categories where playbook_id = 38; #\n\nSELECT * FROM activities WHERE crm_configuration_id = 2 and crm_provider_id is not null order by id desc;\nhttps://app.hubspot.com/contacts/4392066/deal/16964514951/?engagement=96069102624\n https://app.staging.jiminny.com/playback/d5df34dc-bd66-4ff5-a7b3-8d3be30322a0\n\nSELECT * FROM activities WHERE uuid_to_bin('04fdcd0d-818f-4c53-92dc-6f18bc753ffd') = uuid;\n# 609126 softphone tr. 11241\n\nSELECT * FROM activities WHERE uuid_to_bin('6521bfcd-5a30-46e5-9f74-5440fd48befd') = uuid;\n# 608874 conference tr. 11226 crmId: 103422236596\n\nselect * from ai_prompts where transcription_id IN (11241, 11226);\nselect * from activity_summary_logs where activity_id = 608874;\n\nselect * from sidekick_settings;\nselect * from default_activity_types;\n\nselect * from crm_field_data where activity_id = 1223;\n\nselect * from crm_layouts where crm_configuration_id = 2;\nSELECT * FROM crm_layout_entities WHERE crm_layout_id IN (554);\nselect * from crm_fields where crm_configuration_id = 11 and object_type = 'event';\nSELECT * FROM crm_field_values WHERE crm_field_id IN (1455,1450);\n\nSELECT * FROM crm_field_data WHERE crm_layout_entity_id = 971;\nSELECT * FROM crm_field_data WHERE crm_layout_entity_id IN (6494,6495,6496,6497,6498,6499);\n\nSELECT\n CONCAT(u.id, CASE WHEN u.id = t.owner_id THEN ' (owner)' ELSE '' END) AS user_id,\n u.email,\n sa.*,\n t.owner_id FROM social_accounts sa\nJOIN users u\n on u.id = sa.sociable_id\nJOIN teams t on t.id = u.team_id\nWHERE u.team_id = 2 and sa.provider = 'hubspot';\n\nselect * from social_accounts where id = 1499;\n\nselect * from opportunities where team_id = 2\nand crm_provider_id IN ('51317301383');\n\nselect * from contacts where id = 85;\n\nselect * from opportunities where team_id = 2 order by id desc;\nselect * from opportunities where team_id = 2 and crm_provider_id = '51317301383'; # 5112\nselect * from opportunities where team_id = 2 and crm_provider_id = '55976759904'; # 5112\nselect * from opportunity_contacts where opportunity_id = 5117;\nselect * from crm_field_data where object_id = 1365;\nSELECT * FROM crm_fields WHERE id IN (1405, 1407, 1972, 2128);\n\nselect * from features;\nselect * from team_features where team_id IN (1);\nselect * from team_features where feature_id IN (36);\n\nSHOW CREATE TABLE opportunity_contacts;\nSELECT * FROM opportunity_contacts WHERE crm_provider_id = '111751';\n\n# $slug = 'HUBSPOT_WEBHOOK_SYNC';\n# $team = Jiminny\\Models\\Team::find(2);\n# $feature = Feature::query()->where('slug', $slug)->first();\n# TeamFeature::query()->create(['feature_id' => $feature->getId(),'team_id' => $team->getId()]);\n\n# hubspot_webhook_metrics\n\nselect * from opportunities where team_id = 2 and crm_provider_id IN ('374720564','14527423589','49908861993','50435771779'); # 1365\nSELECT * FROM opportunity_contacts WHERE opportunity_id = '414';\nSELECT * FROM opportunity_contacts WHERE crm_provider_id = '131501';\nselect * from contacts where id in (414, 464);\n\nselect * from activities where crm_configuration_id = 2;\n\nselect settings from crm_configurations where id = 11;\n\nselect * from teams; # 1, 2\nselect * from users;\nselect * from crm_configurations where id = 39;\nselect * from team_features where team_id = 2;\nselect * from features;\n# SELECT * FROM opportunities WHERE crm_configuration_id = 2\n# order by id desc;\n# and crm_provider_id = '49908861993';\n\n\nselect * from activity_providers where id IN (443, 202, 203, 227);\n\nselect * from activity_imports where id = 795889;\n\nselect c.id, c.provider, c.settings, t.* from teams t join crm_configurations c on t.id = c.team_id\nwhere c.provider = 'hubspot';\n\nselect * from crm_configurations crm JOIN teams t on crm.team_id = t.id\nwhere provider = 'hubspot';\nSELECT * FROM teams WHERE id = 31;\nSELECT * FROM users WHERE id = 257;\nSELECT * FROM opportunities WHERE team_id = 2;\n\nselect * from opportunity_contacts where opportunity_id = 5124;\nselect * from contacts where id IN (3850,3853,3851,4073,4140,4155,4480,4530,4623,5986,513,687,1806,1523,3613)\n\nselect * from activities where crm_configuration_id = 13;\n\nSELECT * FROM activities WHERE uuid_to_bin('826619ce-ec8e-4e59-8467-a01f5f6ad71e') = uuid; # 418141\n\n\nselect id, team_id, crm_provider_id from crm_configurations where provider = 'hubspot' and crm_provider_id IS NOT NULL;\nSELECT * FROM accounts WHERE team_id = 2 and crm_provider_id = '1212213464' order by id desc;\nSELECT * FROM contacts WHERE team_id = 2 and account_id = 5189 order by id desc;\nSELECT * FROM contacts WHERE team_id = 2 order by id desc;\nselect * from opportunity_contacts where contact_id = 6223;\nSELECT * FROM opportunities WHERE team_id = 2 and account_id = 5189 order by id desc;\n\nselect * from crm_profiles where crm_configuration_id = 2;\n\nselect * from activities where account_id = 46;","role_description":"text entry area","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Sync Changes","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.008643617,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Hide This Notification","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.008643617,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Code changed:","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.042220745,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Hide","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.008643617,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"2","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.007978723,"height":0.0},"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"60","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.010305851,"height":0.0},"on_screen":false,"role_description":"text"},{"role":"AXButton","text":"Previous Highlighted Error","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.00731383,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Next Highlighted Error","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.006981383,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXTextArea","text":"<?php\n\ndeclare(strict_types=1);\n\nnamespace Jiminny\\Services\\Crm\\Hubspot;\n\nuse HubSpot\\Client\\Crm\\Deals\\ApiException as DealApiException;\nuse HubSpot\\Client\\Crm\\Contacts\\ApiException as ContactApiException;\nuse HubSpot\\Client\\Crm\\Companies\\ApiException as CompanyApiException;\nuse HubSpot\\Client\\Crm\\Contacts\\Model\\SimplePublicObjectWithAssociations as ContactsWithAssociations;\nuse HubSpot\\Client\\Crm\\Companies\\Model\\SimplePublicObjectWithAssociations as CompaniesWithAssociations;\nuse HubSpot\\Client\\Crm\\Deals\\Model\\SimplePublicObjectWithAssociations as DealWithAssociations;\nuse HubSpot\\Client\\Crm\\Objects\\Model\\SimplePublicObjectInput;\nuse HubSpot\\Client\\Crm\\Objects\\Model\\SimplePublicObjectWithAssociations as ObjectWithAssociations;\nuse HubSpot\\Client\\Crm\\Pipelines\\Model\\Error;\nuse HubSpot\\Client\\Crm\\Pipelines\\Model\\PipelineStage;\nuse HubSpot\\Client\\Crm\\Properties\\Model\\Property;\nuse HubSpot\\Discovery\\Discovery;\nuse Jiminny\\Component\\Utility\\Service\\ProviderRateLimiter;\nuse Jiminny\\Exceptions\\CrmException;\nuse Jiminny\\Exceptions\\RateLimitException;\nuse Jiminny\\Exceptions\\SocialAccountTokenInvalidException;\nuse Jiminny\\Jobs\\Crm\\NoteObject;\nuse Jiminny\\Models\\Crm\\Field;\nuse Jiminny\\Services\\Crm\\BaseClient;\nuse Jiminny\\Services\\Crm\\Hubspot\\DTO\\Response\\Owner;\nuse Jiminny\\Services\\SocialAccountService;\nuse SevenShores\\Hubspot\\Exceptions\\BadRequest;\nuse SevenShores\\Hubspot\\Exceptions\\HubspotException;\nuse SevenShores\\Hubspot\\Factory;\nuse SevenShores\\Hubspot\\Http\\Response;\nuse Jiminny\\Services\\Crm\\Hubspot\\Pagination\\HubspotPaginationService;\nuse Throwable;\n\n/**\n * @phpstan-type CrmFieldOption array{id:string, label:string, value?:string}\n */\nclass Client extends BaseClient implements HubspotClientInterface\n{\n public const string MIN_API_VERSION = '2';\n\n public const string BASE_URL = 'https://api.hubapi.com';\n\n public const int ASSOCIATIONS_BATCH_SIZE_LIMIT = 1000;\n\n private HubspotPaginationService $paginationService;\n private HubspotTokenManager $tokenManager;\n private ProviderRateLimiter $rateLimiter;\n\n public function __construct(\n SocialAccountService $socialAccountService,\n HubspotPaginationService $paginationService,\n HubspotTokenManager $tokenManager,\n ProviderRateLimiter $rateLimiter,\n ) {\n parent::__construct($socialAccountService);\n $this->paginationService = $paginationService;\n $this->tokenManager = $tokenManager;\n $this->rateLimiter = $rateLimiter;\n\n $this->setBaseUrl(self::BASE_URL);\n $this->setVersion(self::MIN_API_VERSION);\n }\n\n /**\n * Single entry point for every HubSpot API call. Enforces the per-portal\n * rate limit configured in the rate_limits table (morphed to the current\n * Configuration) and reacts to a real 429 from HubSpot by translating it\n * into a RateLimitException carrying Retry-After.\n *\n * Wrap any outbound HubSpot call (SDK or raw HTTP) like:\n *\n * $this->executeRequest(fn () => $this->getNewInstance()->crm()->...);\n *\n * @template T\n * @param callable(): T $apiCall\n * @return T\n *\n * @throws RateLimitException\n */\n private function executeRequest(callable $apiCall)\n {\n if (! $this->rateLimiter->canMakeRequest($this->config)) {\n $retryAfter = $this->rateLimiter->requestAvailableIn($this->config);\n\n $this->log->warning('[Hubspot] Rate limit exceeded, deferring request', [\n 'team_id' => $this->config->team_id,\n 'config_id' => $this->config->getId(),\n 'retry_after' => $retryAfter,\n ]);\n\n throw new RateLimitException(\n 'Hubspot rate limit reached for configuration ' . $this->config->getId(),\n $retryAfter,\n );\n }\n\n $this->rateLimiter->incrementRequestCount($this->config);\n\n try {\n return $apiCall();\n } catch (Throwable $e) {\n if ($this->isHubspotRateLimit($e)) {\n $retryAfter = $this->parseRetryAfter($e);\n\n $this->log->warning('[Hubspot] Received 429 from API', [\n 'team_id' => $this->config->team_id,\n 'config_id' => $this->config->getId(),\n 'retry_after' => $retryAfter,\n 'reason' => $e->getMessage(),\n ]);\n\n throw new RateLimitException('Hubspot returned 429', $retryAfter, $e);\n }\n\n throw $e;\n }\n }\n\n private function isHubspotRateLimit(Throwable $e): bool\n {\n return method_exists($e, 'getCode') && (int) $e->getCode() === 429;\n }\n\n private function parseRetryAfter(Throwable $e): int\n {\n if (method_exists($e, 'getResponseHeaders')) {\n $headers = $e->getResponseHeaders() ?: [];\n $value = $headers['Retry-After'] ?? $headers['retry-after'] ?? null;\n if (is_array($value)) {\n $value = $value[0] ?? null;\n }\n if (is_numeric($value)) {\n return (int) $value;\n }\n }\n\n return 10;\n }\n\n public function getMinimumApiVersion(): string\n {\n return self::MIN_API_VERSION;\n }\n\n public function getInstance(): Factory\n {\n return new Factory([\n 'key' => $this->accessToken,\n 'oauth2' => true,\n 'base_url' => $this->baseUrl,\n ]);\n }\n\n public function getNewInstance(): Discovery\n {\n return \\HubSpot\\Factory::createWithAccessToken($this->accessToken);\n }\n\n /**\n * Secondly and daily limits for Hubspot API\n *\n * Product Tier: Free & Starter | Professional & Enterprise | API add-on (any tier)\n * Burst: 100/10 seconds | 150/10 seconds | 200/10 seconds\n * Daily: 250,000 | 500,000 | 1,000,000\n *\n * Official documentation states: The search endpoints are rate limited to five requests per second.\n * Since with 5 RPS were still hitting secondly rate limits we lowered it to 4\n */\n public function getPaginatedData(array $payload, string $type, int $offset = 0): array\n {\n $total = 0;\n $lastId = null;\n $rows = [];\n foreach ($this->getPaginatedDataGenerator($payload, $type, $offset, $total, $lastId) as $row) {\n $rows[] = $row;\n }\n\n return ['results' => $rows, 'total' => $total, 'last_record' => $lastId];\n }\n\n /**\n * @throws HubspotException\n * @throws SocialAccountTokenInvalidException\n * @throws BadRequest\n */\n public function getPaginatedDataGenerator(\n array $payload,\n string $type,\n int $offset = 0,\n int &$total = 0,\n ?string &$lastRecordId = null\n ): \\Generator {\n return $this->paginationService->getPaginatedDataGenerator(\n $this,\n $payload,\n $type,\n $offset,\n $total,\n $lastRecordId\n );\n }\n\n /**\n * @throws DealApiException\n * @throws CrmException\n */\n public function getOpportunityById(string $crmId, array $fields): array\n {\n try {\n $deal = $this->executeRequest(fn () => $this->getNewInstance()->crm()->deals()->basicApi()->getById(\n $crmId,\n implode(',', $fields),\n 'companies,contacts'\n ));\n } catch (DealApiException $e) {\n $this->log->info('[Hubspot] Failed to fetch opportunity', [\n 'crm_id' => $crmId,\n 'reason' => $e->getMessage(),\n ]);\n\n throw $e;\n }\n\n if (! $deal instanceof DealWithAssociations) {\n throw new CrmException('Deal not found');\n }\n\n return [\n 'id' => $deal->getId(),\n 'properties' => $deal->getProperties(),\n 'associations' => $deal->getAssociations(),\n ];\n }\n\n /**\n * Generic batch read method for HubSpot objects\n *\n * @param string $objectType The object type ('deals', 'companies', 'contacts')\n * @param array<string> $crmIds Array of HubSpot object IDs (max 100)\n * @param array<string> $fields Array of property names to fetch\n *\n * @return array<string, array> Array keyed by CRM ID with object data\n */\n private function batchReadObjects(string $objectType, array $crmIds, array $fields): array\n {\n if (empty($crmIds)) {\n return [];\n }\n\n $this->validateBatchSize($objectType, $crmIds);\n $this->ensureValidToken();\n\n try {\n $batchConfig = $this->createBatchConfiguration($objectType);\n $batchReadRequest = $this->prepareBatchRequest($batchConfig, $crmIds, $fields);\n $response = $batchConfig['api']->read($batchReadRequest);\n\n $this->validateApiResponse($response, $objectType);\n\n $results = $this->processApiResults($response);\n $this->logBatchResults($objectType, $crmIds, $results);\n\n return $results;\n } catch (\\Throwable $e) {\n $this->handleBatchError($e, $objectType, $crmIds);\n }\n }\n\n private function validateBatchSize(string $objectType, array $crmIds): void\n {\n if (count($crmIds) > 100) {\n throw new \\InvalidArgumentException(\"Batch size cannot exceed 100 {$objectType}\");\n }\n }\n\n private function createBatchConfiguration(string $objectType): array\n {\n $configurations = [\n 'deals' => [\n 'batchReadRequest' => new \\HubSpot\\Client\\Crm\\Deals\\Model\\BatchReadInputSimplePublicObjectId(),\n 'inputClass' => \\HubSpot\\Client\\Crm\\Deals\\Model\\SimplePublicObjectId::class,\n 'api' => $this->getNewInstance()->crm()->deals()->batchApi(),\n ],\n 'companies' => [\n 'batchReadRequest' => new \\HubSpot\\Client\\Crm\\Companies\\Model\\BatchReadInputSimplePublicObjectId(),\n 'inputClass' => \\HubSpot\\Client\\Crm\\Companies\\Model\\SimplePublicObjectId::class,\n 'api' => $this->getNewInstance()->crm()->companies()->batchApi(),\n ],\n 'contacts' => [\n 'batchReadRequest' => new \\HubSpot\\Client\\Crm\\Contacts\\Model\\BatchReadInputSimplePublicObjectId(),\n 'inputClass' => \\HubSpot\\Client\\Crm\\Contacts\\Model\\SimplePublicObjectId::class,\n 'api' => $this->getNewInstance()->crm()->contacts()->batchApi(),\n ],\n ];\n\n if (! isset($configurations[$objectType])) {\n throw new \\InvalidArgumentException(\"Unsupported object type: {$objectType}\");\n }\n\n return $configurations[$objectType];\n }\n\n private function prepareBatchRequest(array $batchConfig, array $crmIds, array $fields): object\n {\n $batchReadRequest = $batchConfig['batchReadRequest'];\n $inputClass = $batchConfig['inputClass'];\n\n $inputs = array_map(function ($crmId) use ($inputClass) {\n $input = new $inputClass();\n $input->setId($crmId);\n\n return $input;\n }, $crmIds);\n\n $batchReadRequest->setInputs($inputs);\n $batchReadRequest->setProperties($fields);\n\n return $batchReadRequest;\n }\n\n private function validateApiResponse($response, string $objectType): void\n {\n if (! $response) {\n throw new CrmException(\"HubSpot API returned null response for {$objectType} batch read\");\n }\n }\n\n private function processApiResults($response): array\n {\n $results = [];\n $responseResults = $response->getResults();\n\n if ($responseResults) {\n foreach ($responseResults as $object) {\n if ($object && $object->getId()) {\n $results[$object->getId()] = [\n 'id' => $object->getId(),\n 'properties' => $object->getProperties() ?: [],\n ];\n }\n }\n }\n\n return $results;\n }\n\n private function logBatchResults(string $objectType, array $crmIds, array $results): void\n {\n $this->log->info(\"[HubSpot] Batch fetched {$objectType}\", [\n 'requested_count' => count($crmIds),\n 'returned_count' => count($results),\n 'crm_ids' => $crmIds,\n ]);\n }\n\n private function handleBatchError(\\Throwable $e, string $objectType, array $crmIds): void\n {\n $errorMessage = $e->getMessage() ?: 'Unknown error';\n $errorTrace = $e->getTraceAsString() ?: 'No trace available';\n\n $this->log->error(\"[HubSpot] Failed to batch fetch {$objectType}\", [\n 'crm_ids' => $crmIds,\n 'error' => $errorMessage,\n 'trace' => $errorTrace,\n ]);\n\n throw new CrmException(\"Failed to batch fetch {$objectType}: \" . $errorMessage);\n }\n\n /**\n * Batch read multiple opportunities by their CRM IDs\n *\n * @param array<string> $crmIds Array of HubSpot deal IDs (max 100)\n * @param array<string> $fields Array of property names to fetch\n *\n * @return array<string, array> Array keyed by CRM ID with opportunity data\n */\n public function getOpportunitiesByIds(array $crmIds, array $fields): array\n {\n return $this->batchReadObjects('deals', $crmIds, $fields);\n }\n\n /**\n * Batch read multiple companies by their CRM IDs\n *\n * @param array<string> $crmIds Array of HubSpot company IDs (max 100)\n * @param array<string> $fields Array of property names to fetch\n *\n * @return array<string, array> Array keyed by CRM ID with company data\n */\n public function getCompaniesByIds(array $crmIds, array $fields): array\n {\n return $this->batchReadObjects('companies', $crmIds, $fields);\n }\n\n /**\n * Batch read multiple contacts by their CRM IDs\n *\n * @param array<string> $crmIds Array of HubSpot contact IDs (max 100)\n * @param array<string> $fields Array of property names to fetch\n *\n * @return array<string, array> Array keyed by CRM ID with contact data\n */\n public function getContactsByIds(array $crmIds, array $fields): array\n {\n return $this->batchReadObjects('contacts', $crmIds, $fields);\n }\n\n /**\n * @throws CompanyApiException\n * @throws CrmException\n */\n public function getAccountById(string $crmId, array $fields): array\n {\n try {\n $company = $this->getNewInstance()->crm()->companies()->basicApi()->getById(\n $crmId,\n implode(',', $fields),\n );\n } catch (CompanyApiException $e) {\n $this->log->info('[Hubspot] Failed to fetch account', [\n 'crm_id' => $crmId,\n 'reason' => $e->getMessage(),\n ]);\n\n throw $e;\n }\n\n if (! $company instanceof CompaniesWithAssociations) {\n throw new CrmException('Account not found');\n }\n\n return [\n 'id' => $company->getId(),\n 'properties' => $company->getProperties(),\n ];\n }\n\n /**\n * @throws ContactApiException\n * @throws CrmException\n */\n public function getContactById(string $crmId, array $fields): array\n {\n try {\n $contact = $this->getNewInstance()->crm()->contacts()->basicApi()->getById(\n $crmId,\n implode(',', $fields)\n );\n } catch (ContactApiException $e) {\n $this->log->info('[Hubspot] Failed to fetch contact', [\n 'crm_id' => $crmId,\n 'reason' => $e->getMessage(),\n ]);\n\n throw $e;\n }\n\n if (! $contact instanceof ContactsWithAssociations) {\n throw new CrmException('Contact not found');\n }\n\n return [\n 'id' => $contact->getId(),\n 'properties' => $contact->getProperties(),\n ];\n }\n\n /**\n * This is email search request that Hubspot offers as GET (more generous quota)\n */\n public function getContactByEmail(string $email, array $fields = []): array\n {\n try {\n $contact = $this->getNewInstance()->crm()->contacts()->basicApi()->getById(\n $email,\n implode(',', $fields),\n null,\n false,\n 'email'\n );\n\n return [\n 'id' => $contact->getId(),\n 'properties' => $contact->getProperties(),\n ];\n } catch (ContactApiException $e) {\n $this->log->info('[Hubspot] Failed to fetch contact', [\n 'email' => $email,\n 'reason' => $e->getMessage(),\n ]);\n\n return [];\n }\n }\n\n /**\n * @throws CrmException\n */\n public function fetchProperty(string $objectType, string $propertyId): Property\n {\n $result = $this->getNewInstance()->crm()->properties()->coreApi()->getByName($objectType, $propertyId);\n\n if (! $result instanceof Property) {\n $this->log->error('[Hubspot] Failed to fetch property', [\n 'object_type' => $objectType,\n 'property_id' => $propertyId,\n 'reason' => $result->getMessage(),\n ]);\n\n throw new CrmException('Failed to fetch property');\n }\n\n return $result;\n }\n\n /**\n * @return array<CrmFieldOption>\n */\n public function fetchPropertyOptions(string $objectType, string $propertyId): array\n {\n /** @var array<CrmFieldOption> */\n return $this->fetchProperty($objectType, $propertyId)->getOptions();\n }\n\n /**\n * @return array<array{id:string, label:string, deleted:bool}>\n */\n public function fetchCallDispositions(): array\n {\n /** @var Response $response */\n $response = $this->getInstance()->engagements()->getCallDispositions();\n\n /**\n * @var array<array{\n * id:string,\n * label:string,\n * deleted: bool\n * }>\n */\n return $response->toArray();\n }\n\n /**\n * @return array<CrmFieldOption>\n */\n public function fetchOpportunityPipelineStages(): array\n {\n $stages = [];\n $apiResponse = $this->getNewInstance()->crm()->pipelines()->pipelinesApi()->getAll('deals');\n\n if ($apiResponse instanceof Error) {\n $this->log->error('[Hubspot] Failed to fetch opportunity pipelines', [\n 'reason' => $apiResponse->getMessage(),\n ]);\n\n return [];\n }\n\n foreach ($apiResponse->getResults() as $pipeline) {\n $pipelineStages = array_map(\n static function (PipelineStage $stage) {\n return [\n 'id' => $stage->getId(),\n 'label' => $stage->getLabel(),\n ];\n },\n $pipeline->getStages()\n );\n\n $stages = array_merge($stages, $pipelineStages);\n }\n\n return $stages;\n }\n\n public function fetchOpportunityPipelines(): array\n {\n $pipelines = [];\n\n try {\n $apiResponse = $this->makeRequest('/crm/v3/pipelines/deals');\n } catch (\\Exception $e) {\n $this->log->info('[Hubspot] Failed to fetch opportunity pipelines', [\n 'reason' => $e->getMessage(),\n ]);\n\n return [];\n }\n\n $response = $apiResponse->toArray();\n\n foreach ($response['results'] as $pipeline) {\n $pipelines[] = [\n 'id' => $pipeline['id'],\n 'label' => $pipeline['label'],\n ];\n }\n\n return $pipelines;\n }\n\n /**\n * @return array<CrmFieldOption>\n */\n public function fetchMeetingOutcomeFieldOptions(Field $field): array\n {\n return $field->getCrmProviderId() === 'meetingOutcome'\n ? $this->fetchMeetingOutcomeTypes()\n : $this->fetchCallActivityTypes();\n }\n\n public function fetchMeetingOutcomeTypes(): array\n {\n return $this->extractMeetingTypeOptions(\n 'https://api.hubapi.com/crm/v3/properties/meeting/hs_meeting_outcome'\n );\n }\n\n public function fetchCallActivityTypes(): array\n {\n return $this->extractMeetingTypeOptions(\n 'https://api.hubapi.com/crm/v3/properties/call/hs_activity_type'\n );\n }\n\n private function extractMeetingTypeOptions(string $endpoint): array\n {\n /** @var Response $response */\n $response = $this->getInstance()\n ->getClient()\n ->request('GET', $endpoint);\n\n /**\n * @var array<array{\n * value: string,\n * label: string,\n * displayOrder: int\n * }> $optionData\n */\n $optionData = $response->toArray()['options'] ?? [];\n\n $options = [];\n foreach ($optionData as $item) {\n $options[] = [\n 'id' => $item['value'],\n 'value' => $item['value'],\n 'label' => $item['label'],\n 'display_order' => $item['displayOrder'],\n ];\n }\n\n return $options;\n }\n\n /**\n * @return array<CrmFieldOption>\n */\n public function fetchDispositionFieldOptions(): array\n {\n $options = [];\n\n $dispositions = $this->fetchCallDispositions();\n\n foreach ($dispositions as $disposition) {\n if ($disposition['deleted'] !== false) {\n continue;\n }\n\n $option['value'] = $disposition['id'];\n $option['id'] = $disposition['id'];\n $option['label'] = $disposition['label'];\n\n $options[] = $option;\n }\n\n return $options;\n }\n\n /**\n * @return array<CrmFieldOption>\n */\n public function fetchOpportunityFieldOptions(Field $field): array\n {\n if ($field->isStageField()) {\n return $this->fetchOpportunityPipelineStages();\n }\n\n if ($field->isPipelineField()) {\n return $this->fetchOpportunityPipelines();\n }\n\n return $this->fetchPropertyOptions('deals', $field->getCrmProviderId());\n }\n\n /**\n * @throws BadRequest\n * @throws HubspotException\n */\n public function makeRequest(string $endpoint, $method = 'GET', $payload = [], ?string $queryString = null)\n {\n $endpoint = self::BASE_URL . $endpoint;\n\n if ($method === 'GET') {\n $response = $this->getInstance()->getClient()?->request(\n method: $method,\n endpoint: $endpoint,\n query_string: $queryString\n );\n } else {\n $response = $this->getInstance()->getClient()->request($method, $endpoint, [\n 'json' => ($payload),\n ]);\n }\n \n $max = $response->getHeaderLine('X-HubSpot-RateLimit-Max'); // \"110\"\n $remaining = $response->getHeaderLine('X-HubSpot-RateLimit-Remaining'); // \"109\"\n $interval = $response->getHeaderLine('X-HubSpot-RateLimit-Interval-Milliseconds'); // \"10000\"\n $body = json_decode((string) $response->getBody(), true);\n \n \\Illuminate\\Support\\Facades\\Log::channel('custom_channel')->info('$max ' . PHP_EOL . print_r($max, true));\n \\Illuminate\\Support\\Facades\\Log::channel('custom_channel')->info('$remaining ' . PHP_EOL . print_r($remaining, true));\n \\Illuminate\\Support\\Facades\\Log::channel('custom_channel')->info('$interval ' . PHP_EOL . print_r($interval, true));\n \\Illuminate\\Support\\Facades\\Log::channel('custom_channel')->info('$interval ' . PHP_EOL . print_r($interval, true));\n \n return $response;\n }\n\n /**\n * @throws BadRequest\n * @throws HubspotException\n */\n public function createMeeting(array $payload): Response\n {\n $endpoint = '/crm/v3/objects/meetings';\n\n return $this->makeRequest($endpoint, 'POST', $payload);\n }\n\n /**\n * @throws BadRequest\n * @throws HubspotException\n */\n public function updateMeeting(string $meetingId, array $payload): Response\n {\n $endpoint = '/crm/v3/objects/meetings/' . $meetingId;\n\n return $this->makeRequest($endpoint, 'PATCH', $payload);\n }\n\n /**\n * @throws \\Exception\n */\n public function createNote(\n string $body,\n string $ownerId,\n int $timestamp,\n string $objectId,\n NoteObject $noteObject\n ): ?string {\n try {\n $noteInput = new SimplePublicObjectInput([\n 'properties' => [\n 'hs_note_body' => $body,\n 'hubspot_owner_id' => $ownerId,\n 'hs_timestamp' => $timestamp,\n ],\n ]);\n\n // Create note\n $note = $this->getNewInstance()->crm()->objects()->basicApi()->create('note', $noteInput);\n\n $this->getNewInstance()->crm()->objects()->associationsApi()->create(\n 'note',\n $note->getId(),\n $this->getNoteObject($noteObject),\n $objectId,\n $this->getNoteAssociationType($noteObject),\n );\n\n return $note->getId();\n } catch (\\Exception $e) {\n $this->log->error('[Hubspot] Failed to create note', [\n 'objectId' => $objectId,\n 'noteObject' => $noteObject->getObjectType(),\n 'reason' => $e->getMessage(),\n ]);\n\n \\Sentry::captureException($e);\n }\n\n return null;\n }\n\n public function updateEngagement(string $objectId, array $engagement, array $metadata): void\n {\n $this->getInstance()->engagements()->update($objectId, $engagement, $metadata);\n }\n\n public function getEngagementData(string $engagementId): array\n {\n $engagement = $this->getInstance()->engagements()->get($engagementId);\n\n return $engagement->toArray();\n }\n\n public function createEngagement(array $engagement, array $associations, array $metadata): Response\n {\n return $this->getInstance()\n ->engagements()\n ->create($engagement, $associations, $metadata);\n }\n\n public function isUnauthorizedException(\\Exception $e): bool\n {\n // Check for specific HubSpot API exception types first\n if ($e instanceof BadRequest) {\n // BadRequest can contain 401 status codes\n return $e->getCode() === 401;\n }\n\n // Check for HTTP client exceptions with status codes\n if ($e instanceof \\GuzzleHttp\\Exception\\RequestException && $e->hasResponse()) {\n $response = $e->getResponse();\n if ($response !== null) {\n return $response->getStatusCode() === 401;\n }\n }\n\n // Check for Guzzle HTTP exceptions\n if ($e instanceof \\GuzzleHttp\\Exception\\ClientException) {\n return $e->getCode() === 401;\n }\n\n // Fallback to string matching as last resort, but be more specific\n $message = strtolower($e->getMessage());\n\n return str_contains($message, '401 unauthorized') ||\n str_contains($message, 'http 401') ||\n str_contains($message, 'status code 401') ||\n (preg_match('/\\b401\\b/', $message) && str_contains($message, 'unauthorized'));\n }\n\n /**\n * Validates and refreshes the access token if needed before API requests.\n * This ensures long-running processes don't fail due to token expiration.\n *\n * @throws SocialAccountTokenInvalidException\n */\n public function ensureValidToken(): void\n {\n if ($this->oauthAccount === null) {\n return;\n }\n\n $newToken = $this->tokenManager->ensureValidToken($this->oauthAccount);\n if ($newToken !== null) {\n $this->accessToken = $newToken;\n }\n }\n\n public function getConfig()\n {\n return $this->config;\n }\n\n // returns only active (archived=false)\n public function getOwners(): array\n {\n return $this->getNewInstance()->crm()->owners()->getAll();\n }\n\n /**\n * @param bool $archived\n *\n * @return array<Owner>|[]\n */\n public function getOwnersArchived(bool $archived = true): array\n {\n $endpoint = '/crm/v3/owners';\n $queryParams = [\n 'archived' => $archived ? 'true' : 'false',\n ];\n $queryString = http_build_query($queryParams);\n\n $owners = [];\n\n try {\n $response = $this->makeRequest(endpoint: $endpoint, queryString: $queryString);\n $responseData = $response?->toArray();\n\n foreach ($responseData['results'] as $result) {\n try {\n $owners[] = Owner::create($result);\n } catch (Throwable $e) {\n $this->log->error('[HubSpot] Failed to process owner data', [\n 'result' => $result,\n 'error' => $e->getMessage(),\n ]);\n\n continue;\n }\n }\n } catch (Throwable $e) {\n $this->log->error('HubSpot] Failed to fetch owners', [\n 'archived' => $archived,\n 'error' => $e->getMessage(),\n ]);\n\n return [];\n }\n\n return $owners;\n }\n\n public function getMeeting(string $engagementId): ObjectWithAssociations\n {\n return $this->getNewInstance()->crm()->objects()->basicApi()\n ->getById('meeting', $engagementId, null, 'contact,company,deal');\n }\n\n public function deleteEngagement(string $engagementId): void\n {\n $this->getInstance()->engagements()->delete((int) $engagementId);\n }\n\n public function getAssociationsData(array $ids, string $fromObject, string $toObject): array\n {\n $associationData = [];\n $idChunks = array_chunk($ids, self::ASSOCIATIONS_BATCH_SIZE_LIMIT);\n\n foreach ($idChunks as $idChunk) {\n try {\n $batchInput = new \\HubSpot\\Client\\Crm\\Associations\\Model\\BatchInputPublicObjectId();\n $batchInput->setInputs(array_map(function ($id) {\n $publicObjectId = new \\HubSpot\\Client\\Crm\\Associations\\Model\\PublicObjectId();\n $publicObjectId->setId($id);\n\n return $publicObjectId;\n }, $idChunk));\n\n $associatedObjectsData = $this\n ->getNewInstance()\n ->crm()\n ->associations()\n ->batchApi()\n ->read($fromObject, $toObject, $batchInput);\n\n if ($associatedObjectsData instanceof \\HubSpot\\Client\\Crm\\Associations\\Model\\BatchResponsePublicAssociationMulti) {\n foreach ($associatedObjectsData->getResults() as $association) {\n $from = $association->getFrom()->getId();\n $toAssociations = $association->getTo();\n\n if (! empty($toAssociations)) {\n $associationData[$from] = array_map(function ($item) {\n return $item->getId();\n }, $toAssociations);\n }\n }\n }\n } catch (\\Exception $e) {\n $this->log->error('[Hubspot] Failed to fetch associations', [\n 'from_object' => $fromObject,\n 'to_object' => $toObject,\n 'reason' => $e->getMessage(),\n ]);\n }\n }\n\n return $associationData;\n }\n\n /**\n * @throws \\Exception\n */\n private function getNoteAssociationType(NoteObject $noteObject): string\n {\n return match($noteObject) {\n NoteObject::Opportunity => 'note_to_deal',\n NoteObject::Lead, NoteObject::Contact => 'note_to_contact', // or 'note_to_lead' if your portal supports it\n NoteObject::Account => 'note_to_company',\n NoteObject::Call, NoteObject::Event => throw new \\Exception('Not supported'),\n };\n }\n\n /**\n * @throws \\Exception\n */\n private function getNoteObject(NoteObject $noteObject): string\n {\n return match($noteObject) {\n NoteObject::Opportunity => 'deal',\n NoteObject::Lead, NoteObject::Contact => 'contact',\n NoteObject::Account => 'company',\n NoteObject::Call, NoteObject::Event => throw new \\Exception('Not supported'),\n };\n }\n\n public function addAssociations(string $objectType, string $associationType, array $payload): Response\n {\n $endpoint = \"/crm/v4/associations/$objectType/$associationType/batch/create\";\n\n return $this->makeRequest($endpoint, 'POST', $payload);\n }\n\n public function removeAssociations(string $objectType, string $associationType, array $payload): Response\n {\n $endpoint = \"/crm/v4/associations/$objectType/$associationType/batch/archive\";\n\n return $this->makeRequest($endpoint, 'POST', $payload);\n }\n}","depth":4,"on_screen":true,"value":"<?php\n\ndeclare(strict_types=1);\n\nnamespace Jiminny\\Services\\Crm\\Hubspot;\n\nuse HubSpot\\Client\\Crm\\Deals\\ApiException as DealApiException;\nuse HubSpot\\Client\\Crm\\Contacts\\ApiException as ContactApiException;\nuse HubSpot\\Client\\Crm\\Companies\\ApiException as CompanyApiException;\nuse HubSpot\\Client\\Crm\\Contacts\\Model\\SimplePublicObjectWithAssociations as ContactsWithAssociations;\nuse HubSpot\\Client\\Crm\\Companies\\Model\\SimplePublicObjectWithAssociations as CompaniesWithAssociations;\nuse HubSpot\\Client\\Crm\\Deals\\Model\\SimplePublicObjectWithAssociations as DealWithAssociations;\nuse HubSpot\\Client\\Crm\\Objects\\Model\\SimplePublicObjectInput;\nuse HubSpot\\Client\\Crm\\Objects\\Model\\SimplePublicObjectWithAssociations as ObjectWithAssociations;\nuse HubSpot\\Client\\Crm\\Pipelines\\Model\\Error;\nuse HubSpot\\Client\\Crm\\Pipelines\\Model\\PipelineStage;\nuse HubSpot\\Client\\Crm\\Properties\\Model\\Property;\nuse HubSpot\\Discovery\\Discovery;\nuse Jiminny\\Component\\Utility\\Service\\ProviderRateLimiter;\nuse Jiminny\\Exceptions\\CrmException;\nuse Jiminny\\Exceptions\\RateLimitException;\nuse Jiminny\\Exceptions\\SocialAccountTokenInvalidException;\nuse Jiminny\\Jobs\\Crm\\NoteObject;\nuse Jiminny\\Models\\Crm\\Field;\nuse Jiminny\\Services\\Crm\\BaseClient;\nuse Jiminny\\Services\\Crm\\Hubspot\\DTO\\Response\\Owner;\nuse Jiminny\\Services\\SocialAccountService;\nuse SevenShores\\Hubspot\\Exceptions\\BadRequest;\nuse SevenShores\\Hubspot\\Exceptions\\HubspotException;\nuse SevenShores\\Hubspot\\Factory;\nuse SevenShores\\Hubspot\\Http\\Response;\nuse Jiminny\\Services\\Crm\\Hubspot\\Pagination\\HubspotPaginationService;\nuse Throwable;\n\n/**\n * @phpstan-type CrmFieldOption array{id:string, label:string, value?:string}\n */\nclass Client extends BaseClient implements HubspotClientInterface\n{\n public const string MIN_API_VERSION = '2';\n\n public const string BASE_URL = 'https://api.hubapi.com';\n\n public const int ASSOCIATIONS_BATCH_SIZE_LIMIT = 1000;\n\n private HubspotPaginationService $paginationService;\n private HubspotTokenManager $tokenManager;\n private ProviderRateLimiter $rateLimiter;\n\n public function __construct(\n SocialAccountService $socialAccountService,\n HubspotPaginationService $paginationService,\n HubspotTokenManager $tokenManager,\n ProviderRateLimiter $rateLimiter,\n ) {\n parent::__construct($socialAccountService);\n $this->paginationService = $paginationService;\n $this->tokenManager = $tokenManager;\n $this->rateLimiter = $rateLimiter;\n\n $this->setBaseUrl(self::BASE_URL);\n $this->setVersion(self::MIN_API_VERSION);\n }\n\n /**\n * Single entry point for every HubSpot API call. Enforces the per-portal\n * rate limit configured in the rate_limits table (morphed to the current\n * Configuration) and reacts to a real 429 from HubSpot by translating it\n * into a RateLimitException carrying Retry-After.\n *\n * Wrap any outbound HubSpot call (SDK or raw HTTP) like:\n *\n * $this->executeRequest(fn () => $this->getNewInstance()->crm()->...);\n *\n * @template T\n * @param callable(): T $apiCall\n * @return T\n *\n * @throws RateLimitException\n */\n private function executeRequest(callable $apiCall)\n {\n if (! $this->rateLimiter->canMakeRequest($this->config)) {\n $retryAfter = $this->rateLimiter->requestAvailableIn($this->config);\n\n $this->log->warning('[Hubspot] Rate limit exceeded, deferring request', [\n 'team_id' => $this->config->team_id,\n 'config_id' => $this->config->getId(),\n 'retry_after' => $retryAfter,\n ]);\n\n throw new RateLimitException(\n 'Hubspot rate limit reached for configuration ' . $this->config->getId(),\n $retryAfter,\n );\n }\n\n $this->rateLimiter->incrementRequestCount($this->config);\n\n try {\n return $apiCall();\n } catch (Throwable $e) {\n if ($this->isHubspotRateLimit($e)) {\n $retryAfter = $this->parseRetryAfter($e);\n\n $this->log->warning('[Hubspot] Received 429 from API', [\n 'team_id' => $this->config->team_id,\n 'config_id' => $this->config->getId(),\n 'retry_after' => $retryAfter,\n 'reason' => $e->getMessage(),\n ]);\n\n throw new RateLimitException('Hubspot returned 429', $retryAfter, $e);\n }\n\n throw $e;\n }\n }\n\n private function isHubspotRateLimit(Throwable $e): bool\n {\n return method_exists($e, 'getCode') && (int) $e->getCode() === 429;\n }\n\n private function parseRetryAfter(Throwable $e): int\n {\n if (method_exists($e, 'getResponseHeaders')) {\n $headers = $e->getResponseHeaders() ?: [];\n $value = $headers['Retry-After'] ?? $headers['retry-after'] ?? null;\n if (is_array($value)) {\n $value = $value[0] ?? null;\n }\n if (is_numeric($value)) {\n return (int) $value;\n }\n }\n\n return 10;\n }\n\n public function getMinimumApiVersion(): string\n {\n return self::MIN_API_VERSION;\n }\n\n public function getInstance(): Factory\n {\n return new Factory([\n 'key' => $this->accessToken,\n 'oauth2' => true,\n 'base_url' => $this->baseUrl,\n ]);\n }\n\n public function getNewInstance(): Discovery\n {\n return \\HubSpot\\Factory::createWithAccessToken($this->accessToken);\n }\n\n /**\n * Secondly and daily limits for Hubspot API\n *\n * Product Tier: Free & Starter | Professional & Enterprise | API add-on (any tier)\n * Burst: 100/10 seconds | 150/10 seconds | 200/10 seconds\n * Daily: 250,000 | 500,000 | 1,000,000\n *\n * Official documentation states: The search endpoints are rate limited to five requests per second.\n * Since with 5 RPS were still hitting secondly rate limits we lowered it to 4\n */\n public function getPaginatedData(array $payload, string $type, int $offset = 0): array\n {\n $total = 0;\n $lastId = null;\n $rows = [];\n foreach ($this->getPaginatedDataGenerator($payload, $type, $offset, $total, $lastId) as $row) {\n $rows[] = $row;\n }\n\n return ['results' => $rows, 'total' => $total, 'last_record' => $lastId];\n }\n\n /**\n * @throws HubspotException\n * @throws SocialAccountTokenInvalidException\n * @throws BadRequest\n */\n public function getPaginatedDataGenerator(\n array $payload,\n string $type,\n int $offset = 0,\n int &$total = 0,\n ?string &$lastRecordId = null\n ): \\Generator {\n return $this->paginationService->getPaginatedDataGenerator(\n $this,\n $payload,\n $type,\n $offset,\n $total,\n $lastRecordId\n );\n }\n\n /**\n * @throws DealApiException\n * @throws CrmException\n */\n public function getOpportunityById(string $crmId, array $fields): array\n {\n try {\n $deal = $this->executeRequest(fn () => $this->getNewInstance()->crm()->deals()->basicApi()->getById(\n $crmId,\n implode(',', $fields),\n 'companies,contacts'\n ));\n } catch (DealApiException $e) {\n $this->log->info('[Hubspot] Failed to fetch opportunity', [\n 'crm_id' => $crmId,\n 'reason' => $e->getMessage(),\n ]);\n\n throw $e;\n }\n\n if (! $deal instanceof DealWithAssociations) {\n throw new CrmException('Deal not found');\n }\n\n return [\n 'id' => $deal->getId(),\n 'properties' => $deal->getProperties(),\n 'associations' => $deal->getAssociations(),\n ];\n }\n\n /**\n * Generic batch read method for HubSpot objects\n *\n * @param string $objectType The object type ('deals', 'companies', 'contacts')\n * @param array<string> $crmIds Array of HubSpot object IDs (max 100)\n * @param array<string> $fields Array of property names to fetch\n *\n * @return array<string, array> Array keyed by CRM ID with object data\n */\n private function batchReadObjects(string $objectType, array $crmIds, array $fields): array\n {\n if (empty($crmIds)) {\n return [];\n }\n\n $this->validateBatchSize($objectType, $crmIds);\n $this->ensureValidToken();\n\n try {\n $batchConfig = $this->createBatchConfiguration($objectType);\n $batchReadRequest = $this->prepareBatchRequest($batchConfig, $crmIds, $fields);\n $response = $batchConfig['api']->read($batchReadRequest);\n\n $this->validateApiResponse($response, $objectType);\n\n $results = $this->processApiResults($response);\n $this->logBatchResults($objectType, $crmIds, $results);\n\n return $results;\n } catch (\\Throwable $e) {\n $this->handleBatchError($e, $objectType, $crmIds);\n }\n }\n\n private function validateBatchSize(string $objectType, array $crmIds): void\n {\n if (count($crmIds) > 100) {\n throw new \\InvalidArgumentException(\"Batch size cannot exceed 100 {$objectType}\");\n }\n }\n\n private function createBatchConfiguration(string $objectType): array\n {\n $configurations = [\n 'deals' => [\n 'batchReadRequest' => new \\HubSpot\\Client\\Crm\\Deals\\Model\\BatchReadInputSimplePublicObjectId(),\n 'inputClass' => \\HubSpot\\Client\\Crm\\Deals\\Model\\SimplePublicObjectId::class,\n 'api' => $this->getNewInstance()->crm()->deals()->batchApi(),\n ],\n 'companies' => [\n 'batchReadRequest' => new \\HubSpot\\Client\\Crm\\Companies\\Model\\BatchReadInputSimplePublicObjectId(),\n 'inputClass' => \\HubSpot\\Client\\Crm\\Companies\\Model\\SimplePublicObjectId::class,\n 'api' => $this->getNewInstance()->crm()->companies()->batchApi(),\n ],\n 'contacts' => [\n 'batchReadRequest' => new \\HubSpot\\Client\\Crm\\Contacts\\Model\\BatchReadInputSimplePublicObjectId(),\n 'inputClass' => \\HubSpot\\Client\\Crm\\Contacts\\Model\\SimplePublicObjectId::class,\n 'api' => $this->getNewInstance()->crm()->contacts()->batchApi(),\n ],\n ];\n\n if (! isset($configurations[$objectType])) {\n throw new \\InvalidArgumentException(\"Unsupported object type: {$objectType}\");\n }\n\n return $configurations[$objectType];\n }\n\n private function prepareBatchRequest(array $batchConfig, array $crmIds, array $fields): object\n {\n $batchReadRequest = $batchConfig['batchReadRequest'];\n $inputClass = $batchConfig['inputClass'];\n\n $inputs = array_map(function ($crmId) use ($inputClass) {\n $input = new $inputClass();\n $input->setId($crmId);\n\n return $input;\n }, $crmIds);\n\n $batchReadRequest->setInputs($inputs);\n $batchReadRequest->setProperties($fields);\n\n return $batchReadRequest;\n }\n\n private function validateApiResponse($response, string $objectType): void\n {\n if (! $response) {\n throw new CrmException(\"HubSpot API returned null response for {$objectType} batch read\");\n }\n }\n\n private function processApiResults($response): array\n {\n $results = [];\n $responseResults = $response->getResults();\n\n if ($responseResults) {\n foreach ($responseResults as $object) {\n if ($object && $object->getId()) {\n $results[$object->getId()] = [\n 'id' => $object->getId(),\n 'properties' => $object->getProperties() ?: [],\n ];\n }\n }\n }\n\n return $results;\n }\n\n private function logBatchResults(string $objectType, array $crmIds, array $results): void\n {\n $this->log->info(\"[HubSpot] Batch fetched {$objectType}\", [\n 'requested_count' => count($crmIds),\n 'returned_count' => count($results),\n 'crm_ids' => $crmIds,\n ]);\n }\n\n private function handleBatchError(\\Throwable $e, string $objectType, array $crmIds): void\n {\n $errorMessage = $e->getMessage() ?: 'Unknown error';\n $errorTrace = $e->getTraceAsString() ?: 'No trace available';\n\n $this->log->error(\"[HubSpot] Failed to batch fetch {$objectType}\", [\n 'crm_ids' => $crmIds,\n 'error' => $errorMessage,\n 'trace' => $errorTrace,\n ]);\n\n throw new CrmException(\"Failed to batch fetch {$objectType}: \" . $errorMessage);\n }\n\n /**\n * Batch read multiple opportunities by their CRM IDs\n *\n * @param array<string> $crmIds Array of HubSpot deal IDs (max 100)\n * @param array<string> $fields Array of property names to fetch\n *\n * @return array<string, array> Array keyed by CRM ID with opportunity data\n */\n public function getOpportunitiesByIds(array $crmIds, array $fields): array\n {\n return $this->batchReadObjects('deals', $crmIds, $fields);\n }\n\n /**\n * Batch read multiple companies by their CRM IDs\n *\n * @param array<string> $crmIds Array of HubSpot company IDs (max 100)\n * @param array<string> $fields Array of property names to fetch\n *\n * @return array<string, array> Array keyed by CRM ID with company data\n */\n public function getCompaniesByIds(array $crmIds, array $fields): array\n {\n return $this->batchReadObjects('companies', $crmIds, $fields);\n }\n\n /**\n * Batch read multiple contacts by their CRM IDs\n *\n * @param array<string> $crmIds Array of HubSpot contact IDs (max 100)\n * @param array<string> $fields Array of property names to fetch\n *\n * @return array<string, array> Array keyed by CRM ID with contact data\n */\n public function getContactsByIds(array $crmIds, array $fields): array\n {\n return $this->batchReadObjects('contacts', $crmIds, $fields);\n }\n\n /**\n * @throws CompanyApiException\n * @throws CrmException\n */\n public function getAccountById(string $crmId, array $fields): array\n {\n try {\n $company = $this->getNewInstance()->crm()->companies()->basicApi()->getById(\n $crmId,\n implode(',', $fields),\n );\n } catch (CompanyApiException $e) {\n $this->log->info('[Hubspot] Failed to fetch account', [\n 'crm_id' => $crmId,\n 'reason' => $e->getMessage(),\n ]);\n\n throw $e;\n }\n\n if (! $company instanceof CompaniesWithAssociations) {\n throw new CrmException('Account not found');\n }\n\n return [\n 'id' => $company->getId(),\n 'properties' => $company->getProperties(),\n ];\n }\n\n /**\n * @throws ContactApiException\n * @throws CrmException\n */\n public function getContactById(string $crmId, array $fields): array\n {\n try {\n $contact = $this->getNewInstance()->crm()->contacts()->basicApi()->getById(\n $crmId,\n implode(',', $fields)\n );\n } catch (ContactApiException $e) {\n $this->log->info('[Hubspot] Failed to fetch contact', [\n 'crm_id' => $crmId,\n 'reason' => $e->getMessage(),\n ]);\n\n throw $e;\n }\n\n if (! $contact instanceof ContactsWithAssociations) {\n throw new CrmException('Contact not found');\n }\n\n return [\n 'id' => $contact->getId(),\n 'properties' => $contact->getProperties(),\n ];\n }\n\n /**\n * This is email search request that Hubspot offers as GET (more generous quota)\n */\n public function getContactByEmail(string $email, array $fields = []): array\n {\n try {\n $contact = $this->getNewInstance()->crm()->contacts()->basicApi()->getById(\n $email,\n implode(',', $fields),\n null,\n false,\n 'email'\n );\n\n return [\n 'id' => $contact->getId(),\n 'properties' => $contact->getProperties(),\n ];\n } catch (ContactApiException $e) {\n $this->log->info('[Hubspot] Failed to fetch contact', [\n 'email' => $email,\n 'reason' => $e->getMessage(),\n ]);\n\n return [];\n }\n }\n\n /**\n * @throws CrmException\n */\n public function fetchProperty(string $objectType, string $propertyId): Property\n {\n $result = $this->getNewInstance()->crm()->properties()->coreApi()->getByName($objectType, $propertyId);\n\n if (! $result instanceof Property) {\n $this->log->error('[Hubspot] Failed to fetch property', [\n 'object_type' => $objectType,\n 'property_id' => $propertyId,\n 'reason' => $result->getMessage(),\n ]);\n\n throw new CrmException('Failed to fetch property');\n }\n\n return $result;\n }\n\n /**\n * @return array<CrmFieldOption>\n */\n public function fetchPropertyOptions(string $objectType, string $propertyId): array\n {\n /** @var array<CrmFieldOption> */\n return $this->fetchProperty($objectType, $propertyId)->getOptions();\n }\n\n /**\n * @return array<array{id:string, label:string, deleted:bool}>\n */\n public function fetchCallDispositions(): array\n {\n /** @var Response $response */\n $response = $this->getInstance()->engagements()->getCallDispositions();\n\n /**\n * @var array<array{\n * id:string,\n * label:string,\n * deleted: bool\n * }>\n */\n return $response->toArray();\n }\n\n /**\n * @return array<CrmFieldOption>\n */\n public function fetchOpportunityPipelineStages(): array\n {\n $stages = [];\n $apiResponse = $this->getNewInstance()->crm()->pipelines()->pipelinesApi()->getAll('deals');\n\n if ($apiResponse instanceof Error) {\n $this->log->error('[Hubspot] Failed to fetch opportunity pipelines', [\n 'reason' => $apiResponse->getMessage(),\n ]);\n\n return [];\n }\n\n foreach ($apiResponse->getResults() as $pipeline) {\n $pipelineStages = array_map(\n static function (PipelineStage $stage) {\n return [\n 'id' => $stage->getId(),\n 'label' => $stage->getLabel(),\n ];\n },\n $pipeline->getStages()\n );\n\n $stages = array_merge($stages, $pipelineStages);\n }\n\n return $stages;\n }\n\n public function fetchOpportunityPipelines(): array\n {\n $pipelines = [];\n\n try {\n $apiResponse = $this->makeRequest('/crm/v3/pipelines/deals');\n } catch (\\Exception $e) {\n $this->log->info('[Hubspot] Failed to fetch opportunity pipelines', [\n 'reason' => $e->getMessage(),\n ]);\n\n return [];\n }\n\n $response = $apiResponse->toArray();\n\n foreach ($response['results'] as $pipeline) {\n $pipelines[] = [\n 'id' => $pipeline['id'],\n 'label' => $pipeline['label'],\n ];\n }\n\n return $pipelines;\n }\n\n /**\n * @return array<CrmFieldOption>\n */\n public function fetchMeetingOutcomeFieldOptions(Field $field): array\n {\n return $field->getCrmProviderId() === 'meetingOutcome'\n ? $this->fetchMeetingOutcomeTypes()\n : $this->fetchCallActivityTypes();\n }\n\n public function fetchMeetingOutcomeTypes(): array\n {\n return $this->extractMeetingTypeOptions(\n 'https://api.hubapi.com/crm/v3/properties/meeting/hs_meeting_outcome'\n );\n }\n\n public function fetchCallActivityTypes(): array\n {\n return $this->extractMeetingTypeOptions(\n 'https://api.hubapi.com/crm/v3/properties/call/hs_activity_type'\n );\n }\n\n private function extractMeetingTypeOptions(string $endpoint): array\n {\n /** @var Response $response */\n $response = $this->getInstance()\n ->getClient()\n ->request('GET', $endpoint);\n\n /**\n * @var array<array{\n * value: string,\n * label: string,\n * displayOrder: int\n * }> $optionData\n */\n $optionData = $response->toArray()['options'] ?? [];\n\n $options = [];\n foreach ($optionData as $item) {\n $options[] = [\n 'id' => $item['value'],\n 'value' => $item['value'],\n 'label' => $item['label'],\n 'display_order' => $item['displayOrder'],\n ];\n }\n\n return $options;\n }\n\n /**\n * @return array<CrmFieldOption>\n */\n public function fetchDispositionFieldOptions(): array\n {\n $options = [];\n\n $dispositions = $this->fetchCallDispositions();\n\n foreach ($dispositions as $disposition) {\n if ($disposition['deleted'] !== false) {\n continue;\n }\n\n $option['value'] = $disposition['id'];\n $option['id'] = $disposition['id'];\n $option['label'] = $disposition['label'];\n\n $options[] = $option;\n }\n\n return $options;\n }\n\n /**\n * @return array<CrmFieldOption>\n */\n public function fetchOpportunityFieldOptions(Field $field): array\n {\n if ($field->isStageField()) {\n return $this->fetchOpportunityPipelineStages();\n }\n\n if ($field->isPipelineField()) {\n return $this->fetchOpportunityPipelines();\n }\n\n return $this->fetchPropertyOptions('deals', $field->getCrmProviderId());\n }\n\n /**\n * @throws BadRequest\n * @throws HubspotException\n */\n public function makeRequest(string $endpoint, $method = 'GET', $payload = [], ?string $queryString = null)\n {\n $endpoint = self::BASE_URL . $endpoint;\n\n if ($method === 'GET') {\n $response = $this->getInstance()->getClient()?->request(\n method: $method,\n endpoint: $endpoint,\n query_string: $queryString\n );\n } else {\n $response = $this->getInstance()->getClient()->request($method, $endpoint, [\n 'json' => ($payload),\n ]);\n }\n \n $max = $response->getHeaderLine('X-HubSpot-RateLimit-Max'); // \"110\"\n $remaining = $response->getHeaderLine('X-HubSpot-RateLimit-Remaining'); // \"109\"\n $interval = $response->getHeaderLine('X-HubSpot-RateLimit-Interval-Milliseconds'); // \"10000\"\n $body = json_decode((string) $response->getBody(), true);\n \n \\Illuminate\\Support\\Facades\\Log::channel('custom_channel')->info('$max ' . PHP_EOL . print_r($max, true));\n \\Illuminate\\Support\\Facades\\Log::channel('custom_channel')->info('$remaining ' . PHP_EOL . print_r($remaining, true));\n \\Illuminate\\Support\\Facades\\Log::channel('custom_channel')->info('$interval ' . PHP_EOL . print_r($interval, true));\n \\Illuminate\\Support\\Facades\\Log::channel('custom_channel')->info('$interval ' . PHP_EOL . print_r($interval, true));\n \n return $response;\n }\n\n /**\n * @throws BadRequest\n * @throws HubspotException\n */\n public function createMeeting(array $payload): Response\n {\n $endpoint = '/crm/v3/objects/meetings';\n\n return $this->makeRequest($endpoint, 'POST', $payload);\n }\n\n /**\n * @throws BadRequest\n * @throws HubspotException\n */\n public function updateMeeting(string $meetingId, array $payload): Response\n {\n $endpoint = '/crm/v3/objects/meetings/' . $meetingId;\n\n return $this->makeRequest($endpoint, 'PATCH', $payload);\n }\n\n /**\n * @throws \\Exception\n */\n public function createNote(\n string $body,\n string $ownerId,\n int $timestamp,\n string $objectId,\n NoteObject $noteObject\n ): ?string {\n try {\n $noteInput = new SimplePublicObjectInput([\n 'properties' => [\n 'hs_note_body' => $body,\n 'hubspot_owner_id' => $ownerId,\n 'hs_timestamp' => $timestamp,\n ],\n ]);\n\n // Create note\n $note = $this->getNewInstance()->crm()->objects()->basicApi()->create('note', $noteInput);\n\n $this->getNewInstance()->crm()->objects()->associationsApi()->create(\n 'note',\n $note->getId(),\n $this->getNoteObject($noteObject),\n $objectId,\n $this->getNoteAssociationType($noteObject),\n );\n\n return $note->getId();\n } catch (\\Exception $e) {\n $this->log->error('[Hubspot] Failed to create note', [\n 'objectId' => $objectId,\n 'noteObject' => $noteObject->getObjectType(),\n 'reason' => $e->getMessage(),\n ]);\n\n \\Sentry::captureException($e);\n }\n\n return null;\n }\n\n public function updateEngagement(string $objectId, array $engagement, array $metadata): void\n {\n $this->getInstance()->engagements()->update($objectId, $engagement, $metadata);\n }\n\n public function getEngagementData(string $engagementId): array\n {\n $engagement = $this->getInstance()->engagements()->get($engagementId);\n\n return $engagement->toArray();\n }\n\n public function createEngagement(array $engagement, array $associations, array $metadata): Response\n {\n return $this->getInstance()\n ->engagements()\n ->create($engagement, $associations, $metadata);\n }\n\n public function isUnauthorizedException(\\Exception $e): bool\n {\n // Check for specific HubSpot API exception types first\n if ($e instanceof BadRequest) {\n // BadRequest can contain 401 status codes\n return $e->getCode() === 401;\n }\n\n // Check for HTTP client exceptions with status codes\n if ($e instanceof \\GuzzleHttp\\Exception\\RequestException && $e->hasResponse()) {\n $response = $e->getResponse();\n if ($response !== null) {\n return $response->getStatusCode() === 401;\n }\n }\n\n // Check for Guzzle HTTP exceptions\n if ($e instanceof \\GuzzleHttp\\Exception\\ClientException) {\n return $e->getCode() === 401;\n }\n\n // Fallback to string matching as last resort, but be more specific\n $message = strtolower($e->getMessage());\n\n return str_contains($message, '401 unauthorized') ||\n str_contains($message, 'http 401') ||\n str_contains($message, 'status code 401') ||\n (preg_match('/\\b401\\b/', $message) && str_contains($message, 'unauthorized'));\n }\n\n /**\n * Validates and refreshes the access token if needed before API requests.\n * This ensures long-running processes don't fail due to token expiration.\n *\n * @throws SocialAccountTokenInvalidException\n */\n public function ensureValidToken(): void\n {\n if ($this->oauthAccount === null) {\n return;\n }\n\n $newToken = $this->tokenManager->ensureValidToken($this->oauthAccount);\n if ($newToken !== null) {\n $this->accessToken = $newToken;\n }\n }\n\n public function getConfig()\n {\n return $this->config;\n }\n\n // returns only active (archived=false)\n public function getOwners(): array\n {\n return $this->getNewInstance()->crm()->owners()->getAll();\n }\n\n /**\n * @param bool $archived\n *\n * @return array<Owner>|[]\n */\n public function getOwnersArchived(bool $archived = true): array\n {\n $endpoint = '/crm/v3/owners';\n $queryParams = [\n 'archived' => $archived ? 'true' : 'false',\n ];\n $queryString = http_build_query($queryParams);\n\n $owners = [];\n\n try {\n $response = $this->makeRequest(endpoint: $endpoint, queryString: $queryString);\n $responseData = $response?->toArray();\n\n foreach ($responseData['results'] as $result) {\n try {\n $owners[] = Owner::create($result);\n } catch (Throwable $e) {\n $this->log->error('[HubSpot] Failed to process owner data', [\n 'result' => $result,\n 'error' => $e->getMessage(),\n ]);\n\n continue;\n }\n }\n } catch (Throwable $e) {\n $this->log->error('HubSpot] Failed to fetch owners', [\n 'archived' => $archived,\n 'error' => $e->getMessage(),\n ]);\n\n return [];\n }\n\n return $owners;\n }\n\n public function getMeeting(string $engagementId): ObjectWithAssociations\n {\n return $this->getNewInstance()->crm()->objects()->basicApi()\n ->getById('meeting', $engagementId, null, 'contact,company,deal');\n }\n\n public function deleteEngagement(string $engagementId): void\n {\n $this->getInstance()->engagements()->delete((int) $engagementId);\n }\n\n public function getAssociationsData(array $ids, string $fromObject, string $toObject): array\n {\n $associationData = [];\n $idChunks = array_chunk($ids, self::ASSOCIATIONS_BATCH_SIZE_LIMIT);\n\n foreach ($idChunks as $idChunk) {\n try {\n $batchInput = new \\HubSpot\\Client\\Crm\\Associations\\Model\\BatchInputPublicObjectId();\n $batchInput->setInputs(array_map(function ($id) {\n $publicObjectId = new \\HubSpot\\Client\\Crm\\Associations\\Model\\PublicObjectId();\n $publicObjectId->setId($id);\n\n return $publicObjectId;\n }, $idChunk));\n\n $associatedObjectsData = $this\n ->getNewInstance()\n ->crm()\n ->associations()\n ->batchApi()\n ->read($fromObject, $toObject, $batchInput);\n\n if ($associatedObjectsData instanceof \\HubSpot\\Client\\Crm\\Associations\\Model\\BatchResponsePublicAssociationMulti) {\n foreach ($associatedObjectsData->getResults() as $association) {\n $from = $association->getFrom()->getId();\n $toAssociations = $association->getTo();\n\n if (! empty($toAssociations)) {\n $associationData[$from] = array_map(function ($item) {\n return $item->getId();\n }, $toAssociations);\n }\n }\n }\n } catch (\\Exception $e) {\n $this->log->error('[Hubspot] Failed to fetch associations', [\n 'from_object' => $fromObject,\n 'to_object' => $toObject,\n 'reason' => $e->getMessage(),\n ]);\n }\n }\n\n return $associationData;\n }\n\n /**\n * @throws \\Exception\n */\n private function getNoteAssociationType(NoteObject $noteObject): string\n {\n return match($noteObject) {\n NoteObject::Opportunity => 'note_to_deal',\n NoteObject::Lead, NoteObject::Contact => 'note_to_contact', // or 'note_to_lead' if your portal supports it\n NoteObject::Account => 'note_to_company',\n NoteObject::Call, NoteObject::Event => throw new \\Exception('Not supported'),\n };\n }\n\n /**\n * @throws \\Exception\n */\n private function getNoteObject(NoteObject $noteObject): string\n {\n return match($noteObject) {\n NoteObject::Opportunity => 'deal',\n NoteObject::Lead, NoteObject::Contact => 'contact',\n NoteObject::Account => 'company',\n NoteObject::Call, NoteObject::Event => throw new \\Exception('Not supported'),\n };\n }\n\n public function addAssociations(string $objectType, string $associationType, array $payload): Response\n {\n $endpoint = \"/crm/v4/associations/$objectType/$associationType/batch/create\";\n\n return $this->makeRequest($endpoint, 'POST', $payload);\n }\n\n public function removeAssociations(string $objectType, string $associationType, array $payload): Response\n {\n $endpoint = \"/crm/v4/associations/$objectType/$associationType/batch/archive\";\n\n return $this->makeRequest($endpoint, 'POST', $payload);\n }\n}","role_description":"text entry area","is_enabled":true,"is_focused":true,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"Project","depth":3,"on_screen":false,"role_description":"text"},{"role":"AXButton","text":"Project","depth":3,"bounds":{"left":0.011968086,"top":0.047885075,"width":0.024268618,"height":0.024740623},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"New File or Directory…","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.008643617,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Expand Selected","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.008643617,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Collapse All","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.008643617,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Options","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.008643617,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Hide","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.008643617,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false}]...
|
6582720202572488457
|
5531537304406333540
|
click
|
accessibility
|
NULL
|
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
Execute
Explain Plan
Browse Query History
View Parameters
Open Query Execution Settings…
In-Editor Results
Tx: Auto
Cancel Running Statements
Playground
jiminny
Sync Changes
Hide This Notification
Code changed:
Hide
6
1
6
Previous Highlighted Error
Next Highlighted Error
# [PASSWORD_DOTS] HS [PASSWORD_DOTS]
select * from teams where id = 2; # 2
select * from features; # 2
select * from team_features where team_id = 2; # 2
select * from crm_configurations where id = 2; # 2
select * from users where team_id = 2; #
select * from playbooks where team_id = 2; # event 38
select * from playbook_categories where playbook_id = 38; #
SELECT * FROM activities WHERE crm_configuration_id = 2 and crm_provider_id is not null order by id desc;
[URL_WITH_CREDENTIALS] CrmFieldOption array{id:string, label:string, value?:string}
*/
class Client extends BaseClient implements HubspotClientInterface
{
public const string MIN_API_VERSION = '2';
public const string BASE_URL = '[URL_WITH_CREDENTIALS] T
* @param callable(): T $apiCall
* @return T
*
* @throws RateLimitException
*/
private function executeRequest(callable $apiCall)
{
if (! $this->rateLimiter->canMakeRequest($this->config)) {
$retryAfter = $this->rateLimiter->requestAvailableIn($this->config);
$this->log->warning('[Hubspot] Rate limit exceeded, deferring request', [
'team_id' => $this->config->team_id,
'config_id' => $this->config->getId(),
'retry_after' => $retryAfter,
]);
throw new RateLimitException(
'Hubspot rate limit reached for configuration ' . $this->config->getId(),
$retryAfter,
);
}
$this->rateLimiter->incrementRequestCount($this->config);
try {
return $apiCall();
} catch (Throwable $e) {
if ($this->isHubspotRateLimit($e)) {
$retryAfter = $this->parseRetryAfter($e);
$this->log->warning('[Hubspot] Received 429 from API', [
'team_id' => $this->config->team_id,
'config_id' => $this->config->getId(),
'retry_after' => $retryAfter,
'reason' => $e->getMessage(),
]);
throw new RateLimitException('Hubspot returned 429', $retryAfter, $e);
}
throw $e;
}
}
private function isHubspotRateLimit(Throwable $e): bool
{
return method_exists($e, 'getCode') && (int) $e->getCode() === 429;
}
private function parseRetryAfter(Throwable $e): int
{
if (method_exists($e, 'getResponseHeaders')) {
$headers = $e->getResponseHeaders() ?: [];
$value = $headers['Retry-After'] ?? $headers['retry-after'] ?? null;
if (is_array($value)) {
$value = $value[0] ?? null;
}
if (is_numeric($value)) {
return (int) $value;
}
}
return 10;
}
public function getMinimumApiVersion(): string
{
return self::MIN_API_VERSION;
}
public function getInstance(): Factory
{
return new Factory([
'key' => $this->accessToken,
'oauth2' => true,
'base_url' => $this->baseUrl,
]);
}
public function getNewInstance(): Discovery
{
return \HubSpot\Factory::createWithAccessToken($this->accessToken);
}
/**
* Secondly and daily limits for Hubspot API
*
* Product Tier: Free & Starter | Professional & Enterprise | API add-on (any tier)
* Burst: 100/10 seconds | 150/10 seconds | 200/10 seconds
* Daily: 250,000 | 500,000 | 1,000,000
*
* Official documentation states: The search endpoints are rate limited to five requests per second.
* Since with 5 RPS were still hitting secondly rate limits we lowered it to 4
*/
public function getPaginatedData(array $payload, string $type, int $offset = 0): array
{
$total = 0;
$lastId = null;
$rows = [];
foreach ($this->getPaginatedDataGenerator($payload, $type, $offset, $total, $lastId) as $row) {
$rows[] = $row;
}
return ['results' => $rows, 'total' => $total, 'last_record' => $lastId];
}
/**
* @throws HubspotException
* @throws SocialAccountTokenInvalidException
* @throws BadRequest
*/
public function getPaginatedDataGenerator(
array $payload,
string $type,
int $offset = 0,
int &$total = 0,
?string &$lastRecordId = null
): \Generator {
return $this->paginationService->getPaginatedDataGenerator(
$this,
$payload,
$type,
$offset,
$total,
$lastRecordId
);
}
/**
* @throws DealApiException
* @throws CrmException
*/
public function getOpportunityById(string $crmId, array $fields): array
{
try {
$deal = $this->executeRequest(fn () => $this->getNewInstance()->crm()->deals()->basicApi()->getById(
$crmId,
implode(',', $fields),
'companies,contacts'
));
} catch (DealApiException $e) {
$this->log->info('[Hubspot] Failed to fetch opportunity', [
'crm_id' => $crmId,
'reason' => $e->getMessage(),
]);
throw $e;
}
if (! $deal instanceof DealWithAssociations) {
throw new CrmException('Deal not found');
}
return [
'id' => $deal->getId(),
'properties' => $deal->getProperties(),
'associations' => $deal->getAssociations(),
];
}
/**
* Generic batch read method for HubSpot objects
*
* @param string $objectType The object type ('deals', 'companies', 'contacts')
* @param array<string> $crmIds Array of HubSpot object IDs (max 100)
* @param array<string> $fields Array of property names to fetch
*
* @return array<string, array> Array keyed by CRM ID with object data
*/
private function batchReadObjects(string $objectType, array $crmIds, array $fields): array
{
if (empty($crmIds)) {
return [];
}
$this->validateBatchSize($objectType, $crmIds);
$this->ensureValidToken();
try {
$batchConfig = $this->createBatchConfiguration($objectType);
$batchReadRequest = $this->prepareBatchRequest($batchConfig, $crmIds, $fields);
$response = $batchConfig['api']->read($batchReadRequest);
$this->validateApiResponse($response, $objectType);
$results = $this->processApiResults($response);
$this->logBatchResults($objectType, $crmIds, $results);
return $results;
} catch (\Throwable $e) {
$this->handleBatchError($e, $objectType, $crmIds);
}
}
private function validateBatchSize(string $objectType, array $crmIds): void
{
if (count($crmIds) > 100) {
throw new \InvalidArgumentException("Batch size cannot exceed 100 {$objectType}");
}
}
private function createBatchConfiguration(string $objectType): array
{
$configurations = [
'deals' => [
'batchReadRequest' => new \HubSpot\Client\Crm\Deals\Model\BatchReadInputSimplePublicObjectId(),
'inputClass' => \HubSpot\Client\Crm\Deals\Model\SimplePublicObjectId::class,
'api' => $this->getNewInstance()->crm()->deals()->batchApi(),
],
'companies' => [
'batchReadRequest' => new \HubSpot\Client\Crm\Companies\Model\BatchReadInputSimplePublicObjectId(),
'inputClass' => \HubSpot\Client\Crm\Companies\Model\SimplePublicObjectId::class,
'api' => $this->getNewInstance()->crm()->companies()->batchApi(),
],
'contacts' => [
'batchReadRequest' => new \HubSpot\Client\Crm\Contacts\Model\BatchReadInputSimplePublicObjectId(),
'inputClass' => \HubSpot\Client\Crm\Contacts\Model\SimplePublicObjectId::class,
'api' => $this->getNewInstance()->crm()->contacts()->batchApi(),
],
];
if (! isset($configurations[$objectType])) {
throw new \InvalidArgumentException("Unsupported object type: {$objectType}");
}
return $configurations[$objectType];
}
private function prepareBatchRequest(array $batchConfig, array $crmIds, array $fields): object
{
$batchReadRequest = $batchConfig['batchReadRequest'];
$inputClass = $batchConfig['inputClass'];
$inputs = array_map(function ($crmId) use ($inputClass) {
$input = new $inputClass();
$input->setId($crmId);
return $input;
}, $crmIds);
$batchReadRequest->setInputs($inputs);
$batchReadRequest->setProperties($fields);
return $batchReadRequest;
}
private function validateApiResponse($response, string $objectType): void
{
if (! $response) {
throw new CrmException("HubSpot API returned null response for {$objectType} batch read");
}
}
private function processApiResults($response): array
{
$results = [];
$responseResults = $response->getResults();
if ($responseResults) {
foreach ($responseResults as $object) {
if ($object && $object->getId()) {
$results[$object->getId()] = [
'id' => $object->getId(),
'properties' => $object->getProperties() ?: [],
];
}
}
}
return $results;
}
private function logBatchResults(string $objectType, array $crmIds, array $results): void
{
$this->log->info("[HubSpot] Batch fetched {$objectType}", [
'requested_count' => count($crmIds),
'returned_count' => count($results),
'crm_ids' => $crmIds,
]);
}
private function handleBatchError(\Throwable $e, string $objectType, array $crmIds): void
{
$errorMessage = $e->getMessage() ?: 'Unknown error';
$errorTrace = $e->getTraceAsString() ?: 'No trace available';
$this->log->error("[HubSpot] Failed to batch fetch {$objectType}", [
'crm_ids' => $crmIds,
'error' => $errorMessage,
'trace' => $errorTrace,
]);
throw new CrmException("Failed to batch fetch {$objectType}: " . $errorMessage);
}
/**
* Batch read multiple opportunities by their CRM IDs
*
* @param array<string> $crmIds Array of HubSpot deal IDs (max 100)
* @param array<string> $fields Array of property names to fetch
*
* @return array<string, array> Array keyed by CRM ID with opportunity data
*/
public function getOpportunitiesByIds(array $crmIds, array $fields): array
{
return $this->batchReadObjects('deals', $crmIds, $fields);
}
/**
* Batch read multiple companies by their CRM IDs
*
* @param array<string> $crmIds Array of HubSpot company IDs (max 100)
* @param array<string> $fields Array of property names to fetch
*
* @return array<string, array> Array keyed by CRM ID with company data
*/
public function getCompaniesByIds(array $crmIds, array $fields): array
{
return $this->batchReadObjects('companies', $crmIds, $fields);
}
/**
* Batch read multiple contacts by their CRM IDs
*
* @param array<string> $crmIds Array of HubSpot contact IDs (max 100)
* @param array<string> $fields Array of property names to fetch
*
* @return array<string, array> Array keyed by CRM ID with contact data
*/
public function getContactsByIds(array $crmIds, array $fields): array
{
return $this->batchReadObjects('contacts', $crmIds, $fields);
}
/**
* @throws CompanyApiException
* @throws CrmException
*/
public function getAccountById(string $crmId, array $fields): array
{
try {
$company = $this->getNewInstance()->crm()->companies()->basicApi()->getById(
$crmId,
implode(',', $fields),
);
} catch (CompanyApiException $e) {
$this->log->info('[Hubspot] Failed to fetch account', [
'crm_id' => $crmId,
'reason' => $e->getMessage(),
]);
throw $e;
}
if (! $company instanceof CompaniesWithAssociations) {
throw new CrmException('Account not found');
}
return [
'id' => $company->getId(),
'properties' => $company->getProperties(),
];
}
/**
* @throws ContactApiException
* @throws CrmException
*/
public function getContactById(string $crmId, array $fields): array
{
try {
$contact = $this->getNewInstance()->crm()->contacts()->basicApi()->getById(
$crmId,
implode(',', $fields)
);
} catch (ContactApiException $e) {
$this->log->info('[Hubspot] Failed to fetch contact', [
'crm_id' => $crmId,
'reason' => $e->getMessage(),
]);
throw $e;
}
if (! $contact instanceof ContactsWithAssociations) {
throw new CrmException('Contact not found');
}
return [
'id' => $contact->getId(),
'properties' => $contact->getProperties(),
];
}
/**
* This is email search request that Hubspot offers as GET (more generous quota)
*/
public function getContactByEmail(string $email, array $fields = []): array
{
try {
$contact = $this->getNewInstance()->crm()->contacts()->basicApi()->getById(
$email,
implode(',', $fields),
null,
false,
'email'
);
return [
'id' => $contact->getId(),
'properties' => $contact->getProperties(),
];
} catch (ContactApiException $e) {
$this->log->info('[Hubspot] Failed to fetch contact', [
'email' => $email,
'reason' => $e->getMessage(),
]);
return [];
}
}
/**
* @throws CrmException
*/
public function fetchProperty(string $objectType, string $propertyId): Property
{
$result = $this->getNewInstance()->crm()->properties()->coreApi()->getByName($objectType, $propertyId);
if (! $result instanceof Property) {
$this->log->error('[Hubspot] Failed to fetch property', [
'object_type' => $objectType,
'property_id' => $propertyId,
'reason' => $result->getMessage(),
]);
throw new CrmException('Failed to fetch property');
}
return $result;
}
/**
* @return array<CrmFieldOption>
*/
public function fetchPropertyOptions(string $objectType, string $propertyId): array
{
/** @var array<CrmFieldOption> */
return $this->fetchProperty($objectType, $propertyId)->getOptions();
}
/**
* @return array<array{id:string, label:string, deleted:bool}>
*/
public function fetchCallDispositions(): array
{
/** @var Response $response */
$response = $this->getInstance()->engagements()->getCallDispositions();
/**
* @var array<array{
* id:string,
* label:string,
* deleted: bool
* }>
*/
return $response->toArray();
}
/**
* @return array<CrmFieldOption>
*/
public function fetchOpportunityPipelineStages(): array
{
$stages = [];
$apiResponse = $this->getNewInstance()->crm()->pipelines()->pipelinesApi()->getAll('deals');
if ($apiResponse instanceof Error) {
$this->log->error('[Hubspot] Failed to fetch opportunity pipelines', [
'reason' => $apiResponse->getMessage(),
]);
return [];
}
foreach ($apiResponse->getResults() as $pipeline) {
$pipelineStages = array_map(
static function (PipelineStage $stage) {
return [
'id' => $stage->getId(),
'label' => $stage->getLabel(),
];
},
$pipeline->getStages()
);
$stages = array_merge($stages, $pipelineStages);
}
return $stages;
}
public function fetchOpportunityPipelines(): array
{
$pipelines = [];
try {
$apiResponse = $this->makeRequest('/crm/v3/pipelines/deals');
} catch (\Exception $e) {
$this->log->info('[Hubspot] Failed to fetch opportunity pipelines', [
'reason' => $e->getMessage(),
]);
return [];
}
$response = $apiResponse->toArray();
foreach ($response['results'] as $pipeline) {
$pipelines[] = [
'id' => $pipeline['id'],
'label' => $pipeline['label'],
];
}
return $pipelines;
}
/**
* @return array<CrmFieldOption>
*/
public function fetchMeetingOutcomeFieldOptions(Field $field): array
{
return $field->getCrmProviderId() === 'meetingOutcome'
? $this->fetchMeetingOutcomeTypes()
: $this->fetchCallActivityTypes();
}
public function fetchMeetingOutcomeTypes(): array
{
return $this->extractMeetingTypeOptions(
'[URL_WITH_CREDENTIALS] Response $response */
$response = $this->getInstance()
->getClient()
->request('GET', $endpoint);
/**
* @var array<array{
* value: string,
* label: string,
* displayOrder: int
* }> $optionData
*/
$optionData = $response->toArray()['options'] ?? [];
$options = [];
foreach ($optionData as $item) {
$options[] = [
'id' => $item['value'],
'value' => $item['value'],
'label' => $item['label'],
'display_order' => $item['displayOrder'],
];
}
return $options;
}
/**
* @return array<CrmFieldOption>
*/
public function fetchDispositionFieldOptions(): array
{
$options = [];
$dispositions = $this->fetchCallDispositions();
foreach ($dispositions as $disposition) {
if ($disposition['deleted'] !== false) {
continue;
}
$option['value'] = $disposition['id'];
$option['id'] = $disposition['id'];
$option['label'] = $disposition['label'];
$options[] = $option;
}
return $options;
}
/**
* @return array<CrmFieldOption>
*/
public function fetchOpportunityFieldOptions(Field $field): array
{
if ($field->isStageField()) {
return $this->fetchOpportunityPipelineStages();
}
if ($field->isPipelineField()) {
return $this->fetchOpportunityPipelines();
}
return $this->fetchPropertyOptions('deals', $field->getCrmProviderId());
}
/**
* @throws BadRequest
* @throws HubspotException
*/
public function makeRequest(string $endpoint, $method = 'GET', $payload = [], ?string $queryString = null)
{
$endpoint = self::BASE_URL . $endpoint;
if ($method === 'GET') {
$response = $this->getInstance()->getClient()?->request(
method: $method,
endpoint: $endpoint,
query_string: $queryString
);
} else {
$response = $this->getInstance()->getClient()->request($method, $endpoint, [
'json' => ($payload),
]);
}
$max = $response->getHeaderLine('X-HubSpot-RateLimit-Max'); // "110"
$remaining = $response->getHeaderLine('X-HubSpot-RateLimit-Remaining'); // "109"
$interval = $response->getHeaderLine('X-HubSpot-RateLimit-Interval-Milliseconds'); // "10000"
$body = json_decode((string) $response->getBody(), true);
\Illuminate\Support\Facades\Log::channel('custom_channel')->info('$max ' . PHP_EOL . print_r($max, true));
\Illuminate\Support\Facades\Log::channel('custom_channel')->info('$remaining ' . PHP_EOL . print_r($remaining, true));
\Illuminate\Support\Facades\Log::channel('custom_channel')->info('$interval ' . PHP_EOL . print_r($interval, true));
\Illuminate\Support\Facades\Log::channel('custom_channel')->info('$interval ' . PHP_EOL . print_r($interval, true));
return $response;
}
/**
* @throws BadRequest
* @throws HubspotException
*/
public function createMeeting(array $payload): Response
{
$endpoint = '/crm/v3/objects/meetings';
return $this->makeRequest($endpoint, 'POST', $payload);
}
/**
* @throws BadRequest
* @throws HubspotException
*/
public function updateMeeting(string $meetingId, array $payload): Response
{
$endpoint = '/crm/v3/objects/meetings/' . $meetingId;
return $this->makeRequest($endpoint, 'PATCH', $payload);
}
/**
* @throws \Exception
*/
public function createNote(
string $body,
string $ownerId,
int $timestamp,
string $objectId,
NoteObject $noteObject
): ?string {
try {
$noteInput = new SimplePublicObjectInput([
'properties' => [
'hs_note_body' => $body,
'hubspot_owner_id' => $ownerId,
'hs_timestamp' => $timestamp,
],
]);
// Create note
$note = $this->getNewInstance()->crm()->objects()->basicApi()->create('note', $noteInput);
$this->getNewInstance()->crm()->objects()->associationsApi()->create(
'note',
$note->getId(),
$this->getNoteObject($noteObject),
$objectId,
$this->getNoteAssociationType($noteObject),
);
return $note->getId();
} catch (\Exception $e) {
$this->log->error('[Hubspot] Failed to create note', [
'objectId' => $objectId,
'noteObject' => $noteObject->getObjectType(),
'reason' => $e->getMessage(),
]);
\Sentry::captureException($e);
}
return null;
}
public function updateEngagement(string $objectId, array $engagement, array $metadata): void
{
$this->getInstance()->engagements()->update($objectId, $engagement, $metadata);
}
public function getEngagementData(string $engagementId): array
{
$engagement = $this->getInstance()->engagements()->get($engagementId);
return $engagement->toArray();
}
public function createEngagement(array $engagement, array $associations, array $metadata): Response
{
return $this->getInstance()
->engagements()
->create($engagement, $associations, $metadata);
}
public function isUnauthorizedException(\Exception $e): bool
{
// Check for specific HubSpot API exception types first
if ($e instanceof BadRequest) {
// BadRequest can contain 401 status codes
return $e->getCode() === 401;
}
// Check for HTTP client exceptions with status codes
if ($e instanceof \GuzzleHttp\Exception\RequestException && $e->hasResponse()) {
$response = $e->getResponse();
if ($response !== null) {
return $response->getStatusCode() === 401;
}
}
// Check for Guzzle HTTP exceptions
if ($e instanceof \GuzzleHttp\Exception\ClientException) {
return $e->getCode() === 401;
}
// Fallback to string matching as last resort, but be more specific
$message = strtolower($e->getMessage());
return str_contains($message, '401 unauthorized') ||
str_contains($message, 'http 401') ||
str_contains($message, 'status code 401') ||
(preg_match('/\b401\b/', $message) && str_contains($message, 'unauthorized'));
}
/**
* Validates and refreshes the access token if needed before API requests.
* This ensures long-running processes don't fail due to token expiration.
*
* @throws SocialAccountTokenInvalidException
*/
public function ensureValidToken(): void
{
if ($this->oauthAccount === null) {
return;
}
$newToken = $this->tokenManager->ensureValidToken($this->oauthAccount);
if ($newToken !== null) {
$this->accessToken = $newToken;
}
}
public function getConfig()
{
return $this->config;
}
// returns only active (archived=false)
public function getOwners(): array
{
return $this->getNewInstance()->crm()->owners()->getAll();
}
/**
* @param bool $archived
*
* @return array<Owner>|[]
*/
public function getOwnersArchived(bool $archived = true): array
{
$endpoint = '/crm/v3/owners';
$queryParams = [
'archived' => $archived ? 'true' : 'false',
];
$queryString = http_build_query($queryParams);
$owners = [];
try {
$response = $this->makeRequest(endpoint: $endpoint, queryString: $queryString);
$responseData = $response?->toArray();
foreach ($responseData['results'] as $result) {
try {
$owners[] = Owner::create($result);
} catch (Throwable $e) {
$this->log->error('[HubSpot] Failed to process owner data', [
'result' => $result,
'error' => $e->getMessage(),
]);
continue;
}
}
} catch (Throwable $e) {
$this->log->error('HubSpot] Failed to fetch owners', [
'archived' => $archived,
'error' => $e->getMessage(),
]);
return [];
}
return $owners;
}
public function getMeeting(string $engagementId): ObjectWithAssociations
{
return $this->getNewInstance()->crm()->objects()->basicApi()
->getById('meeting', $engagementId, null, 'contact,company,deal');
}
public function deleteEngagement(string $engagementId): void
{
$this->getInstance()->engagements()->delete((int) $engagementId);
}
public function getAssociationsData(array $ids, string $fromObject, string $toObject): array
{
$associationData = [];
$idChunks = array_chunk($ids, self::ASSOCIATIONS_BATCH_SIZE_LIMIT);
foreach ($idChunks as $idChunk) {
try {
$batchInput = new \HubSpot\Client\Crm\Associations\Model\BatchInputPublicObjectId();
$batchInput->setInputs(array_map(function ($id) {
$publicObjectId = new \HubSpot\Client\Crm\Associations\Model\PublicObjectId();
$publicObjectId->setId($id);
return $publicObjectId;
}, $idChunk));
$associatedObjectsData = $this
->getNewInstance()
->crm()
->associations()
->batchApi()
->read($fromObject, $toObject, $batchInput);
if ($associatedObjectsData instanceof \HubSpot\Client\Crm\Associations\Model\BatchResponsePublicAssociationMulti) {
foreach ($associatedObjectsData->getResults() as $association) {
$from = $association->getFrom()->getId();
$toAssociations = $association->getTo();
if (! empty($toAssociations)) {
$associationData[$from] = array_map(function ($item) {
return $item->getId();
}, $toAssociations);
}
}
}
} catch (\Exception $e) {
$this->log->error('[Hubspot] Failed to fetch associations', [
'from_object' => $fromObject,
'to_object' => $toObject,
'reason' => $e->getMessage(),
]);
}
}
return $associationData;
}
/**
* @throws \Exception
*/
private function getNoteAssociationType(NoteObject $noteObject): string
{
return match($noteObject) {
NoteObject::Opportunity => 'note_to_deal',
NoteObject::Lead, NoteObject::Contact => 'note_to_contact', // or 'note_to_lead' if your portal supports it
NoteObject::Account => 'note_to_company',
NoteObject::Call, NoteObject::Event => throw new \Exception('Not supported'),
};
}
/**
* @throws \Exception
*/
private function getNoteObject(NoteObject $noteObject): string
{
return match($noteObject) {
NoteObject::Opportunity => 'deal',
NoteObject::Lead, NoteObject::Contact => 'contact',
NoteObject::Account => 'company',
NoteObject::Call, NoteObject::Event => throw new \Exception('Not supported'),
};
}
public function addAssociations(string $objectType, string $associationType, array $payload): Response
{
$endpoint = "/crm/v4/associations/$objectType/$associationType/batch/create";
return $this->makeRequest($endpoint, 'POST', $payload);
}
public function removeAssociations(string $objectType, string $associationType, array $payload): Response
{
$endpoint = "/crm/v4/associations/$objectType/$associationType/batch/archive";
return $this->makeRequest($endpoint, 'POST', $payload);
}
}
Project
Project
New File or Directory…
Expand Selected
Collapse All
Options
Hide...
|
2684
|
NULL
|
NULL
|
NULL
|
|
2688
|
111
|
14
|
2026-05-07T11:35:45.183858+00:00
|
/Users/lukas/.screenpipe/data/data/2026-05-07/1778 /Users/lukas/.screenpipe/data/data/2026-05-07/1778153745183_m1.jpg...
|
PhpStorm
|
faVsco.js – Hubspot/Client.php
|
True
|
NULL
|
monitor_1
|
NULL
|
NULL
|
NULL
|
NULL
|
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
Execute
Explain Plan
Browse Query History
View Parameters
Open Query Execution Settings…
In-Editor Results
Tx: Auto
Cancel Running Statements
Playground
jiminny
Sync Changes
Hide This Notification
Code changed:
Hide
6
1
6
Previous Highlighted Error
Next Highlighted Error
# [PASSWORD_DOTS] HS [PASSWORD_DOTS]
select * from teams where id = 2; # 2
select * from features; # 2
select * from team_features where team_id = 2; # 2
select * from crm_configurations where id = 2; # 2
select * from users where team_id = 2; #
select * from playbooks where team_id = 2; # event 38
select * from playbook_categories where playbook_id = 38; #
SELECT * FROM activities WHERE crm_configuration_id = 2 and crm_provider_id is not null order by id desc;
https://app.hubspot.com/contacts/4392066/deal/16964514951/?engagement=96069102624
https://app.staging.jiminny.com/playback/d5df34dc-bd66-4ff5-a7b3-8d3be30322a0
SELECT * FROM activities WHERE uuid_to_bin('04fdcd0d-818f-4c53-92dc-6f18bc753ffd') = uuid;
# 609126 softphone tr. 11241
SELECT * FROM activities WHERE uuid_to_bin('6521bfcd-5a30-46e5-9f74-5440fd48befd') = uuid;
# 608874 conference tr. 11226 crmId: 103422236596
select * from ai_prompts where transcription_id IN (11241, 11226);
select * from activity_summary_logs where activity_id = 608874;
select * from sidekick_settings;
select * from default_activity_types;
select * from crm_field_data where activity_id = 1223;
select * from crm_layouts where crm_configuration_id = 2;
SELECT * FROM crm_layout_entities WHERE crm_layout_id IN (554);
select * from crm_fields where crm_configuration_id = 11 and object_type = 'event';
SELECT * FROM crm_field_values WHERE crm_field_id IN (1455,1450);
SELECT * FROM crm_field_data WHERE crm_layout_entity_id = 971;
SELECT * FROM crm_field_data WHERE crm_layout_entity_id IN (6494,6495,6496,6497,6498,6499);
SELECT
CONCAT(u.id, CASE WHEN u.id = t.owner_id THEN ' (owner)' ELSE '' END) AS user_id,
u.email,
sa.*,
t.owner_id FROM social_accounts sa
JOIN users u
on u.id = sa.sociable_id
JOIN teams t on t.id = u.team_id
WHERE u.team_id = 2 and sa.provider = 'hubspot';
select * from social_accounts where id = 1499;
select * from opportunities where team_id = 2
and crm_provider_id IN ('51317301383');
select * from contacts where id = 85;
select * from opportunities where team_id = 2 order by id desc;
select * from opportunities where team_id = 2 and crm_provider_id = '51317301383'; # 5112
select * from opportunities where team_id = 2 and crm_provider_id = '55976759904'; # 5112
select * from opportunity_contacts where opportunity_id = 5117;
select * from crm_field_data where object_id = 1365;
SELECT * FROM crm_fields WHERE id IN (1405, 1407, 1972, 2128);
select * from features;
select * from team_features where team_id IN (1);
select * from team_features where feature_id IN (36);
SHOW CREATE TABLE opportunity_contacts;
SELECT * FROM opportunity_contacts WHERE crm_provider_id = '111751';
# $slug = 'HUBSPOT_WEBHOOK_SYNC';
# $team = Jiminny\Models\Team::find(2);
# $feature = Feature::query()->where('slug', $slug)->first();
# TeamFeature::query()->create(['feature_id' => $feature->getId(),'team_id' => $team->getId()]);
# hubspot_webhook_metrics
select * from opportunities where team_id = 2 and crm_provider_id IN ('374720564','14527423589','49908861993','50435771779'); # 1365
SELECT * FROM opportunity_contacts WHERE opportunity_id = '414';
SELECT * FROM opportunity_contacts WHERE crm_provider_id = '131501';
select * from contacts where id in (414, 464);
select * from activities where crm_configuration_id = 2;
select settings from crm_configurations where id = 11;
select * from teams; # 1, 2
select * from users;
select * from crm_configurations where id = 39;
select * from team_features where team_id = 2;
select * from features;
# SELECT * FROM opportunities WHERE crm_configuration_id = 2
# order by id desc;
# and crm_provider_id = '49908861993';
select * from activity_providers where id IN (443, 202, 203, 227);
select * from activity_imports where id = 795889;
select c.id, c.provider, c.settings, t.* from teams t join crm_configurations c on t.id = c.team_id
where c.provider = 'hubspot';
select * from crm_configurations crm JOIN teams t on crm.team_id = t.id
where provider = 'hubspot';
SELECT * FROM teams WHERE id = 31;
SELECT * FROM users WHERE id = 257;
SELECT * FROM opportunities WHERE team_id = 2;
select * from opportunity_contacts where opportunity_id = 5124;
select * from contacts where id IN (3850,3853,3851,4073,4140,4155,4480,4530,4623,5986,513,687,1806,1523,3613)
select * from activities where crm_configuration_id = 13;
SELECT * FROM activities WHERE uuid_to_bin('826619ce-ec8e-4e59-8467-a01f5f6ad71e') = uuid; # 418141
select id, team_id, crm_provider_id from crm_configurations where provider = 'hubspot' and crm_provider_id IS NOT NULL;
SELECT * FROM accounts WHERE team_id = 2 and crm_provider_id = '1212213464' order by id desc;
SELECT * FROM contacts WHERE team_id = 2 and account_id = 5189 order by id desc;
SELECT * FROM contacts WHERE team_id = 2 order by id desc;
select * from opportunity_contacts where contact_id = 6223;
SELECT * FROM opportunities WHERE team_id = 2 and account_id = 5189 order by id desc;
select * from crm_profiles where crm_configuration_id = 2;
select * from activities where account_id = 46;
Sync Changes
Hide This Notification
Code changed:...
|
[{"role":"AXButton","text" [{"role":"AXButton","text":"Project: faVsco.js, menu","depth":5,"on_screen":true,"help_text":"~/jiminny/app","role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"master, menu","depth":5,"on_screen":true,"help_text":"Git Branch: master","role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Start Listening for PHP Debug Connections","depth":5,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"AskJiminnyReportActivityServiceTest","depth":6,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Run 'AskJiminnyReportActivityServiceTest'","depth":6,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Debug 'AskJiminnyReportActivityServiceTest'","depth":6,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"More Actions","depth":6,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"JetBrains AI","depth":5,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Search Everywhere","depth":5,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"IDE and Project Settings","depth":5,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Execute","depth":4,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Explain Plan","depth":4,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Browse Query History","depth":4,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"View Parameters","depth":4,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Open Query Execution Settings…","depth":4,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"In-Editor Results","depth":4,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Tx: Auto","depth":4,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Cancel Running Statements","depth":4,"on_screen":true,"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Playground","depth":4,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"jiminny","depth":4,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Sync Changes","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.018055556,"height":0.026666667},"on_screen":false,"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Hide This Notification","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.018055556,"height":0.026666667},"on_screen":false,"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Code changed:","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.088194445,"height":0.027777778},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Hide","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.018055556,"height":0.026666667},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"6","depth":4,"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"1","depth":4,"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"6","depth":4,"on_screen":true,"role_description":"text"},{"role":"AXButton","text":"Previous Highlighted Error","depth":4,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Next Highlighted Error","depth":4,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXTextArea","text":"# **************************** HS **************************************\n\nselect * from teams where id = 2; # 2\nselect * from features; # 2\nselect * from team_features where team_id = 2; # 2\nselect * from crm_configurations where id = 2; # 2\nselect * from users where team_id = 2; #\nselect * from playbooks where team_id = 2; # event 38\nselect * from playbook_categories where playbook_id = 38; #\n\nSELECT * FROM activities WHERE crm_configuration_id = 2 and crm_provider_id is not null order by id desc;\nhttps://app.hubspot.com/contacts/4392066/deal/16964514951/?engagement=96069102624\n https://app.staging.jiminny.com/playback/d5df34dc-bd66-4ff5-a7b3-8d3be30322a0\n\nSELECT * FROM activities WHERE uuid_to_bin('04fdcd0d-818f-4c53-92dc-6f18bc753ffd') = uuid;\n# 609126 softphone tr. 11241\n\nSELECT * FROM activities WHERE uuid_to_bin('6521bfcd-5a30-46e5-9f74-5440fd48befd') = uuid;\n# 608874 conference tr. 11226 crmId: 103422236596\n\nselect * from ai_prompts where transcription_id IN (11241, 11226);\nselect * from activity_summary_logs where activity_id = 608874;\n\nselect * from sidekick_settings;\nselect * from default_activity_types;\n\nselect * from crm_field_data where activity_id = 1223;\n\nselect * from crm_layouts where crm_configuration_id = 2;\nSELECT * FROM crm_layout_entities WHERE crm_layout_id IN (554);\nselect * from crm_fields where crm_configuration_id = 11 and object_type = 'event';\nSELECT * FROM crm_field_values WHERE crm_field_id IN (1455,1450);\n\nSELECT * FROM crm_field_data WHERE crm_layout_entity_id = 971;\nSELECT * FROM crm_field_data WHERE crm_layout_entity_id IN (6494,6495,6496,6497,6498,6499);\n\nSELECT\n CONCAT(u.id, CASE WHEN u.id = t.owner_id THEN ' (owner)' ELSE '' END) AS user_id,\n u.email,\n sa.*,\n t.owner_id FROM social_accounts sa\nJOIN users u\n on u.id = sa.sociable_id\nJOIN teams t on t.id = u.team_id\nWHERE u.team_id = 2 and sa.provider = 'hubspot';\n\nselect * from social_accounts where id = 1499;\n\nselect * from opportunities where team_id = 2\nand crm_provider_id IN ('51317301383');\n\nselect * from contacts where id = 85;\n\nselect * from opportunities where team_id = 2 order by id desc;\nselect * from opportunities where team_id = 2 and crm_provider_id = '51317301383'; # 5112\nselect * from opportunities where team_id = 2 and crm_provider_id = '55976759904'; # 5112\nselect * from opportunity_contacts where opportunity_id = 5117;\nselect * from crm_field_data where object_id = 1365;\nSELECT * FROM crm_fields WHERE id IN (1405, 1407, 1972, 2128);\n\nselect * from features;\nselect * from team_features where team_id IN (1);\nselect * from team_features where feature_id IN (36);\n\nSHOW CREATE TABLE opportunity_contacts;\nSELECT * FROM opportunity_contacts WHERE crm_provider_id = '111751';\n\n# $slug = 'HUBSPOT_WEBHOOK_SYNC';\n# $team = Jiminny\\Models\\Team::find(2);\n# $feature = Feature::query()->where('slug', $slug)->first();\n# TeamFeature::query()->create(['feature_id' => $feature->getId(),'team_id' => $team->getId()]);\n\n# hubspot_webhook_metrics\n\nselect * from opportunities where team_id = 2 and crm_provider_id IN ('374720564','14527423589','49908861993','50435771779'); # 1365\nSELECT * FROM opportunity_contacts WHERE opportunity_id = '414';\nSELECT * FROM opportunity_contacts WHERE crm_provider_id = '131501';\nselect * from contacts where id in (414, 464);\n\nselect * from activities where crm_configuration_id = 2;\n\nselect settings from crm_configurations where id = 11;\n\nselect * from teams; # 1, 2\nselect * from users;\nselect * from crm_configurations where id = 39;\nselect * from team_features where team_id = 2;\nselect * from features;\n# SELECT * FROM opportunities WHERE crm_configuration_id = 2\n# order by id desc;\n# and crm_provider_id = '49908861993';\n\n\nselect * from activity_providers where id IN (443, 202, 203, 227);\n\nselect * from activity_imports where id = 795889;\n\nselect c.id, c.provider, c.settings, t.* from teams t join crm_configurations c on t.id = c.team_id\nwhere c.provider = 'hubspot';\n\nselect * from crm_configurations crm JOIN teams t on crm.team_id = t.id\nwhere provider = 'hubspot';\nSELECT * FROM teams WHERE id = 31;\nSELECT * FROM users WHERE id = 257;\nSELECT * FROM opportunities WHERE team_id = 2;\n\nselect * from opportunity_contacts where opportunity_id = 5124;\nselect * from contacts where id IN (3850,3853,3851,4073,4140,4155,4480,4530,4623,5986,513,687,1806,1523,3613)\n\nselect * from activities where crm_configuration_id = 13;\n\nSELECT * FROM activities WHERE uuid_to_bin('826619ce-ec8e-4e59-8467-a01f5f6ad71e') = uuid; # 418141\n\n\nselect id, team_id, crm_provider_id from crm_configurations where provider = 'hubspot' and crm_provider_id IS NOT NULL;\nSELECT * FROM accounts WHERE team_id = 2 and crm_provider_id = '1212213464' order by id desc;\nSELECT * FROM contacts WHERE team_id = 2 and account_id = 5189 order by id desc;\nSELECT * FROM contacts WHERE team_id = 2 order by id desc;\nselect * from opportunity_contacts where contact_id = 6223;\nSELECT * FROM opportunities WHERE team_id = 2 and account_id = 5189 order by id desc;\n\nselect * from crm_profiles where crm_configuration_id = 2;\n\nselect * from activities where account_id = 46;","depth":4,"on_screen":true,"value":"# **************************** HS **************************************\n\nselect * from teams where id = 2; # 2\nselect * from features; # 2\nselect * from team_features where team_id = 2; # 2\nselect * from crm_configurations where id = 2; # 2\nselect * from users where team_id = 2; #\nselect * from playbooks where team_id = 2; # event 38\nselect * from playbook_categories where playbook_id = 38; #\n\nSELECT * FROM activities WHERE crm_configuration_id = 2 and crm_provider_id is not null order by id desc;\nhttps://app.hubspot.com/contacts/4392066/deal/16964514951/?engagement=96069102624\n https://app.staging.jiminny.com/playback/d5df34dc-bd66-4ff5-a7b3-8d3be30322a0\n\nSELECT * FROM activities WHERE uuid_to_bin('04fdcd0d-818f-4c53-92dc-6f18bc753ffd') = uuid;\n# 609126 softphone tr. 11241\n\nSELECT * FROM activities WHERE uuid_to_bin('6521bfcd-5a30-46e5-9f74-5440fd48befd') = uuid;\n# 608874 conference tr. 11226 crmId: 103422236596\n\nselect * from ai_prompts where transcription_id IN (11241, 11226);\nselect * from activity_summary_logs where activity_id = 608874;\n\nselect * from sidekick_settings;\nselect * from default_activity_types;\n\nselect * from crm_field_data where activity_id = 1223;\n\nselect * from crm_layouts where crm_configuration_id = 2;\nSELECT * FROM crm_layout_entities WHERE crm_layout_id IN (554);\nselect * from crm_fields where crm_configuration_id = 11 and object_type = 'event';\nSELECT * FROM crm_field_values WHERE crm_field_id IN (1455,1450);\n\nSELECT * FROM crm_field_data WHERE crm_layout_entity_id = 971;\nSELECT * FROM crm_field_data WHERE crm_layout_entity_id IN (6494,6495,6496,6497,6498,6499);\n\nSELECT\n CONCAT(u.id, CASE WHEN u.id = t.owner_id THEN ' (owner)' ELSE '' END) AS user_id,\n u.email,\n sa.*,\n t.owner_id FROM social_accounts sa\nJOIN users u\n on u.id = sa.sociable_id\nJOIN teams t on t.id = u.team_id\nWHERE u.team_id = 2 and sa.provider = 'hubspot';\n\nselect * from social_accounts where id = 1499;\n\nselect * from opportunities where team_id = 2\nand crm_provider_id IN ('51317301383');\n\nselect * from contacts where id = 85;\n\nselect * from opportunities where team_id = 2 order by id desc;\nselect * from opportunities where team_id = 2 and crm_provider_id = '51317301383'; # 5112\nselect * from opportunities where team_id = 2 and crm_provider_id = '55976759904'; # 5112\nselect * from opportunity_contacts where opportunity_id = 5117;\nselect * from crm_field_data where object_id = 1365;\nSELECT * FROM crm_fields WHERE id IN (1405, 1407, 1972, 2128);\n\nselect * from features;\nselect * from team_features where team_id IN (1);\nselect * from team_features where feature_id IN (36);\n\nSHOW CREATE TABLE opportunity_contacts;\nSELECT * FROM opportunity_contacts WHERE crm_provider_id = '111751';\n\n# $slug = 'HUBSPOT_WEBHOOK_SYNC';\n# $team = Jiminny\\Models\\Team::find(2);\n# $feature = Feature::query()->where('slug', $slug)->first();\n# TeamFeature::query()->create(['feature_id' => $feature->getId(),'team_id' => $team->getId()]);\n\n# hubspot_webhook_metrics\n\nselect * from opportunities where team_id = 2 and crm_provider_id IN ('374720564','14527423589','49908861993','50435771779'); # 1365\nSELECT * FROM opportunity_contacts WHERE opportunity_id = '414';\nSELECT * FROM opportunity_contacts WHERE crm_provider_id = '131501';\nselect * from contacts where id in (414, 464);\n\nselect * from activities where crm_configuration_id = 2;\n\nselect settings from crm_configurations where id = 11;\n\nselect * from teams; # 1, 2\nselect * from users;\nselect * from crm_configurations where id = 39;\nselect * from team_features where team_id = 2;\nselect * from features;\n# SELECT * FROM opportunities WHERE crm_configuration_id = 2\n# order by id desc;\n# and crm_provider_id = '49908861993';\n\n\nselect * from activity_providers where id IN (443, 202, 203, 227);\n\nselect * from activity_imports where id = 795889;\n\nselect c.id, c.provider, c.settings, t.* from teams t join crm_configurations c on t.id = c.team_id\nwhere c.provider = 'hubspot';\n\nselect * from crm_configurations crm JOIN teams t on crm.team_id = t.id\nwhere provider = 'hubspot';\nSELECT * FROM teams WHERE id = 31;\nSELECT * FROM users WHERE id = 257;\nSELECT * FROM opportunities WHERE team_id = 2;\n\nselect * from opportunity_contacts where opportunity_id = 5124;\nselect * from contacts where id IN (3850,3853,3851,4073,4140,4155,4480,4530,4623,5986,513,687,1806,1523,3613)\n\nselect * from activities where crm_configuration_id = 13;\n\nSELECT * FROM activities WHERE uuid_to_bin('826619ce-ec8e-4e59-8467-a01f5f6ad71e') = uuid; # 418141\n\n\nselect id, team_id, crm_provider_id from crm_configurations where provider = 'hubspot' and crm_provider_id IS NOT NULL;\nSELECT * FROM accounts WHERE team_id = 2 and crm_provider_id = '1212213464' order by id desc;\nSELECT * FROM contacts WHERE team_id = 2 and account_id = 5189 order by id desc;\nSELECT * FROM contacts WHERE team_id = 2 order by id desc;\nselect * from opportunity_contacts where contact_id = 6223;\nSELECT * FROM opportunities WHERE team_id = 2 and account_id = 5189 order by id desc;\n\nselect * from crm_profiles where crm_configuration_id = 2;\n\nselect * from activities where account_id = 46;","role_description":"text entry area","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Sync Changes","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.018055556,"height":0.026666667},"on_screen":false,"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Hide This Notification","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.018055556,"height":0.026666667},"on_screen":false,"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Code changed:","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.088194445,"height":0.027777778},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false}]...
|
1613713078165912296
|
921758650370266701
|
click
|
accessibility
|
NULL
|
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
Execute
Explain Plan
Browse Query History
View Parameters
Open Query Execution Settings…
In-Editor Results
Tx: Auto
Cancel Running Statements
Playground
jiminny
Sync Changes
Hide This Notification
Code changed:
Hide
6
1
6
Previous Highlighted Error
Next Highlighted Error
# [PASSWORD_DOTS] HS [PASSWORD_DOTS]
select * from teams where id = 2; # 2
select * from features; # 2
select * from team_features where team_id = 2; # 2
select * from crm_configurations where id = 2; # 2
select * from users where team_id = 2; #
select * from playbooks where team_id = 2; # event 38
select * from playbook_categories where playbook_id = 38; #
SELECT * FROM activities WHERE crm_configuration_id = 2 and crm_provider_id is not null order by id desc;
https://app.hubspot.com/contacts/4392066/deal/16964514951/?engagement=96069102624
https://app.staging.jiminny.com/playback/d5df34dc-bd66-4ff5-a7b3-8d3be30322a0
SELECT * FROM activities WHERE uuid_to_bin('04fdcd0d-818f-4c53-92dc-6f18bc753ffd') = uuid;
# 609126 softphone tr. 11241
SELECT * FROM activities WHERE uuid_to_bin('6521bfcd-5a30-46e5-9f74-5440fd48befd') = uuid;
# 608874 conference tr. 11226 crmId: 103422236596
select * from ai_prompts where transcription_id IN (11241, 11226);
select * from activity_summary_logs where activity_id = 608874;
select * from sidekick_settings;
select * from default_activity_types;
select * from crm_field_data where activity_id = 1223;
select * from crm_layouts where crm_configuration_id = 2;
SELECT * FROM crm_layout_entities WHERE crm_layout_id IN (554);
select * from crm_fields where crm_configuration_id = 11 and object_type = 'event';
SELECT * FROM crm_field_values WHERE crm_field_id IN (1455,1450);
SELECT * FROM crm_field_data WHERE crm_layout_entity_id = 971;
SELECT * FROM crm_field_data WHERE crm_layout_entity_id IN (6494,6495,6496,6497,6498,6499);
SELECT
CONCAT(u.id, CASE WHEN u.id = t.owner_id THEN ' (owner)' ELSE '' END) AS user_id,
u.email,
sa.*,
t.owner_id FROM social_accounts sa
JOIN users u
on u.id = sa.sociable_id
JOIN teams t on t.id = u.team_id
WHERE u.team_id = 2 and sa.provider = 'hubspot';
select * from social_accounts where id = 1499;
select * from opportunities where team_id = 2
and crm_provider_id IN ('51317301383');
select * from contacts where id = 85;
select * from opportunities where team_id = 2 order by id desc;
select * from opportunities where team_id = 2 and crm_provider_id = '51317301383'; # 5112
select * from opportunities where team_id = 2 and crm_provider_id = '55976759904'; # 5112
select * from opportunity_contacts where opportunity_id = 5117;
select * from crm_field_data where object_id = 1365;
SELECT * FROM crm_fields WHERE id IN (1405, 1407, 1972, 2128);
select * from features;
select * from team_features where team_id IN (1);
select * from team_features where feature_id IN (36);
SHOW CREATE TABLE opportunity_contacts;
SELECT * FROM opportunity_contacts WHERE crm_provider_id = '111751';
# $slug = 'HUBSPOT_WEBHOOK_SYNC';
# $team = Jiminny\Models\Team::find(2);
# $feature = Feature::query()->where('slug', $slug)->first();
# TeamFeature::query()->create(['feature_id' => $feature->getId(),'team_id' => $team->getId()]);
# hubspot_webhook_metrics
select * from opportunities where team_id = 2 and crm_provider_id IN ('374720564','14527423589','49908861993','50435771779'); # 1365
SELECT * FROM opportunity_contacts WHERE opportunity_id = '414';
SELECT * FROM opportunity_contacts WHERE crm_provider_id = '131501';
select * from contacts where id in (414, 464);
select * from activities where crm_configuration_id = 2;
select settings from crm_configurations where id = 11;
select * from teams; # 1, 2
select * from users;
select * from crm_configurations where id = 39;
select * from team_features where team_id = 2;
select * from features;
# SELECT * FROM opportunities WHERE crm_configuration_id = 2
# order by id desc;
# and crm_provider_id = '49908861993';
select * from activity_providers where id IN (443, 202, 203, 227);
select * from activity_imports where id = 795889;
select c.id, c.provider, c.settings, t.* from teams t join crm_configurations c on t.id = c.team_id
where c.provider = 'hubspot';
select * from crm_configurations crm JOIN teams t on crm.team_id = t.id
where provider = 'hubspot';
SELECT * FROM teams WHERE id = 31;
SELECT * FROM users WHERE id = 257;
SELECT * FROM opportunities WHERE team_id = 2;
select * from opportunity_contacts where opportunity_id = 5124;
select * from contacts where id IN (3850,3853,3851,4073,4140,4155,4480,4530,4623,5986,513,687,1806,1523,3613)
select * from activities where crm_configuration_id = 13;
SELECT * FROM activities WHERE uuid_to_bin('826619ce-ec8e-4e59-8467-a01f5f6ad71e') = uuid; # 418141
select id, team_id, crm_provider_id from crm_configurations where provider = 'hubspot' and crm_provider_id IS NOT NULL;
SELECT * FROM accounts WHERE team_id = 2 and crm_provider_id = '1212213464' order by id desc;
SELECT * FROM contacts WHERE team_id = 2 and account_id = 5189 order by id desc;
SELECT * FROM contacts WHERE team_id = 2 order by id desc;
select * from opportunity_contacts where contact_id = 6223;
SELECT * FROM opportunities WHERE team_id = 2 and account_id = 5189 order by id desc;
select * from crm_profiles where crm_configuration_id = 2;
select * from activities where account_id = 46;
Sync Changes
Hide This Notification
Code changed:...
|
2687
|
NULL
|
NULL
|
NULL
|
|
2690
|
111
|
15
|
2026-05-07T11:35:46.428046+00:00
|
/Users/lukas/.screenpipe/data/data/2026-05-07/1778 /Users/lukas/.screenpipe/data/data/2026-05-07/1778153746428_m1.jpg...
|
PhpStorm
|
faVsco.js – Hubspot/Client.php
|
True
|
NULL
|
monitor_1
|
NULL
|
NULL
|
NULL
|
NULL
|
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
Execute
Explain Plan
Browse Query History
View Parameters
Open Query Execution Settings…
In-Editor Results
Tx: Auto
Cancel Running Statements
Playground
jiminny
Sync Changes
Hide This Notification
Code changed:
Hide
6
1
6
Previous Highlighted Error
Next Highlighted Error
# [PASSWORD_DOTS] HS [PASSWORD_DOTS]
select * from teams where id = 2; # 2
select * from features; # 2
select * from team_features where team_id = 2; # 2
select * from crm_configurations where id = 2; # 2
select * from users where team_id = 2; #
select * from playbooks where team_id = 2; # event 38
select * from playbook_categories where playbook_id = 38; #
SELECT * FROM activities WHERE crm_configuration_id = 2 and crm_provider_id is not null order by id desc;
[URL_WITH_CREDENTIALS] CrmFieldOption array{id:string, label:string, value?:string}
*/
class Client extends BaseClient implements HubspotClientInterface
{
public const string MIN_API_VERSION = '2';
public const string BASE_URL = '[URL_WITH_CREDENTIALS] T
* @param callable(): T $apiCall
* @return T
*
* @throws RateLimitException
*/
private function executeRequest(callable $apiCall)
{
if (! $this->rateLimiter->canMakeRequest($this->config)) {
$retryAfter = $this->rateLimiter->requestAvailableIn($this->config);
$this->log->warning('[Hubspot] Rate limit exceeded, deferring request', [
'team_id' => $this->config->team_id,
'config_id' => $this->config->getId(),
'retry_after' => $retryAfter,
]);
throw new RateLimitException(
'Hubspot rate limit reached for configuration ' . $this->config->getId(),
$retryAfter,
);
}
$this->rateLimiter->incrementRequestCount($this->config);
try {
return $apiCall();
} catch (Throwable $e) {
if ($this->isHubspotRateLimit($e)) {
$retryAfter = $this->parseRetryAfter($e);
$this->log->warning('[Hubspot] Received 429 from API', [
'team_id' => $this->config->team_id,
'config_id' => $this->config->getId(),
'retry_after' => $retryAfter,
'reason' => $e->getMessage(),
]);
throw new RateLimitException('Hubspot returned 429', $retryAfter, $e);
}
throw $e;
}
}
private function isHubspotRateLimit(Throwable $e): bool
{
return method_exists($e, 'getCode') && (int) $e->getCode() === 429;
}
private function parseRetryAfter(Throwable $e): int
{
if (method_exists($e, 'getResponseHeaders')) {
$headers = $e->getResponseHeaders() ?: [];
$value = $headers['Retry-After'] ?? $headers['retry-after'] ?? null;
if (is_array($value)) {
$value = $value[0] ?? null;
}
if (is_numeric($value)) {
return (int) $value;
}
}
return 10;
}
public function getMinimumApiVersion(): string
{
return self::MIN_API_VERSION;
}
public function getInstance(): Factory
{
return new Factory([
'key' => $this->accessToken,
'oauth2' => true,
'base_url' => $this->baseUrl,
]);
}
public function getNewInstance(): Discovery
{
return \HubSpot\Factory::createWithAccessToken($this->accessToken);
}
/**
* Secondly and daily limits for Hubspot API
*
* Product Tier: Free & Starter | Professional & Enterprise | API add-on (any tier)
* Burst: 100/10 seconds | 150/10 seconds | 200/10 seconds
* Daily: 250,000 | 500,000 | 1,000,000
*
* Official documentation states: The search endpoints are rate limited to five requests per second.
* Since with 5 RPS were still hitting secondly rate limits we lowered it to 4
*/
public function getPaginatedData(array $payload, string $type, int $offset = 0): array
{
$total = 0;
$lastId = null;
$rows = [];
foreach ($this->getPaginatedDataGenerator($payload, $type, $offset, $total, $lastId) as $row) {
$rows[] = $row;
}
return ['results' => $rows, 'total' => $total, 'last_record' => $lastId];
}
/**
* @throws HubspotException
* @throws SocialAccountTokenInvalidException
* @throws BadRequest
*/
public function getPaginatedDataGenerator(
array $payload,
string $type,
int $offset = 0,
int &$total = 0,
?string &$lastRecordId = null
): \Generator {
return $this->paginationService->getPaginatedDataGenerator(
$this,
$payload,
$type,
$offset,
$total,
$lastRecordId
);
}
/**
* @throws DealApiException
* @throws CrmException
*/
public function getOpportunityById(string $crmId, array $fields): array
{
try {
$deal = $this->executeRequest(fn () => $this->getNewInstance()->crm()->deals()->basicApi()->getById(
$crmId,
implode(',', $fields),
'companies,contacts'
));
} catch (DealApiException $e) {
$this->log->info('[Hubspot] Failed to fetch opportunity', [
'crm_id' => $crmId,
'reason' => $e->getMessage(),
]);
throw $e;
}
if (! $deal instanceof DealWithAssociations) {
throw new CrmException('Deal not found');
}
return [
'id' => $deal->getId(),
'properties' => $deal->getProperties(),
'associations' => $deal->getAssociations(),
];
}
/**
* Generic batch read method for HubSpot objects
*
* @param string $objectType The object type ('deals', 'companies', 'contacts')
* @param array<string> $crmIds Array of HubSpot object IDs (max 100)
* @param array<string> $fields Array of property names to fetch
*
* @return array<string, array> Array keyed by CRM ID with object data
*/
private function batchReadObjects(string $objectType, array $crmIds, array $fields): array
{
if (empty($crmIds)) {
return [];
}
$this->validateBatchSize($objectType, $crmIds);
$this->ensureValidToken();
try {
$batchConfig = $this->createBatchConfiguration($objectType);
$batchReadRequest = $this->prepareBatchRequest($batchConfig, $crmIds, $fields);
$response = $batchConfig['api']->read($batchReadRequest);
$this->validateApiResponse($response, $objectType);
$results = $this->processApiResults($response);
$this->logBatchResults($objectType, $crmIds, $results);
return $results;
} catch (\Throwable $e) {
$this->handleBatchError($e, $objectType, $crmIds);
}
}
private function validateBatchSize(string $objectType, array $crmIds): void
{
if (count($crmIds) > 100) {
throw new \InvalidArgumentException("Batch size cannot exceed 100 {$objectType}");
}
}
private function createBatchConfiguration(string $objectType): array
{
$configurations = [
'deals' => [
'batchReadRequest' => new \HubSpot\Client\Crm\Deals\Model\BatchReadInputSimplePublicObjectId(),
'inputClass' => \HubSpot\Client\Crm\Deals\Model\SimplePublicObjectId::class,
'api' => $this->getNewInstance()->crm()->deals()->batchApi(),
],
'companies' => [
'batchReadRequest' => new \HubSpot\Client\Crm\Companies\Model\BatchReadInputSimplePublicObjectId(),
'inputClass' => \HubSpot\Client\Crm\Companies\Model\SimplePublicObjectId::class,
'api' => $this->getNewInstance()->crm()->companies()->batchApi(),
],
'contacts' => [
'batchReadRequest' => new \HubSpot\Client\Crm\Contacts\Model\BatchReadInputSimplePublicObjectId(),
'inputClass' => \HubSpot\Client\Crm\Contacts\Model\SimplePublicObjectId::class,
'api' => $this->getNewInstance()->crm()->contacts()->batchApi(),
],
];
if (! isset($configurations[$objectType])) {
throw new \InvalidArgumentException("Unsupported object type: {$objectType}");
}
return $configurations[$objectType];
}
private function prepareBatchRequest(array $batchConfig, array $crmIds, array $fields): object
{
$batchReadRequest = $batchConfig['batchReadRequest'];
$inputClass = $batchConfig['inputClass'];
$inputs = array_map(function ($crmId) use ($inputClass) {
$input = new $inputClass();
$input->setId($crmId);
return $input;
}, $crmIds);
$batchReadRequest->setInputs($inputs);
$batchReadRequest->setProperties($fields);
return $batchReadRequest;
}
private function validateApiResponse($response, string $objectType): void
{
if (! $response) {
throw new CrmException("HubSpot API returned null response for {$objectType} batch read");
}
}
private function processApiResults($response): array
{
$results = [];
$responseResults = $response->getResults();
if ($responseResults) {
foreach ($responseResults as $object) {
if ($object && $object->getId()) {
$results[$object->getId()] = [
'id' => $object->getId(),
'properties' => $object->getProperties() ?: [],
];
}
}
}
return $results;
}
private function logBatchResults(string $objectType, array $crmIds, array $results): void
{
$this->log->info("[HubSpot] Batch fetched {$objectType}", [
'requested_count' => count($crmIds),
'returned_count' => count($results),
'crm_ids' => $crmIds,
]);
}
private function handleBatchError(\Throwable $e, string $objectType, array $crmIds): void
{
$errorMessage = $e->getMessage() ?: 'Unknown error';
$errorTrace = $e->getTraceAsString() ?: 'No trace available';
$this->log->error("[HubSpot] Failed to batch fetch {$objectType}", [
'crm_ids' => $crmIds,
'error' => $errorMessage,
'trace' => $errorTrace,
]);
throw new CrmException("Failed to batch fetch {$objectType}: " . $errorMessage);
}
/**
* Batch read multiple opportunities by their CRM IDs
*
* @param array<string> $crmIds Array of HubSpot deal IDs (max 100)
* @param array<string> $fields Array of property names to fetch
*
* @return array<string, array> Array keyed by CRM ID with opportunity data
*/
public function getOpportunitiesByIds(array $crmIds, array $fields): array
{
return $this->batchReadObjects('deals', $crmIds, $fields);
}
/**
* Batch read multiple companies by their CRM IDs
*
* @param array<string> $crmIds Array of HubSpot company IDs (max 100)
* @param array<string> $fields Array of property names to fetch
*
* @return array<string, array> Array keyed by CRM ID with company data
*/
public function getCompaniesByIds(array $crmIds, array $fields): array
{
return $this->batchReadObjects('companies', $crmIds, $fields);
}
/**
* Batch read multiple contacts by their CRM IDs
*
* @param array<string> $crmIds Array of HubSpot contact IDs (max 100)
* @param array<string> $fields Array of property names to fetch
*
* @return array<string, array> Array keyed by CRM ID with contact data
*/
public function getContactsByIds(array $crmIds, array $fields): array
{
return $this->batchReadObjects('contacts', $crmIds, $fields);
}
/**
* @throws CompanyApiException
* @throws CrmException
*/
public function getAccountById(string $crmId, array $fields): array
{
try {
$company = $this->getNewInstance()->crm()->companies()->basicApi()->getById(
$crmId,
implode(',', $fields),
);
} catch (CompanyApiException $e) {
$this->log->info('[Hubspot] Failed to fetch account', [
'crm_id' => $crmId,
'reason' => $e->getMessage(),
]);
throw $e;
}
if (! $company instanceof CompaniesWithAssociations) {
throw new CrmException('Account not found');
}
return [
'id' => $company->getId(),
'properties' => $company->getProperties(),
];
}
/**
* @throws ContactApiException
* @throws CrmException
*/
public function getContactById(string $crmId, array $fields): array
{
try {
$contact = $this->getNewInstance()->crm()->contacts()->basicApi()->getById(
$crmId,
implode(',', $fields)
);
} catch (ContactApiException $e) {
$this->log->info('[Hubspot] Failed to fetch contact', [
'crm_id' => $crmId,
'reason' => $e->getMessage(),
]);
throw $e;
}
if (! $contact instanceof ContactsWithAssociations) {
throw new CrmException('Contact not found');
}
return [
'id' => $contact->getId(),
'properties' => $contact->getProperties(),
];
}
/**
* This is email search request that Hubspot offers as GET (more generous quota)
*/
public function getContactByEmail(string $email, array $fields = []): array
{
try {
$contact = $this->getNewInstance()->crm()->contacts()->basicApi()->getById(
$email,
implode(',', $fields),
null,
false,
'email'
);
return [
'id' => $contact->getId(),
'properties' => $contact->getProperties(),
];
} catch (ContactApiException $e) {
$this->log->info('[Hubspot] Failed to fetch contact', [
'email' => $email,
'reason' => $e->getMessage(),
]);
return [];
}
}
/**
* @throws CrmException
*/
public function fetchProperty(string $objectType, string $propertyId): Property
{
$result = $this->getNewInstance()->crm()->properties()->coreApi()->getByName($objectType, $propertyId);
if (! $result instanceof Property) {
$this->log->error('[Hubspot] Failed to fetch property', [
'object_type' => $objectType,
'property_id' => $propertyId,
'reason' => $result->getMessage(),
]);
throw new CrmException('Failed to fetch property');
}
return $result;
}
/**
* @return array<CrmFieldOption>
*/
public function fetchPropertyOptions(string $objectType, string $propertyId): array
{
/** @var array<CrmFieldOption> */
return $this->fetchProperty($objectType, $propertyId)->getOptions();
}
/**
* @return array<array{id:string, label:string, deleted:bool}>
*/
public function fetchCallDispositions(): array
{
/** @var Response $response */
$response = $this->getInstance()->engagements()->getCallDispositions();
/**
* @var array<array{
* id:string,
* label:string,
* deleted: bool
* }>
*/
return $response->toArray();
}
/**
* @return array<CrmFieldOption>
*/
public function fetchOpportunityPipelineStages(): array
{
$stages = [];
$apiResponse = $this->getNewInstance()->crm()->pipelines()->pipelinesApi()->getAll('deals');
if ($apiResponse instanceof Error) {
$this->log->error('[Hubspot] Failed to fetch opportunity pipelines', [
'reason' => $apiResponse->getMessage(),
]);
return [];
}
foreach ($apiResponse->getResults() as $pipeline) {
$pipelineStages = array_map(
static function (PipelineStage $stage) {
return [
'id' => $stage->getId(),
'label' => $stage->getLabel(),
];
},
$pipeline->getStages()
);
$stages = array_merge($stages, $pipelineStages);
}
return $stages;
}
public function fetchOpportunityPipelines(): array
{
$pipelines = [];
try {
$apiResponse = $this->makeRequest('/crm/v3/pipelines/deals');
} catch (\Exception $e) {
$this->log->info('[Hubspot] Failed to fetch opportunity pipelines', [
'reason' => $e->getMessage(),
]);
return [];
}
$response = $apiResponse->toArray();
foreach ($response['results'] as $pipeline) {
$pipelines[] = [
'id' => $pipeline['id'],
'label' => $pipeline['label'],
];
}
return $pipelines;
}
/**
* @return array<CrmFieldOption>
*/
public function fetchMeetingOutcomeFieldOptions(Field $field): array
{
return $field->getCrmProviderId() === 'meetingOutcome'
? $this->fetchMeetingOutcomeTypes()
: $this->fetchCallActivityTypes();
}
public function fetchMeetingOutcomeTypes(): array
{
return $this->extractMeetingTypeOptions(
'[URL_WITH_CREDENTIALS] Response $response */
$response = $this->getInstance()
->getClient()
->request('GET', $endpoint);
/**
* @var array<array{
* value: string,
* label: string,
* displayOrder: int
* }> $optionData
*/
$optionData = $response->toArray()['options'] ?? [];
$options = [];
foreach ($optionData as $item) {
$options[] = [
'id' => $item['value'],
'value' => $item['value'],
'label' => $item['label'],
'display_order' => $item['displayOrder'],
];
}
return $options;
}
/**
* @return array<CrmFieldOption>
*/
public function fetchDispositionFieldOptions(): array
{
$options = [];
$dispositions = $this->fetchCallDispositions();
foreach ($dispositions as $disposition) {
if ($disposition['deleted'] !== false) {
continue;
}
$option['value'] = $disposition['id'];
$option['id'] = $disposition['id'];
$option['label'] = $disposition['label'];
$options[] = $option;
}
return $options;
}
/**
* @return array<CrmFieldOption>
*/
public function fetchOpportunityFieldOptions(Field $field): array
{
if ($field->isStageField()) {
return $this->fetchOpportunityPipelineStages();
}
if ($field->isPipelineField()) {
return $this->fetchOpportunityPipelines();
}
return $this->fetchPropertyOptions('deals', $field->getCrmProviderId());
}
/**
* @throws BadRequest
* @throws HubspotException
*/
public function makeRequest(string $endpoint, $method = 'GET', $payload = [], ?string $queryString = null)
{
$endpoint = self::BASE_URL . $endpoint;
if ($method === 'GET') {
$response = $this->getInstance()->getClient()?->request(
method: $method,
endpoint: $endpoint,
query_string: $queryString
);
} else {
$response = $this->getInstance()->getClient()->request($method, $endpoint, [
'json' => ($payload),
]);
}
$max = $response->getHeaderLine('X-HubSpot-RateLimit-Max'); // "110"
$remaining = $response->getHeaderLine('X-HubSpot-RateLimit-Remaining'); // "109"
$interval = $response->getHeaderLine('X-HubSpot-RateLimit-Interval-Milliseconds'); // "10000"
$body = json_decode((string) $response->getBody(), true);
\Illuminate\Support\Facades\Log::channel('custom_channel')->info('$max ' . PHP_EOL . print_r($max, true));
\Illuminate\Support\Facades\Log::channel('custom_channel')->info('$remaining ' . PHP_EOL . print_r($remaining, true));
\Illuminate\Support\Facades\Log::channel('custom_channel')->info('$interval ' . PHP_EOL . print_r($interval, true));
\Illuminate\Support\Facades\Log::channel('custom_channel')->info('$body ' . PHP_EOL . print_r($body, true));
return $response;
}
/**
* @throws BadRequest
* @throws HubspotException
*/
public function createMeeting(array $payload): Response
{
$endpoint = '/crm/v3/objects/meetings';
return $this->makeRequest($endpoint, 'POST', $payload);
}
/**
* @throws BadRequest
* @throws HubspotException
*/
public function updateMeeting(string $meetingId, array $payload): Response
{
$endpoint = '/crm/v3/objects/meetings/' . $meetingId;
return $this->makeRequest($endpoint, 'PATCH', $payload);
}
/**
* @throws \Exception
*/
public function createNote(
string $body,
string $ownerId,
int $timestamp,
string $objectId,
NoteObject $noteObject
): ?string {
try {
$noteInput = new SimplePublicObjectInput([
'properties' => [
'hs_note_body' => $body,
'hubspot_owner_id' => $ownerId,
'hs_timestamp' => $timestamp,
],
]);
// Create note
$note = $this->getNewInstance()->crm()->objects()->basicApi()->create('note', $noteInput);
$this->getNewInstance()->crm()->objects()->associationsApi()->create(
'note',
$note->getId(),
$this->getNoteObject($noteObject),
$objectId,
$this->getNoteAssociationType($noteObject),
);
return $note->getId();
} catch (\Exception $e) {
$this->log->error('[Hubspot] Failed to create note', [
'objectId' => $objectId,
'noteObject' => $noteObject->getObjectType(),
'reason' => $e->getMessage(),
]);
\Sentry::captureException($e);
}
return null;
}
public function updateEngagement(string $objectId, array $engagement, array $metadata): void
{
$this->getInstance()->engagements()->update($objectId, $engagement, $metadata);
}
public function getEngagementData(string $engagementId): array
{
$engagement = $this->getInstance()->engagements()->get($engagementId);
return $engagement->toArray();
}
public function createEngagement(array $engagement, array $associations, array $metadata): Response
{
return $this->getInstance()
->engagements()
->create($engagement, $associations, $metadata);
}
public function isUnauthorizedException(\Exception $e): bool
{
// Check for specific HubSpot API exception types first
if ($e instanceof BadRequest) {
// BadRequest can contain 401 status codes
return $e->getCode() === 401;
}
// Check for HTTP client exceptions with status codes
if ($e instanceof \GuzzleHttp\Exception\RequestException && $e->hasResponse()) {
$response = $e->getResponse();
if ($response !== null) {
return $response->getStatusCode() === 401;
}
}
// Check for Guzzle HTTP exceptions
if ($e instanceof \GuzzleHttp\Exception\ClientException) {
return $e->getCode() === 401;
}
// Fallback to string matching as last resort, but be more specific
$message = strtolower($e->getMessage());
return str_contains($message, '401 unauthorized') ||
str_contains($message, 'http 401') ||
str_contains($message, 'status code 401') ||
(preg_match('/\b401\b/', $message) && str_contains($message, 'unauthorized'));
}
/**
* Validates and refreshes the access token if needed before API requests.
* This ensures long-running processes don't fail due to token expiration.
*
* @throws SocialAccountTokenInvalidException
*/
public function ensureValidToken(): void
{
if ($this->oauthAccount === null) {
return;
}
$newToken = $this->tokenManager->ensureValidToken($this->oauthAccount);
if ($newToken !== null) {
$this->accessToken = $newToken;
}
}
public function getConfig()
{
return $this->config;
}
// returns only active (archived=false)
public function getOwners(): array
{
return $this->getNewInstance()->crm()->owners()->getAll();
}
/**
* @param bool $archived
*
* @return array<Owner>|[]
*/
public function getOwnersArchived(bool $archived = true): array
{
$endpoint = '/crm/v3/owners';
$queryParams = [
'archived' => $archived ? 'true' : 'false',
];
$queryString = http_build_query($queryParams);
$owners = [];
try {
$response = $this->makeRequest(endpoint: $endpoint, queryString: $queryString);
$responseData = $response?->toArray();
foreach ($responseData['results'] as $result) {
try {
$owners[] = Owner::create($result);
} catch (Throwable $e) {
$this->log->error('[HubSpot] Failed to process owner data', [
'result' => $result,
'error' => $e->getMessage(),
]);
continue;
}
}
} catch (Throwable $e) {
$this->log->error('HubSpot] Failed to fetch owners', [
'archived' => $archived,
'error' => $e->getMessage(),
]);
return [];
}
return $owners;
}
public function getMeeting(string $engagementId): ObjectWithAssociations
{
return $this->getNewInstance()->crm()->objects()->basicApi()
->getById('meeting', $engagementId, null, 'contact,company,deal');
}
public function deleteEngagement(string $engagementId): void
{
$this->getInstance()->engagements()->delete((int) $engagementId);
}
public function getAssociationsData(array $ids, string $fromObject, string $toObject): array
{
$associationData = [];
$idChunks = array_chunk($ids, self::ASSOCIATIONS_BATCH_SIZE_LIMIT);
foreach ($idChunks as $idChunk) {
try {
$batchInput = new \HubSpot\Client\Crm\Associations\Model\BatchInputPublicObjectId();
$batchInput->setInputs(array_map(function ($id) {
$publicObjectId = new \HubSpot\Client\Crm\Associations\Model\PublicObjectId();
$publicObjectId->setId($id);
return $publicObjectId;
}, $idChunk));
$associatedObjectsData = $this
->getNewInstance()
->crm()
->associations()
->batchApi()
->read($fromObject, $toObject, $batchInput);
if ($associatedObjectsData instanceof \HubSpot\Client\Crm\Associations\Model\BatchResponsePublicAssociationMulti) {
foreach ($associatedObjectsData->getResults() as $association) {
$from = $association->getFrom()->getId();
$toAssociations = $association->getTo();
if (! empty($toAssociations)) {
$associationData[$from] = array_map(function ($item) {
return $item->getId();
}, $toAssociations);
}
}
}
} catch (\Exception $e) {
$this->log->error('[Hubspot] Failed to fetch associations', [
'from_object' => $fromObject,
'to_object' => $toObject,
'reason' => $e->getMessage(),
]);
}
}
return $associationData;
}
/**
* @throws \Exception
*/
private function getNoteAssociationType(NoteObject $noteObject): string
{
return match($noteObject) {
NoteObject::Opportunity => 'note_to_deal',
NoteObject::Lead, NoteObject::Contact => 'note_to_contact', // or 'note_to_lead' if your portal supports it
NoteObject::Account => 'note_to_company',
NoteObject::Call, NoteObject::Event => throw new \Exception('Not supported'),
};
}
/**
* @throws \Exception
*/
private function getNoteObject(NoteObject $noteObject): string
{
return match($noteObject) {
NoteObject::Opportunity => 'deal',
NoteObject::Lead, NoteObject::Contact => 'contact',
NoteObject::Account => 'company',
NoteObject::Call, NoteObject::Event => throw new \Exception('Not supported'),
};
}
public function addAssociations(string $objectType, string $associationType, array $payload): Response
{
$endpoint = "/crm/v4/associations/$objectType/$associationType/batch/create";
return $this->makeRequest($endpoint, 'POST', $payload);
}
public function removeAssociations(string $objectType, string $associationType, array $payload): Response
{
$endpoint = "/crm/v4/associations/$objectType/$associationType/batch/archive";
return $this->makeRequest($endpoint, 'POST', $payload);
}
}
Project
Project
New File or Directory…
Expand Selected
Collapse All
Options
Hide...
|
[{"role":"AXButton","text" [{"role":"AXButton","text":"Project: faVsco.js, menu","depth":5,"on_screen":true,"help_text":"~/jiminny/app","role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"master, menu","depth":5,"on_screen":true,"help_text":"Git Branch: master","role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Start Listening for PHP Debug Connections","depth":5,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"AskJiminnyReportActivityServiceTest","depth":6,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Run 'AskJiminnyReportActivityServiceTest'","depth":6,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Debug 'AskJiminnyReportActivityServiceTest'","depth":6,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"More Actions","depth":6,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"JetBrains AI","depth":5,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Search Everywhere","depth":5,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"IDE and Project Settings","depth":5,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Execute","depth":4,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Explain Plan","depth":4,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Browse Query History","depth":4,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"View Parameters","depth":4,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Open Query Execution Settings…","depth":4,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"In-Editor Results","depth":4,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Tx: Auto","depth":4,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Cancel Running Statements","depth":4,"on_screen":true,"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Playground","depth":4,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"jiminny","depth":4,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Sync Changes","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.018055556,"height":0.026666667},"on_screen":false,"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Hide This Notification","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.018055556,"height":0.026666667},"on_screen":false,"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Code changed:","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.088194445,"height":0.027777778},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Hide","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.018055556,"height":0.026666667},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"6","depth":4,"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"1","depth":4,"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"6","depth":4,"on_screen":true,"role_description":"text"},{"role":"AXButton","text":"Previous Highlighted Error","depth":4,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Next Highlighted Error","depth":4,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXTextArea","text":"# **************************** HS **************************************\n\nselect * from teams where id = 2; # 2\nselect * from features; # 2\nselect * from team_features where team_id = 2; # 2\nselect * from crm_configurations where id = 2; # 2\nselect * from users where team_id = 2; #\nselect * from playbooks where team_id = 2; # event 38\nselect * from playbook_categories where playbook_id = 38; #\n\nSELECT * FROM activities WHERE crm_configuration_id = 2 and crm_provider_id is not null order by id desc;\nhttps://app.hubspot.com/contacts/4392066/deal/16964514951/?engagement=96069102624\n https://app.staging.jiminny.com/playback/d5df34dc-bd66-4ff5-a7b3-8d3be30322a0\n\nSELECT * FROM activities WHERE uuid_to_bin('04fdcd0d-818f-4c53-92dc-6f18bc753ffd') = uuid;\n# 609126 softphone tr. 11241\n\nSELECT * FROM activities WHERE uuid_to_bin('6521bfcd-5a30-46e5-9f74-5440fd48befd') = uuid;\n# 608874 conference tr. 11226 crmId: 103422236596\n\nselect * from ai_prompts where transcription_id IN (11241, 11226);\nselect * from activity_summary_logs where activity_id = 608874;\n\nselect * from sidekick_settings;\nselect * from default_activity_types;\n\nselect * from crm_field_data where activity_id = 1223;\n\nselect * from crm_layouts where crm_configuration_id = 2;\nSELECT * FROM crm_layout_entities WHERE crm_layout_id IN (554);\nselect * from crm_fields where crm_configuration_id = 11 and object_type = 'event';\nSELECT * FROM crm_field_values WHERE crm_field_id IN (1455,1450);\n\nSELECT * FROM crm_field_data WHERE crm_layout_entity_id = 971;\nSELECT * FROM crm_field_data WHERE crm_layout_entity_id IN (6494,6495,6496,6497,6498,6499);\n\nSELECT\n CONCAT(u.id, CASE WHEN u.id = t.owner_id THEN ' (owner)' ELSE '' END) AS user_id,\n u.email,\n sa.*,\n t.owner_id FROM social_accounts sa\nJOIN users u\n on u.id = sa.sociable_id\nJOIN teams t on t.id = u.team_id\nWHERE u.team_id = 2 and sa.provider = 'hubspot';\n\nselect * from social_accounts where id = 1499;\n\nselect * from opportunities where team_id = 2\nand crm_provider_id IN ('51317301383');\n\nselect * from contacts where id = 85;\n\nselect * from opportunities where team_id = 2 order by id desc;\nselect * from opportunities where team_id = 2 and crm_provider_id = '51317301383'; # 5112\nselect * from opportunities where team_id = 2 and crm_provider_id = '55976759904'; # 5112\nselect * from opportunity_contacts where opportunity_id = 5117;\nselect * from crm_field_data where object_id = 1365;\nSELECT * FROM crm_fields WHERE id IN (1405, 1407, 1972, 2128);\n\nselect * from features;\nselect * from team_features where team_id IN (1);\nselect * from team_features where feature_id IN (36);\n\nSHOW CREATE TABLE opportunity_contacts;\nSELECT * FROM opportunity_contacts WHERE crm_provider_id = '111751';\n\n# $slug = 'HUBSPOT_WEBHOOK_SYNC';\n# $team = Jiminny\\Models\\Team::find(2);\n# $feature = Feature::query()->where('slug', $slug)->first();\n# TeamFeature::query()->create(['feature_id' => $feature->getId(),'team_id' => $team->getId()]);\n\n# hubspot_webhook_metrics\n\nselect * from opportunities where team_id = 2 and crm_provider_id IN ('374720564','14527423589','49908861993','50435771779'); # 1365\nSELECT * FROM opportunity_contacts WHERE opportunity_id = '414';\nSELECT * FROM opportunity_contacts WHERE crm_provider_id = '131501';\nselect * from contacts where id in (414, 464);\n\nselect * from activities where crm_configuration_id = 2;\n\nselect settings from crm_configurations where id = 11;\n\nselect * from teams; # 1, 2\nselect * from users;\nselect * from crm_configurations where id = 39;\nselect * from team_features where team_id = 2;\nselect * from features;\n# SELECT * FROM opportunities WHERE crm_configuration_id = 2\n# order by id desc;\n# and crm_provider_id = '49908861993';\n\n\nselect * from activity_providers where id IN (443, 202, 203, 227);\n\nselect * from activity_imports where id = 795889;\n\nselect c.id, c.provider, c.settings, t.* from teams t join crm_configurations c on t.id = c.team_id\nwhere c.provider = 'hubspot';\n\nselect * from crm_configurations crm JOIN teams t on crm.team_id = t.id\nwhere provider = 'hubspot';\nSELECT * FROM teams WHERE id = 31;\nSELECT * FROM users WHERE id = 257;\nSELECT * FROM opportunities WHERE team_id = 2;\n\nselect * from opportunity_contacts where opportunity_id = 5124;\nselect * from contacts where id IN (3850,3853,3851,4073,4140,4155,4480,4530,4623,5986,513,687,1806,1523,3613)\n\nselect * from activities where crm_configuration_id = 13;\n\nSELECT * FROM activities WHERE uuid_to_bin('826619ce-ec8e-4e59-8467-a01f5f6ad71e') = uuid; # 418141\n\n\nselect id, team_id, crm_provider_id from crm_configurations where provider = 'hubspot' and crm_provider_id IS NOT NULL;\nSELECT * FROM accounts WHERE team_id = 2 and crm_provider_id = '1212213464' order by id desc;\nSELECT * FROM contacts WHERE team_id = 2 and account_id = 5189 order by id desc;\nSELECT * FROM contacts WHERE team_id = 2 order by id desc;\nselect * from opportunity_contacts where contact_id = 6223;\nSELECT * FROM opportunities WHERE team_id = 2 and account_id = 5189 order by id desc;\n\nselect * from crm_profiles where crm_configuration_id = 2;\n\nselect * from activities where account_id = 46;","depth":4,"on_screen":true,"value":"# **************************** HS **************************************\n\nselect * from teams where id = 2; # 2\nselect * from features; # 2\nselect * from team_features where team_id = 2; # 2\nselect * from crm_configurations where id = 2; # 2\nselect * from users where team_id = 2; #\nselect * from playbooks where team_id = 2; # event 38\nselect * from playbook_categories where playbook_id = 38; #\n\nSELECT * FROM activities WHERE crm_configuration_id = 2 and crm_provider_id is not null order by id desc;\nhttps://app.hubspot.com/contacts/4392066/deal/16964514951/?engagement=96069102624\n https://app.staging.jiminny.com/playback/d5df34dc-bd66-4ff5-a7b3-8d3be30322a0\n\nSELECT * FROM activities WHERE uuid_to_bin('04fdcd0d-818f-4c53-92dc-6f18bc753ffd') = uuid;\n# 609126 softphone tr. 11241\n\nSELECT * FROM activities WHERE uuid_to_bin('6521bfcd-5a30-46e5-9f74-5440fd48befd') = uuid;\n# 608874 conference tr. 11226 crmId: 103422236596\n\nselect * from ai_prompts where transcription_id IN (11241, 11226);\nselect * from activity_summary_logs where activity_id = 608874;\n\nselect * from sidekick_settings;\nselect * from default_activity_types;\n\nselect * from crm_field_data where activity_id = 1223;\n\nselect * from crm_layouts where crm_configuration_id = 2;\nSELECT * FROM crm_layout_entities WHERE crm_layout_id IN (554);\nselect * from crm_fields where crm_configuration_id = 11 and object_type = 'event';\nSELECT * FROM crm_field_values WHERE crm_field_id IN (1455,1450);\n\nSELECT * FROM crm_field_data WHERE crm_layout_entity_id = 971;\nSELECT * FROM crm_field_data WHERE crm_layout_entity_id IN (6494,6495,6496,6497,6498,6499);\n\nSELECT\n CONCAT(u.id, CASE WHEN u.id = t.owner_id THEN ' (owner)' ELSE '' END) AS user_id,\n u.email,\n sa.*,\n t.owner_id FROM social_accounts sa\nJOIN users u\n on u.id = sa.sociable_id\nJOIN teams t on t.id = u.team_id\nWHERE u.team_id = 2 and sa.provider = 'hubspot';\n\nselect * from social_accounts where id = 1499;\n\nselect * from opportunities where team_id = 2\nand crm_provider_id IN ('51317301383');\n\nselect * from contacts where id = 85;\n\nselect * from opportunities where team_id = 2 order by id desc;\nselect * from opportunities where team_id = 2 and crm_provider_id = '51317301383'; # 5112\nselect * from opportunities where team_id = 2 and crm_provider_id = '55976759904'; # 5112\nselect * from opportunity_contacts where opportunity_id = 5117;\nselect * from crm_field_data where object_id = 1365;\nSELECT * FROM crm_fields WHERE id IN (1405, 1407, 1972, 2128);\n\nselect * from features;\nselect * from team_features where team_id IN (1);\nselect * from team_features where feature_id IN (36);\n\nSHOW CREATE TABLE opportunity_contacts;\nSELECT * FROM opportunity_contacts WHERE crm_provider_id = '111751';\n\n# $slug = 'HUBSPOT_WEBHOOK_SYNC';\n# $team = Jiminny\\Models\\Team::find(2);\n# $feature = Feature::query()->where('slug', $slug)->first();\n# TeamFeature::query()->create(['feature_id' => $feature->getId(),'team_id' => $team->getId()]);\n\n# hubspot_webhook_metrics\n\nselect * from opportunities where team_id = 2 and crm_provider_id IN ('374720564','14527423589','49908861993','50435771779'); # 1365\nSELECT * FROM opportunity_contacts WHERE opportunity_id = '414';\nSELECT * FROM opportunity_contacts WHERE crm_provider_id = '131501';\nselect * from contacts where id in (414, 464);\n\nselect * from activities where crm_configuration_id = 2;\n\nselect settings from crm_configurations where id = 11;\n\nselect * from teams; # 1, 2\nselect * from users;\nselect * from crm_configurations where id = 39;\nselect * from team_features where team_id = 2;\nselect * from features;\n# SELECT * FROM opportunities WHERE crm_configuration_id = 2\n# order by id desc;\n# and crm_provider_id = '49908861993';\n\n\nselect * from activity_providers where id IN (443, 202, 203, 227);\n\nselect * from activity_imports where id = 795889;\n\nselect c.id, c.provider, c.settings, t.* from teams t join crm_configurations c on t.id = c.team_id\nwhere c.provider = 'hubspot';\n\nselect * from crm_configurations crm JOIN teams t on crm.team_id = t.id\nwhere provider = 'hubspot';\nSELECT * FROM teams WHERE id = 31;\nSELECT * FROM users WHERE id = 257;\nSELECT * FROM opportunities WHERE team_id = 2;\n\nselect * from opportunity_contacts where opportunity_id = 5124;\nselect * from contacts where id IN (3850,3853,3851,4073,4140,4155,4480,4530,4623,5986,513,687,1806,1523,3613)\n\nselect * from activities where crm_configuration_id = 13;\n\nSELECT * FROM activities WHERE uuid_to_bin('826619ce-ec8e-4e59-8467-a01f5f6ad71e') = uuid; # 418141\n\n\nselect id, team_id, crm_provider_id from crm_configurations where provider = 'hubspot' and crm_provider_id IS NOT NULL;\nSELECT * FROM accounts WHERE team_id = 2 and crm_provider_id = '1212213464' order by id desc;\nSELECT * FROM contacts WHERE team_id = 2 and account_id = 5189 order by id desc;\nSELECT * FROM contacts WHERE team_id = 2 order by id desc;\nselect * from opportunity_contacts where contact_id = 6223;\nSELECT * FROM opportunities WHERE team_id = 2 and account_id = 5189 order by id desc;\n\nselect * from crm_profiles where crm_configuration_id = 2;\n\nselect * from activities where account_id = 46;","role_description":"text entry area","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Sync Changes","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.018055556,"height":0.026666667},"on_screen":false,"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Hide This Notification","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.018055556,"height":0.026666667},"on_screen":false,"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Code changed:","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.088194445,"height":0.027777778},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Hide","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.018055556,"height":0.026666667},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"2","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.016666668,"height":0.02111111},"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"60","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.021527778,"height":0.02111111},"on_screen":false,"role_description":"text"},{"role":"AXButton","text":"Previous Highlighted Error","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.015277778,"height":0.025555555},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Next Highlighted Error","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.014583333,"height":0.025555555},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXTextArea","text":"<?php\n\ndeclare(strict_types=1);\n\nnamespace Jiminny\\Services\\Crm\\Hubspot;\n\nuse HubSpot\\Client\\Crm\\Deals\\ApiException as DealApiException;\nuse HubSpot\\Client\\Crm\\Contacts\\ApiException as ContactApiException;\nuse HubSpot\\Client\\Crm\\Companies\\ApiException as CompanyApiException;\nuse HubSpot\\Client\\Crm\\Contacts\\Model\\SimplePublicObjectWithAssociations as ContactsWithAssociations;\nuse HubSpot\\Client\\Crm\\Companies\\Model\\SimplePublicObjectWithAssociations as CompaniesWithAssociations;\nuse HubSpot\\Client\\Crm\\Deals\\Model\\SimplePublicObjectWithAssociations as DealWithAssociations;\nuse HubSpot\\Client\\Crm\\Objects\\Model\\SimplePublicObjectInput;\nuse HubSpot\\Client\\Crm\\Objects\\Model\\SimplePublicObjectWithAssociations as ObjectWithAssociations;\nuse HubSpot\\Client\\Crm\\Pipelines\\Model\\Error;\nuse HubSpot\\Client\\Crm\\Pipelines\\Model\\PipelineStage;\nuse HubSpot\\Client\\Crm\\Properties\\Model\\Property;\nuse HubSpot\\Discovery\\Discovery;\nuse Jiminny\\Component\\Utility\\Service\\ProviderRateLimiter;\nuse Jiminny\\Exceptions\\CrmException;\nuse Jiminny\\Exceptions\\RateLimitException;\nuse Jiminny\\Exceptions\\SocialAccountTokenInvalidException;\nuse Jiminny\\Jobs\\Crm\\NoteObject;\nuse Jiminny\\Models\\Crm\\Field;\nuse Jiminny\\Services\\Crm\\BaseClient;\nuse Jiminny\\Services\\Crm\\Hubspot\\DTO\\Response\\Owner;\nuse Jiminny\\Services\\SocialAccountService;\nuse SevenShores\\Hubspot\\Exceptions\\BadRequest;\nuse SevenShores\\Hubspot\\Exceptions\\HubspotException;\nuse SevenShores\\Hubspot\\Factory;\nuse SevenShores\\Hubspot\\Http\\Response;\nuse Jiminny\\Services\\Crm\\Hubspot\\Pagination\\HubspotPaginationService;\nuse Throwable;\n\n/**\n * @phpstan-type CrmFieldOption array{id:string, label:string, value?:string}\n */\nclass Client extends BaseClient implements HubspotClientInterface\n{\n public const string MIN_API_VERSION = '2';\n\n public const string BASE_URL = 'https://api.hubapi.com';\n\n public const int ASSOCIATIONS_BATCH_SIZE_LIMIT = 1000;\n\n private HubspotPaginationService $paginationService;\n private HubspotTokenManager $tokenManager;\n private ProviderRateLimiter $rateLimiter;\n\n public function __construct(\n SocialAccountService $socialAccountService,\n HubspotPaginationService $paginationService,\n HubspotTokenManager $tokenManager,\n ProviderRateLimiter $rateLimiter,\n ) {\n parent::__construct($socialAccountService);\n $this->paginationService = $paginationService;\n $this->tokenManager = $tokenManager;\n $this->rateLimiter = $rateLimiter;\n\n $this->setBaseUrl(self::BASE_URL);\n $this->setVersion(self::MIN_API_VERSION);\n }\n\n /**\n * Single entry point for every HubSpot API call. Enforces the per-portal\n * rate limit configured in the rate_limits table (morphed to the current\n * Configuration) and reacts to a real 429 from HubSpot by translating it\n * into a RateLimitException carrying Retry-After.\n *\n * Wrap any outbound HubSpot call (SDK or raw HTTP) like:\n *\n * $this->executeRequest(fn () => $this->getNewInstance()->crm()->...);\n *\n * @template T\n * @param callable(): T $apiCall\n * @return T\n *\n * @throws RateLimitException\n */\n private function executeRequest(callable $apiCall)\n {\n if (! $this->rateLimiter->canMakeRequest($this->config)) {\n $retryAfter = $this->rateLimiter->requestAvailableIn($this->config);\n\n $this->log->warning('[Hubspot] Rate limit exceeded, deferring request', [\n 'team_id' => $this->config->team_id,\n 'config_id' => $this->config->getId(),\n 'retry_after' => $retryAfter,\n ]);\n\n throw new RateLimitException(\n 'Hubspot rate limit reached for configuration ' . $this->config->getId(),\n $retryAfter,\n );\n }\n\n $this->rateLimiter->incrementRequestCount($this->config);\n\n try {\n return $apiCall();\n } catch (Throwable $e) {\n if ($this->isHubspotRateLimit($e)) {\n $retryAfter = $this->parseRetryAfter($e);\n\n $this->log->warning('[Hubspot] Received 429 from API', [\n 'team_id' => $this->config->team_id,\n 'config_id' => $this->config->getId(),\n 'retry_after' => $retryAfter,\n 'reason' => $e->getMessage(),\n ]);\n\n throw new RateLimitException('Hubspot returned 429', $retryAfter, $e);\n }\n\n throw $e;\n }\n }\n\n private function isHubspotRateLimit(Throwable $e): bool\n {\n return method_exists($e, 'getCode') && (int) $e->getCode() === 429;\n }\n\n private function parseRetryAfter(Throwable $e): int\n {\n if (method_exists($e, 'getResponseHeaders')) {\n $headers = $e->getResponseHeaders() ?: [];\n $value = $headers['Retry-After'] ?? $headers['retry-after'] ?? null;\n if (is_array($value)) {\n $value = $value[0] ?? null;\n }\n if (is_numeric($value)) {\n return (int) $value;\n }\n }\n\n return 10;\n }\n\n public function getMinimumApiVersion(): string\n {\n return self::MIN_API_VERSION;\n }\n\n public function getInstance(): Factory\n {\n return new Factory([\n 'key' => $this->accessToken,\n 'oauth2' => true,\n 'base_url' => $this->baseUrl,\n ]);\n }\n\n public function getNewInstance(): Discovery\n {\n return \\HubSpot\\Factory::createWithAccessToken($this->accessToken);\n }\n\n /**\n * Secondly and daily limits for Hubspot API\n *\n * Product Tier: Free & Starter | Professional & Enterprise | API add-on (any tier)\n * Burst: 100/10 seconds | 150/10 seconds | 200/10 seconds\n * Daily: 250,000 | 500,000 | 1,000,000\n *\n * Official documentation states: The search endpoints are rate limited to five requests per second.\n * Since with 5 RPS were still hitting secondly rate limits we lowered it to 4\n */\n public function getPaginatedData(array $payload, string $type, int $offset = 0): array\n {\n $total = 0;\n $lastId = null;\n $rows = [];\n foreach ($this->getPaginatedDataGenerator($payload, $type, $offset, $total, $lastId) as $row) {\n $rows[] = $row;\n }\n\n return ['results' => $rows, 'total' => $total, 'last_record' => $lastId];\n }\n\n /**\n * @throws HubspotException\n * @throws SocialAccountTokenInvalidException\n * @throws BadRequest\n */\n public function getPaginatedDataGenerator(\n array $payload,\n string $type,\n int $offset = 0,\n int &$total = 0,\n ?string &$lastRecordId = null\n ): \\Generator {\n return $this->paginationService->getPaginatedDataGenerator(\n $this,\n $payload,\n $type,\n $offset,\n $total,\n $lastRecordId\n );\n }\n\n /**\n * @throws DealApiException\n * @throws CrmException\n */\n public function getOpportunityById(string $crmId, array $fields): array\n {\n try {\n $deal = $this->executeRequest(fn () => $this->getNewInstance()->crm()->deals()->basicApi()->getById(\n $crmId,\n implode(',', $fields),\n 'companies,contacts'\n ));\n } catch (DealApiException $e) {\n $this->log->info('[Hubspot] Failed to fetch opportunity', [\n 'crm_id' => $crmId,\n 'reason' => $e->getMessage(),\n ]);\n\n throw $e;\n }\n\n if (! $deal instanceof DealWithAssociations) {\n throw new CrmException('Deal not found');\n }\n\n return [\n 'id' => $deal->getId(),\n 'properties' => $deal->getProperties(),\n 'associations' => $deal->getAssociations(),\n ];\n }\n\n /**\n * Generic batch read method for HubSpot objects\n *\n * @param string $objectType The object type ('deals', 'companies', 'contacts')\n * @param array<string> $crmIds Array of HubSpot object IDs (max 100)\n * @param array<string> $fields Array of property names to fetch\n *\n * @return array<string, array> Array keyed by CRM ID with object data\n */\n private function batchReadObjects(string $objectType, array $crmIds, array $fields): array\n {\n if (empty($crmIds)) {\n return [];\n }\n\n $this->validateBatchSize($objectType, $crmIds);\n $this->ensureValidToken();\n\n try {\n $batchConfig = $this->createBatchConfiguration($objectType);\n $batchReadRequest = $this->prepareBatchRequest($batchConfig, $crmIds, $fields);\n $response = $batchConfig['api']->read($batchReadRequest);\n\n $this->validateApiResponse($response, $objectType);\n\n $results = $this->processApiResults($response);\n $this->logBatchResults($objectType, $crmIds, $results);\n\n return $results;\n } catch (\\Throwable $e) {\n $this->handleBatchError($e, $objectType, $crmIds);\n }\n }\n\n private function validateBatchSize(string $objectType, array $crmIds): void\n {\n if (count($crmIds) > 100) {\n throw new \\InvalidArgumentException(\"Batch size cannot exceed 100 {$objectType}\");\n }\n }\n\n private function createBatchConfiguration(string $objectType): array\n {\n $configurations = [\n 'deals' => [\n 'batchReadRequest' => new \\HubSpot\\Client\\Crm\\Deals\\Model\\BatchReadInputSimplePublicObjectId(),\n 'inputClass' => \\HubSpot\\Client\\Crm\\Deals\\Model\\SimplePublicObjectId::class,\n 'api' => $this->getNewInstance()->crm()->deals()->batchApi(),\n ],\n 'companies' => [\n 'batchReadRequest' => new \\HubSpot\\Client\\Crm\\Companies\\Model\\BatchReadInputSimplePublicObjectId(),\n 'inputClass' => \\HubSpot\\Client\\Crm\\Companies\\Model\\SimplePublicObjectId::class,\n 'api' => $this->getNewInstance()->crm()->companies()->batchApi(),\n ],\n 'contacts' => [\n 'batchReadRequest' => new \\HubSpot\\Client\\Crm\\Contacts\\Model\\BatchReadInputSimplePublicObjectId(),\n 'inputClass' => \\HubSpot\\Client\\Crm\\Contacts\\Model\\SimplePublicObjectId::class,\n 'api' => $this->getNewInstance()->crm()->contacts()->batchApi(),\n ],\n ];\n\n if (! isset($configurations[$objectType])) {\n throw new \\InvalidArgumentException(\"Unsupported object type: {$objectType}\");\n }\n\n return $configurations[$objectType];\n }\n\n private function prepareBatchRequest(array $batchConfig, array $crmIds, array $fields): object\n {\n $batchReadRequest = $batchConfig['batchReadRequest'];\n $inputClass = $batchConfig['inputClass'];\n\n $inputs = array_map(function ($crmId) use ($inputClass) {\n $input = new $inputClass();\n $input->setId($crmId);\n\n return $input;\n }, $crmIds);\n\n $batchReadRequest->setInputs($inputs);\n $batchReadRequest->setProperties($fields);\n\n return $batchReadRequest;\n }\n\n private function validateApiResponse($response, string $objectType): void\n {\n if (! $response) {\n throw new CrmException(\"HubSpot API returned null response for {$objectType} batch read\");\n }\n }\n\n private function processApiResults($response): array\n {\n $results = [];\n $responseResults = $response->getResults();\n\n if ($responseResults) {\n foreach ($responseResults as $object) {\n if ($object && $object->getId()) {\n $results[$object->getId()] = [\n 'id' => $object->getId(),\n 'properties' => $object->getProperties() ?: [],\n ];\n }\n }\n }\n\n return $results;\n }\n\n private function logBatchResults(string $objectType, array $crmIds, array $results): void\n {\n $this->log->info(\"[HubSpot] Batch fetched {$objectType}\", [\n 'requested_count' => count($crmIds),\n 'returned_count' => count($results),\n 'crm_ids' => $crmIds,\n ]);\n }\n\n private function handleBatchError(\\Throwable $e, string $objectType, array $crmIds): void\n {\n $errorMessage = $e->getMessage() ?: 'Unknown error';\n $errorTrace = $e->getTraceAsString() ?: 'No trace available';\n\n $this->log->error(\"[HubSpot] Failed to batch fetch {$objectType}\", [\n 'crm_ids' => $crmIds,\n 'error' => $errorMessage,\n 'trace' => $errorTrace,\n ]);\n\n throw new CrmException(\"Failed to batch fetch {$objectType}: \" . $errorMessage);\n }\n\n /**\n * Batch read multiple opportunities by their CRM IDs\n *\n * @param array<string> $crmIds Array of HubSpot deal IDs (max 100)\n * @param array<string> $fields Array of property names to fetch\n *\n * @return array<string, array> Array keyed by CRM ID with opportunity data\n */\n public function getOpportunitiesByIds(array $crmIds, array $fields): array\n {\n return $this->batchReadObjects('deals', $crmIds, $fields);\n }\n\n /**\n * Batch read multiple companies by their CRM IDs\n *\n * @param array<string> $crmIds Array of HubSpot company IDs (max 100)\n * @param array<string> $fields Array of property names to fetch\n *\n * @return array<string, array> Array keyed by CRM ID with company data\n */\n public function getCompaniesByIds(array $crmIds, array $fields): array\n {\n return $this->batchReadObjects('companies', $crmIds, $fields);\n }\n\n /**\n * Batch read multiple contacts by their CRM IDs\n *\n * @param array<string> $crmIds Array of HubSpot contact IDs (max 100)\n * @param array<string> $fields Array of property names to fetch\n *\n * @return array<string, array> Array keyed by CRM ID with contact data\n */\n public function getContactsByIds(array $crmIds, array $fields): array\n {\n return $this->batchReadObjects('contacts', $crmIds, $fields);\n }\n\n /**\n * @throws CompanyApiException\n * @throws CrmException\n */\n public function getAccountById(string $crmId, array $fields): array\n {\n try {\n $company = $this->getNewInstance()->crm()->companies()->basicApi()->getById(\n $crmId,\n implode(',', $fields),\n );\n } catch (CompanyApiException $e) {\n $this->log->info('[Hubspot] Failed to fetch account', [\n 'crm_id' => $crmId,\n 'reason' => $e->getMessage(),\n ]);\n\n throw $e;\n }\n\n if (! $company instanceof CompaniesWithAssociations) {\n throw new CrmException('Account not found');\n }\n\n return [\n 'id' => $company->getId(),\n 'properties' => $company->getProperties(),\n ];\n }\n\n /**\n * @throws ContactApiException\n * @throws CrmException\n */\n public function getContactById(string $crmId, array $fields): array\n {\n try {\n $contact = $this->getNewInstance()->crm()->contacts()->basicApi()->getById(\n $crmId,\n implode(',', $fields)\n );\n } catch (ContactApiException $e) {\n $this->log->info('[Hubspot] Failed to fetch contact', [\n 'crm_id' => $crmId,\n 'reason' => $e->getMessage(),\n ]);\n\n throw $e;\n }\n\n if (! $contact instanceof ContactsWithAssociations) {\n throw new CrmException('Contact not found');\n }\n\n return [\n 'id' => $contact->getId(),\n 'properties' => $contact->getProperties(),\n ];\n }\n\n /**\n * This is email search request that Hubspot offers as GET (more generous quota)\n */\n public function getContactByEmail(string $email, array $fields = []): array\n {\n try {\n $contact = $this->getNewInstance()->crm()->contacts()->basicApi()->getById(\n $email,\n implode(',', $fields),\n null,\n false,\n 'email'\n );\n\n return [\n 'id' => $contact->getId(),\n 'properties' => $contact->getProperties(),\n ];\n } catch (ContactApiException $e) {\n $this->log->info('[Hubspot] Failed to fetch contact', [\n 'email' => $email,\n 'reason' => $e->getMessage(),\n ]);\n\n return [];\n }\n }\n\n /**\n * @throws CrmException\n */\n public function fetchProperty(string $objectType, string $propertyId): Property\n {\n $result = $this->getNewInstance()->crm()->properties()->coreApi()->getByName($objectType, $propertyId);\n\n if (! $result instanceof Property) {\n $this->log->error('[Hubspot] Failed to fetch property', [\n 'object_type' => $objectType,\n 'property_id' => $propertyId,\n 'reason' => $result->getMessage(),\n ]);\n\n throw new CrmException('Failed to fetch property');\n }\n\n return $result;\n }\n\n /**\n * @return array<CrmFieldOption>\n */\n public function fetchPropertyOptions(string $objectType, string $propertyId): array\n {\n /** @var array<CrmFieldOption> */\n return $this->fetchProperty($objectType, $propertyId)->getOptions();\n }\n\n /**\n * @return array<array{id:string, label:string, deleted:bool}>\n */\n public function fetchCallDispositions(): array\n {\n /** @var Response $response */\n $response = $this->getInstance()->engagements()->getCallDispositions();\n\n /**\n * @var array<array{\n * id:string,\n * label:string,\n * deleted: bool\n * }>\n */\n return $response->toArray();\n }\n\n /**\n * @return array<CrmFieldOption>\n */\n public function fetchOpportunityPipelineStages(): array\n {\n $stages = [];\n $apiResponse = $this->getNewInstance()->crm()->pipelines()->pipelinesApi()->getAll('deals');\n\n if ($apiResponse instanceof Error) {\n $this->log->error('[Hubspot] Failed to fetch opportunity pipelines', [\n 'reason' => $apiResponse->getMessage(),\n ]);\n\n return [];\n }\n\n foreach ($apiResponse->getResults() as $pipeline) {\n $pipelineStages = array_map(\n static function (PipelineStage $stage) {\n return [\n 'id' => $stage->getId(),\n 'label' => $stage->getLabel(),\n ];\n },\n $pipeline->getStages()\n );\n\n $stages = array_merge($stages, $pipelineStages);\n }\n\n return $stages;\n }\n\n public function fetchOpportunityPipelines(): array\n {\n $pipelines = [];\n\n try {\n $apiResponse = $this->makeRequest('/crm/v3/pipelines/deals');\n } catch (\\Exception $e) {\n $this->log->info('[Hubspot] Failed to fetch opportunity pipelines', [\n 'reason' => $e->getMessage(),\n ]);\n\n return [];\n }\n\n $response = $apiResponse->toArray();\n\n foreach ($response['results'] as $pipeline) {\n $pipelines[] = [\n 'id' => $pipeline['id'],\n 'label' => $pipeline['label'],\n ];\n }\n\n return $pipelines;\n }\n\n /**\n * @return array<CrmFieldOption>\n */\n public function fetchMeetingOutcomeFieldOptions(Field $field): array\n {\n return $field->getCrmProviderId() === 'meetingOutcome'\n ? $this->fetchMeetingOutcomeTypes()\n : $this->fetchCallActivityTypes();\n }\n\n public function fetchMeetingOutcomeTypes(): array\n {\n return $this->extractMeetingTypeOptions(\n 'https://api.hubapi.com/crm/v3/properties/meeting/hs_meeting_outcome'\n );\n }\n\n public function fetchCallActivityTypes(): array\n {\n return $this->extractMeetingTypeOptions(\n 'https://api.hubapi.com/crm/v3/properties/call/hs_activity_type'\n );\n }\n\n private function extractMeetingTypeOptions(string $endpoint): array\n {\n /** @var Response $response */\n $response = $this->getInstance()\n ->getClient()\n ->request('GET', $endpoint);\n\n /**\n * @var array<array{\n * value: string,\n * label: string,\n * displayOrder: int\n * }> $optionData\n */\n $optionData = $response->toArray()['options'] ?? [];\n\n $options = [];\n foreach ($optionData as $item) {\n $options[] = [\n 'id' => $item['value'],\n 'value' => $item['value'],\n 'label' => $item['label'],\n 'display_order' => $item['displayOrder'],\n ];\n }\n\n return $options;\n }\n\n /**\n * @return array<CrmFieldOption>\n */\n public function fetchDispositionFieldOptions(): array\n {\n $options = [];\n\n $dispositions = $this->fetchCallDispositions();\n\n foreach ($dispositions as $disposition) {\n if ($disposition['deleted'] !== false) {\n continue;\n }\n\n $option['value'] = $disposition['id'];\n $option['id'] = $disposition['id'];\n $option['label'] = $disposition['label'];\n\n $options[] = $option;\n }\n\n return $options;\n }\n\n /**\n * @return array<CrmFieldOption>\n */\n public function fetchOpportunityFieldOptions(Field $field): array\n {\n if ($field->isStageField()) {\n return $this->fetchOpportunityPipelineStages();\n }\n\n if ($field->isPipelineField()) {\n return $this->fetchOpportunityPipelines();\n }\n\n return $this->fetchPropertyOptions('deals', $field->getCrmProviderId());\n }\n\n /**\n * @throws BadRequest\n * @throws HubspotException\n */\n public function makeRequest(string $endpoint, $method = 'GET', $payload = [], ?string $queryString = null)\n {\n $endpoint = self::BASE_URL . $endpoint;\n\n if ($method === 'GET') {\n $response = $this->getInstance()->getClient()?->request(\n method: $method,\n endpoint: $endpoint,\n query_string: $queryString\n );\n } else {\n $response = $this->getInstance()->getClient()->request($method, $endpoint, [\n 'json' => ($payload),\n ]);\n }\n\n $max = $response->getHeaderLine('X-HubSpot-RateLimit-Max'); // \"110\"\n $remaining = $response->getHeaderLine('X-HubSpot-RateLimit-Remaining'); // \"109\"\n $interval = $response->getHeaderLine('X-HubSpot-RateLimit-Interval-Milliseconds'); // \"10000\"\n $body = json_decode((string) $response->getBody(), true);\n\n \\Illuminate\\Support\\Facades\\Log::channel('custom_channel')->info('$max ' . PHP_EOL . print_r($max, true));\n \\Illuminate\\Support\\Facades\\Log::channel('custom_channel')->info('$remaining ' . PHP_EOL . print_r($remaining, true));\n \\Illuminate\\Support\\Facades\\Log::channel('custom_channel')->info('$interval ' . PHP_EOL . print_r($interval, true));\n \\Illuminate\\Support\\Facades\\Log::channel('custom_channel')->info('$body ' . PHP_EOL . print_r($body, true));\n\n return $response;\n }\n\n /**\n * @throws BadRequest\n * @throws HubspotException\n */\n public function createMeeting(array $payload): Response\n {\n $endpoint = '/crm/v3/objects/meetings';\n\n return $this->makeRequest($endpoint, 'POST', $payload);\n }\n\n /**\n * @throws BadRequest\n * @throws HubspotException\n */\n public function updateMeeting(string $meetingId, array $payload): Response\n {\n $endpoint = '/crm/v3/objects/meetings/' . $meetingId;\n\n return $this->makeRequest($endpoint, 'PATCH', $payload);\n }\n\n /**\n * @throws \\Exception\n */\n public function createNote(\n string $body,\n string $ownerId,\n int $timestamp,\n string $objectId,\n NoteObject $noteObject\n ): ?string {\n try {\n $noteInput = new SimplePublicObjectInput([\n 'properties' => [\n 'hs_note_body' => $body,\n 'hubspot_owner_id' => $ownerId,\n 'hs_timestamp' => $timestamp,\n ],\n ]);\n\n // Create note\n $note = $this->getNewInstance()->crm()->objects()->basicApi()->create('note', $noteInput);\n\n $this->getNewInstance()->crm()->objects()->associationsApi()->create(\n 'note',\n $note->getId(),\n $this->getNoteObject($noteObject),\n $objectId,\n $this->getNoteAssociationType($noteObject),\n );\n\n return $note->getId();\n } catch (\\Exception $e) {\n $this->log->error('[Hubspot] Failed to create note', [\n 'objectId' => $objectId,\n 'noteObject' => $noteObject->getObjectType(),\n 'reason' => $e->getMessage(),\n ]);\n\n \\Sentry::captureException($e);\n }\n\n return null;\n }\n\n public function updateEngagement(string $objectId, array $engagement, array $metadata): void\n {\n $this->getInstance()->engagements()->update($objectId, $engagement, $metadata);\n }\n\n public function getEngagementData(string $engagementId): array\n {\n $engagement = $this->getInstance()->engagements()->get($engagementId);\n\n return $engagement->toArray();\n }\n\n public function createEngagement(array $engagement, array $associations, array $metadata): Response\n {\n return $this->getInstance()\n ->engagements()\n ->create($engagement, $associations, $metadata);\n }\n\n public function isUnauthorizedException(\\Exception $e): bool\n {\n // Check for specific HubSpot API exception types first\n if ($e instanceof BadRequest) {\n // BadRequest can contain 401 status codes\n return $e->getCode() === 401;\n }\n\n // Check for HTTP client exceptions with status codes\n if ($e instanceof \\GuzzleHttp\\Exception\\RequestException && $e->hasResponse()) {\n $response = $e->getResponse();\n if ($response !== null) {\n return $response->getStatusCode() === 401;\n }\n }\n\n // Check for Guzzle HTTP exceptions\n if ($e instanceof \\GuzzleHttp\\Exception\\ClientException) {\n return $e->getCode() === 401;\n }\n\n // Fallback to string matching as last resort, but be more specific\n $message = strtolower($e->getMessage());\n\n return str_contains($message, '401 unauthorized') ||\n str_contains($message, 'http 401') ||\n str_contains($message, 'status code 401') ||\n (preg_match('/\\b401\\b/', $message) && str_contains($message, 'unauthorized'));\n }\n\n /**\n * Validates and refreshes the access token if needed before API requests.\n * This ensures long-running processes don't fail due to token expiration.\n *\n * @throws SocialAccountTokenInvalidException\n */\n public function ensureValidToken(): void\n {\n if ($this->oauthAccount === null) {\n return;\n }\n\n $newToken = $this->tokenManager->ensureValidToken($this->oauthAccount);\n if ($newToken !== null) {\n $this->accessToken = $newToken;\n }\n }\n\n public function getConfig()\n {\n return $this->config;\n }\n\n // returns only active (archived=false)\n public function getOwners(): array\n {\n return $this->getNewInstance()->crm()->owners()->getAll();\n }\n\n /**\n * @param bool $archived\n *\n * @return array<Owner>|[]\n */\n public function getOwnersArchived(bool $archived = true): array\n {\n $endpoint = '/crm/v3/owners';\n $queryParams = [\n 'archived' => $archived ? 'true' : 'false',\n ];\n $queryString = http_build_query($queryParams);\n\n $owners = [];\n\n try {\n $response = $this->makeRequest(endpoint: $endpoint, queryString: $queryString);\n $responseData = $response?->toArray();\n\n foreach ($responseData['results'] as $result) {\n try {\n $owners[] = Owner::create($result);\n } catch (Throwable $e) {\n $this->log->error('[HubSpot] Failed to process owner data', [\n 'result' => $result,\n 'error' => $e->getMessage(),\n ]);\n\n continue;\n }\n }\n } catch (Throwable $e) {\n $this->log->error('HubSpot] Failed to fetch owners', [\n 'archived' => $archived,\n 'error' => $e->getMessage(),\n ]);\n\n return [];\n }\n\n return $owners;\n }\n\n public function getMeeting(string $engagementId): ObjectWithAssociations\n {\n return $this->getNewInstance()->crm()->objects()->basicApi()\n ->getById('meeting', $engagementId, null, 'contact,company,deal');\n }\n\n public function deleteEngagement(string $engagementId): void\n {\n $this->getInstance()->engagements()->delete((int) $engagementId);\n }\n\n public function getAssociationsData(array $ids, string $fromObject, string $toObject): array\n {\n $associationData = [];\n $idChunks = array_chunk($ids, self::ASSOCIATIONS_BATCH_SIZE_LIMIT);\n\n foreach ($idChunks as $idChunk) {\n try {\n $batchInput = new \\HubSpot\\Client\\Crm\\Associations\\Model\\BatchInputPublicObjectId();\n $batchInput->setInputs(array_map(function ($id) {\n $publicObjectId = new \\HubSpot\\Client\\Crm\\Associations\\Model\\PublicObjectId();\n $publicObjectId->setId($id);\n\n return $publicObjectId;\n }, $idChunk));\n\n $associatedObjectsData = $this\n ->getNewInstance()\n ->crm()\n ->associations()\n ->batchApi()\n ->read($fromObject, $toObject, $batchInput);\n\n if ($associatedObjectsData instanceof \\HubSpot\\Client\\Crm\\Associations\\Model\\BatchResponsePublicAssociationMulti) {\n foreach ($associatedObjectsData->getResults() as $association) {\n $from = $association->getFrom()->getId();\n $toAssociations = $association->getTo();\n\n if (! empty($toAssociations)) {\n $associationData[$from] = array_map(function ($item) {\n return $item->getId();\n }, $toAssociations);\n }\n }\n }\n } catch (\\Exception $e) {\n $this->log->error('[Hubspot] Failed to fetch associations', [\n 'from_object' => $fromObject,\n 'to_object' => $toObject,\n 'reason' => $e->getMessage(),\n ]);\n }\n }\n\n return $associationData;\n }\n\n /**\n * @throws \\Exception\n */\n private function getNoteAssociationType(NoteObject $noteObject): string\n {\n return match($noteObject) {\n NoteObject::Opportunity => 'note_to_deal',\n NoteObject::Lead, NoteObject::Contact => 'note_to_contact', // or 'note_to_lead' if your portal supports it\n NoteObject::Account => 'note_to_company',\n NoteObject::Call, NoteObject::Event => throw new \\Exception('Not supported'),\n };\n }\n\n /**\n * @throws \\Exception\n */\n private function getNoteObject(NoteObject $noteObject): string\n {\n return match($noteObject) {\n NoteObject::Opportunity => 'deal',\n NoteObject::Lead, NoteObject::Contact => 'contact',\n NoteObject::Account => 'company',\n NoteObject::Call, NoteObject::Event => throw new \\Exception('Not supported'),\n };\n }\n\n public function addAssociations(string $objectType, string $associationType, array $payload): Response\n {\n $endpoint = \"/crm/v4/associations/$objectType/$associationType/batch/create\";\n\n return $this->makeRequest($endpoint, 'POST', $payload);\n }\n\n public function removeAssociations(string $objectType, string $associationType, array $payload): Response\n {\n $endpoint = \"/crm/v4/associations/$objectType/$associationType/batch/archive\";\n\n return $this->makeRequest($endpoint, 'POST', $payload);\n }\n}","depth":4,"on_screen":true,"value":"<?php\n\ndeclare(strict_types=1);\n\nnamespace Jiminny\\Services\\Crm\\Hubspot;\n\nuse HubSpot\\Client\\Crm\\Deals\\ApiException as DealApiException;\nuse HubSpot\\Client\\Crm\\Contacts\\ApiException as ContactApiException;\nuse HubSpot\\Client\\Crm\\Companies\\ApiException as CompanyApiException;\nuse HubSpot\\Client\\Crm\\Contacts\\Model\\SimplePublicObjectWithAssociations as ContactsWithAssociations;\nuse HubSpot\\Client\\Crm\\Companies\\Model\\SimplePublicObjectWithAssociations as CompaniesWithAssociations;\nuse HubSpot\\Client\\Crm\\Deals\\Model\\SimplePublicObjectWithAssociations as DealWithAssociations;\nuse HubSpot\\Client\\Crm\\Objects\\Model\\SimplePublicObjectInput;\nuse HubSpot\\Client\\Crm\\Objects\\Model\\SimplePublicObjectWithAssociations as ObjectWithAssociations;\nuse HubSpot\\Client\\Crm\\Pipelines\\Model\\Error;\nuse HubSpot\\Client\\Crm\\Pipelines\\Model\\PipelineStage;\nuse HubSpot\\Client\\Crm\\Properties\\Model\\Property;\nuse HubSpot\\Discovery\\Discovery;\nuse Jiminny\\Component\\Utility\\Service\\ProviderRateLimiter;\nuse Jiminny\\Exceptions\\CrmException;\nuse Jiminny\\Exceptions\\RateLimitException;\nuse Jiminny\\Exceptions\\SocialAccountTokenInvalidException;\nuse Jiminny\\Jobs\\Crm\\NoteObject;\nuse Jiminny\\Models\\Crm\\Field;\nuse Jiminny\\Services\\Crm\\BaseClient;\nuse Jiminny\\Services\\Crm\\Hubspot\\DTO\\Response\\Owner;\nuse Jiminny\\Services\\SocialAccountService;\nuse SevenShores\\Hubspot\\Exceptions\\BadRequest;\nuse SevenShores\\Hubspot\\Exceptions\\HubspotException;\nuse SevenShores\\Hubspot\\Factory;\nuse SevenShores\\Hubspot\\Http\\Response;\nuse Jiminny\\Services\\Crm\\Hubspot\\Pagination\\HubspotPaginationService;\nuse Throwable;\n\n/**\n * @phpstan-type CrmFieldOption array{id:string, label:string, value?:string}\n */\nclass Client extends BaseClient implements HubspotClientInterface\n{\n public const string MIN_API_VERSION = '2';\n\n public const string BASE_URL = 'https://api.hubapi.com';\n\n public const int ASSOCIATIONS_BATCH_SIZE_LIMIT = 1000;\n\n private HubspotPaginationService $paginationService;\n private HubspotTokenManager $tokenManager;\n private ProviderRateLimiter $rateLimiter;\n\n public function __construct(\n SocialAccountService $socialAccountService,\n HubspotPaginationService $paginationService,\n HubspotTokenManager $tokenManager,\n ProviderRateLimiter $rateLimiter,\n ) {\n parent::__construct($socialAccountService);\n $this->paginationService = $paginationService;\n $this->tokenManager = $tokenManager;\n $this->rateLimiter = $rateLimiter;\n\n $this->setBaseUrl(self::BASE_URL);\n $this->setVersion(self::MIN_API_VERSION);\n }\n\n /**\n * Single entry point for every HubSpot API call. Enforces the per-portal\n * rate limit configured in the rate_limits table (morphed to the current\n * Configuration) and reacts to a real 429 from HubSpot by translating it\n * into a RateLimitException carrying Retry-After.\n *\n * Wrap any outbound HubSpot call (SDK or raw HTTP) like:\n *\n * $this->executeRequest(fn () => $this->getNewInstance()->crm()->...);\n *\n * @template T\n * @param callable(): T $apiCall\n * @return T\n *\n * @throws RateLimitException\n */\n private function executeRequest(callable $apiCall)\n {\n if (! $this->rateLimiter->canMakeRequest($this->config)) {\n $retryAfter = $this->rateLimiter->requestAvailableIn($this->config);\n\n $this->log->warning('[Hubspot] Rate limit exceeded, deferring request', [\n 'team_id' => $this->config->team_id,\n 'config_id' => $this->config->getId(),\n 'retry_after' => $retryAfter,\n ]);\n\n throw new RateLimitException(\n 'Hubspot rate limit reached for configuration ' . $this->config->getId(),\n $retryAfter,\n );\n }\n\n $this->rateLimiter->incrementRequestCount($this->config);\n\n try {\n return $apiCall();\n } catch (Throwable $e) {\n if ($this->isHubspotRateLimit($e)) {\n $retryAfter = $this->parseRetryAfter($e);\n\n $this->log->warning('[Hubspot] Received 429 from API', [\n 'team_id' => $this->config->team_id,\n 'config_id' => $this->config->getId(),\n 'retry_after' => $retryAfter,\n 'reason' => $e->getMessage(),\n ]);\n\n throw new RateLimitException('Hubspot returned 429', $retryAfter, $e);\n }\n\n throw $e;\n }\n }\n\n private function isHubspotRateLimit(Throwable $e): bool\n {\n return method_exists($e, 'getCode') && (int) $e->getCode() === 429;\n }\n\n private function parseRetryAfter(Throwable $e): int\n {\n if (method_exists($e, 'getResponseHeaders')) {\n $headers = $e->getResponseHeaders() ?: [];\n $value = $headers['Retry-After'] ?? $headers['retry-after'] ?? null;\n if (is_array($value)) {\n $value = $value[0] ?? null;\n }\n if (is_numeric($value)) {\n return (int) $value;\n }\n }\n\n return 10;\n }\n\n public function getMinimumApiVersion(): string\n {\n return self::MIN_API_VERSION;\n }\n\n public function getInstance(): Factory\n {\n return new Factory([\n 'key' => $this->accessToken,\n 'oauth2' => true,\n 'base_url' => $this->baseUrl,\n ]);\n }\n\n public function getNewInstance(): Discovery\n {\n return \\HubSpot\\Factory::createWithAccessToken($this->accessToken);\n }\n\n /**\n * Secondly and daily limits for Hubspot API\n *\n * Product Tier: Free & Starter | Professional & Enterprise | API add-on (any tier)\n * Burst: 100/10 seconds | 150/10 seconds | 200/10 seconds\n * Daily: 250,000 | 500,000 | 1,000,000\n *\n * Official documentation states: The search endpoints are rate limited to five requests per second.\n * Since with 5 RPS were still hitting secondly rate limits we lowered it to 4\n */\n public function getPaginatedData(array $payload, string $type, int $offset = 0): array\n {\n $total = 0;\n $lastId = null;\n $rows = [];\n foreach ($this->getPaginatedDataGenerator($payload, $type, $offset, $total, $lastId) as $row) {\n $rows[] = $row;\n }\n\n return ['results' => $rows, 'total' => $total, 'last_record' => $lastId];\n }\n\n /**\n * @throws HubspotException\n * @throws SocialAccountTokenInvalidException\n * @throws BadRequest\n */\n public function getPaginatedDataGenerator(\n array $payload,\n string $type,\n int $offset = 0,\n int &$total = 0,\n ?string &$lastRecordId = null\n ): \\Generator {\n return $this->paginationService->getPaginatedDataGenerator(\n $this,\n $payload,\n $type,\n $offset,\n $total,\n $lastRecordId\n );\n }\n\n /**\n * @throws DealApiException\n * @throws CrmException\n */\n public function getOpportunityById(string $crmId, array $fields): array\n {\n try {\n $deal = $this->executeRequest(fn () => $this->getNewInstance()->crm()->deals()->basicApi()->getById(\n $crmId,\n implode(',', $fields),\n 'companies,contacts'\n ));\n } catch (DealApiException $e) {\n $this->log->info('[Hubspot] Failed to fetch opportunity', [\n 'crm_id' => $crmId,\n 'reason' => $e->getMessage(),\n ]);\n\n throw $e;\n }\n\n if (! $deal instanceof DealWithAssociations) {\n throw new CrmException('Deal not found');\n }\n\n return [\n 'id' => $deal->getId(),\n 'properties' => $deal->getProperties(),\n 'associations' => $deal->getAssociations(),\n ];\n }\n\n /**\n * Generic batch read method for HubSpot objects\n *\n * @param string $objectType The object type ('deals', 'companies', 'contacts')\n * @param array<string> $crmIds Array of HubSpot object IDs (max 100)\n * @param array<string> $fields Array of property names to fetch\n *\n * @return array<string, array> Array keyed by CRM ID with object data\n */\n private function batchReadObjects(string $objectType, array $crmIds, array $fields): array\n {\n if (empty($crmIds)) {\n return [];\n }\n\n $this->validateBatchSize($objectType, $crmIds);\n $this->ensureValidToken();\n\n try {\n $batchConfig = $this->createBatchConfiguration($objectType);\n $batchReadRequest = $this->prepareBatchRequest($batchConfig, $crmIds, $fields);\n $response = $batchConfig['api']->read($batchReadRequest);\n\n $this->validateApiResponse($response, $objectType);\n\n $results = $this->processApiResults($response);\n $this->logBatchResults($objectType, $crmIds, $results);\n\n return $results;\n } catch (\\Throwable $e) {\n $this->handleBatchError($e, $objectType, $crmIds);\n }\n }\n\n private function validateBatchSize(string $objectType, array $crmIds): void\n {\n if (count($crmIds) > 100) {\n throw new \\InvalidArgumentException(\"Batch size cannot exceed 100 {$objectType}\");\n }\n }\n\n private function createBatchConfiguration(string $objectType): array\n {\n $configurations = [\n 'deals' => [\n 'batchReadRequest' => new \\HubSpot\\Client\\Crm\\Deals\\Model\\BatchReadInputSimplePublicObjectId(),\n 'inputClass' => \\HubSpot\\Client\\Crm\\Deals\\Model\\SimplePublicObjectId::class,\n 'api' => $this->getNewInstance()->crm()->deals()->batchApi(),\n ],\n 'companies' => [\n 'batchReadRequest' => new \\HubSpot\\Client\\Crm\\Companies\\Model\\BatchReadInputSimplePublicObjectId(),\n 'inputClass' => \\HubSpot\\Client\\Crm\\Companies\\Model\\SimplePublicObjectId::class,\n 'api' => $this->getNewInstance()->crm()->companies()->batchApi(),\n ],\n 'contacts' => [\n 'batchReadRequest' => new \\HubSpot\\Client\\Crm\\Contacts\\Model\\BatchReadInputSimplePublicObjectId(),\n 'inputClass' => \\HubSpot\\Client\\Crm\\Contacts\\Model\\SimplePublicObjectId::class,\n 'api' => $this->getNewInstance()->crm()->contacts()->batchApi(),\n ],\n ];\n\n if (! isset($configurations[$objectType])) {\n throw new \\InvalidArgumentException(\"Unsupported object type: {$objectType}\");\n }\n\n return $configurations[$objectType];\n }\n\n private function prepareBatchRequest(array $batchConfig, array $crmIds, array $fields): object\n {\n $batchReadRequest = $batchConfig['batchReadRequest'];\n $inputClass = $batchConfig['inputClass'];\n\n $inputs = array_map(function ($crmId) use ($inputClass) {\n $input = new $inputClass();\n $input->setId($crmId);\n\n return $input;\n }, $crmIds);\n\n $batchReadRequest->setInputs($inputs);\n $batchReadRequest->setProperties($fields);\n\n return $batchReadRequest;\n }\n\n private function validateApiResponse($response, string $objectType): void\n {\n if (! $response) {\n throw new CrmException(\"HubSpot API returned null response for {$objectType} batch read\");\n }\n }\n\n private function processApiResults($response): array\n {\n $results = [];\n $responseResults = $response->getResults();\n\n if ($responseResults) {\n foreach ($responseResults as $object) {\n if ($object && $object->getId()) {\n $results[$object->getId()] = [\n 'id' => $object->getId(),\n 'properties' => $object->getProperties() ?: [],\n ];\n }\n }\n }\n\n return $results;\n }\n\n private function logBatchResults(string $objectType, array $crmIds, array $results): void\n {\n $this->log->info(\"[HubSpot] Batch fetched {$objectType}\", [\n 'requested_count' => count($crmIds),\n 'returned_count' => count($results),\n 'crm_ids' => $crmIds,\n ]);\n }\n\n private function handleBatchError(\\Throwable $e, string $objectType, array $crmIds): void\n {\n $errorMessage = $e->getMessage() ?: 'Unknown error';\n $errorTrace = $e->getTraceAsString() ?: 'No trace available';\n\n $this->log->error(\"[HubSpot] Failed to batch fetch {$objectType}\", [\n 'crm_ids' => $crmIds,\n 'error' => $errorMessage,\n 'trace' => $errorTrace,\n ]);\n\n throw new CrmException(\"Failed to batch fetch {$objectType}: \" . $errorMessage);\n }\n\n /**\n * Batch read multiple opportunities by their CRM IDs\n *\n * @param array<string> $crmIds Array of HubSpot deal IDs (max 100)\n * @param array<string> $fields Array of property names to fetch\n *\n * @return array<string, array> Array keyed by CRM ID with opportunity data\n */\n public function getOpportunitiesByIds(array $crmIds, array $fields): array\n {\n return $this->batchReadObjects('deals', $crmIds, $fields);\n }\n\n /**\n * Batch read multiple companies by their CRM IDs\n *\n * @param array<string> $crmIds Array of HubSpot company IDs (max 100)\n * @param array<string> $fields Array of property names to fetch\n *\n * @return array<string, array> Array keyed by CRM ID with company data\n */\n public function getCompaniesByIds(array $crmIds, array $fields): array\n {\n return $this->batchReadObjects('companies', $crmIds, $fields);\n }\n\n /**\n * Batch read multiple contacts by their CRM IDs\n *\n * @param array<string> $crmIds Array of HubSpot contact IDs (max 100)\n * @param array<string> $fields Array of property names to fetch\n *\n * @return array<string, array> Array keyed by CRM ID with contact data\n */\n public function getContactsByIds(array $crmIds, array $fields): array\n {\n return $this->batchReadObjects('contacts', $crmIds, $fields);\n }\n\n /**\n * @throws CompanyApiException\n * @throws CrmException\n */\n public function getAccountById(string $crmId, array $fields): array\n {\n try {\n $company = $this->getNewInstance()->crm()->companies()->basicApi()->getById(\n $crmId,\n implode(',', $fields),\n );\n } catch (CompanyApiException $e) {\n $this->log->info('[Hubspot] Failed to fetch account', [\n 'crm_id' => $crmId,\n 'reason' => $e->getMessage(),\n ]);\n\n throw $e;\n }\n\n if (! $company instanceof CompaniesWithAssociations) {\n throw new CrmException('Account not found');\n }\n\n return [\n 'id' => $company->getId(),\n 'properties' => $company->getProperties(),\n ];\n }\n\n /**\n * @throws ContactApiException\n * @throws CrmException\n */\n public function getContactById(string $crmId, array $fields): array\n {\n try {\n $contact = $this->getNewInstance()->crm()->contacts()->basicApi()->getById(\n $crmId,\n implode(',', $fields)\n );\n } catch (ContactApiException $e) {\n $this->log->info('[Hubspot] Failed to fetch contact', [\n 'crm_id' => $crmId,\n 'reason' => $e->getMessage(),\n ]);\n\n throw $e;\n }\n\n if (! $contact instanceof ContactsWithAssociations) {\n throw new CrmException('Contact not found');\n }\n\n return [\n 'id' => $contact->getId(),\n 'properties' => $contact->getProperties(),\n ];\n }\n\n /**\n * This is email search request that Hubspot offers as GET (more generous quota)\n */\n public function getContactByEmail(string $email, array $fields = []): array\n {\n try {\n $contact = $this->getNewInstance()->crm()->contacts()->basicApi()->getById(\n $email,\n implode(',', $fields),\n null,\n false,\n 'email'\n );\n\n return [\n 'id' => $contact->getId(),\n 'properties' => $contact->getProperties(),\n ];\n } catch (ContactApiException $e) {\n $this->log->info('[Hubspot] Failed to fetch contact', [\n 'email' => $email,\n 'reason' => $e->getMessage(),\n ]);\n\n return [];\n }\n }\n\n /**\n * @throws CrmException\n */\n public function fetchProperty(string $objectType, string $propertyId): Property\n {\n $result = $this->getNewInstance()->crm()->properties()->coreApi()->getByName($objectType, $propertyId);\n\n if (! $result instanceof Property) {\n $this->log->error('[Hubspot] Failed to fetch property', [\n 'object_type' => $objectType,\n 'property_id' => $propertyId,\n 'reason' => $result->getMessage(),\n ]);\n\n throw new CrmException('Failed to fetch property');\n }\n\n return $result;\n }\n\n /**\n * @return array<CrmFieldOption>\n */\n public function fetchPropertyOptions(string $objectType, string $propertyId): array\n {\n /** @var array<CrmFieldOption> */\n return $this->fetchProperty($objectType, $propertyId)->getOptions();\n }\n\n /**\n * @return array<array{id:string, label:string, deleted:bool}>\n */\n public function fetchCallDispositions(): array\n {\n /** @var Response $response */\n $response = $this->getInstance()->engagements()->getCallDispositions();\n\n /**\n * @var array<array{\n * id:string,\n * label:string,\n * deleted: bool\n * }>\n */\n return $response->toArray();\n }\n\n /**\n * @return array<CrmFieldOption>\n */\n public function fetchOpportunityPipelineStages(): array\n {\n $stages = [];\n $apiResponse = $this->getNewInstance()->crm()->pipelines()->pipelinesApi()->getAll('deals');\n\n if ($apiResponse instanceof Error) {\n $this->log->error('[Hubspot] Failed to fetch opportunity pipelines', [\n 'reason' => $apiResponse->getMessage(),\n ]);\n\n return [];\n }\n\n foreach ($apiResponse->getResults() as $pipeline) {\n $pipelineStages = array_map(\n static function (PipelineStage $stage) {\n return [\n 'id' => $stage->getId(),\n 'label' => $stage->getLabel(),\n ];\n },\n $pipeline->getStages()\n );\n\n $stages = array_merge($stages, $pipelineStages);\n }\n\n return $stages;\n }\n\n public function fetchOpportunityPipelines(): array\n {\n $pipelines = [];\n\n try {\n $apiResponse = $this->makeRequest('/crm/v3/pipelines/deals');\n } catch (\\Exception $e) {\n $this->log->info('[Hubspot] Failed to fetch opportunity pipelines', [\n 'reason' => $e->getMessage(),\n ]);\n\n return [];\n }\n\n $response = $apiResponse->toArray();\n\n foreach ($response['results'] as $pipeline) {\n $pipelines[] = [\n 'id' => $pipeline['id'],\n 'label' => $pipeline['label'],\n ];\n }\n\n return $pipelines;\n }\n\n /**\n * @return array<CrmFieldOption>\n */\n public function fetchMeetingOutcomeFieldOptions(Field $field): array\n {\n return $field->getCrmProviderId() === 'meetingOutcome'\n ? $this->fetchMeetingOutcomeTypes()\n : $this->fetchCallActivityTypes();\n }\n\n public function fetchMeetingOutcomeTypes(): array\n {\n return $this->extractMeetingTypeOptions(\n 'https://api.hubapi.com/crm/v3/properties/meeting/hs_meeting_outcome'\n );\n }\n\n public function fetchCallActivityTypes(): array\n {\n return $this->extractMeetingTypeOptions(\n 'https://api.hubapi.com/crm/v3/properties/call/hs_activity_type'\n );\n }\n\n private function extractMeetingTypeOptions(string $endpoint): array\n {\n /** @var Response $response */\n $response = $this->getInstance()\n ->getClient()\n ->request('GET', $endpoint);\n\n /**\n * @var array<array{\n * value: string,\n * label: string,\n * displayOrder: int\n * }> $optionData\n */\n $optionData = $response->toArray()['options'] ?? [];\n\n $options = [];\n foreach ($optionData as $item) {\n $options[] = [\n 'id' => $item['value'],\n 'value' => $item['value'],\n 'label' => $item['label'],\n 'display_order' => $item['displayOrder'],\n ];\n }\n\n return $options;\n }\n\n /**\n * @return array<CrmFieldOption>\n */\n public function fetchDispositionFieldOptions(): array\n {\n $options = [];\n\n $dispositions = $this->fetchCallDispositions();\n\n foreach ($dispositions as $disposition) {\n if ($disposition['deleted'] !== false) {\n continue;\n }\n\n $option['value'] = $disposition['id'];\n $option['id'] = $disposition['id'];\n $option['label'] = $disposition['label'];\n\n $options[] = $option;\n }\n\n return $options;\n }\n\n /**\n * @return array<CrmFieldOption>\n */\n public function fetchOpportunityFieldOptions(Field $field): array\n {\n if ($field->isStageField()) {\n return $this->fetchOpportunityPipelineStages();\n }\n\n if ($field->isPipelineField()) {\n return $this->fetchOpportunityPipelines();\n }\n\n return $this->fetchPropertyOptions('deals', $field->getCrmProviderId());\n }\n\n /**\n * @throws BadRequest\n * @throws HubspotException\n */\n public function makeRequest(string $endpoint, $method = 'GET', $payload = [], ?string $queryString = null)\n {\n $endpoint = self::BASE_URL . $endpoint;\n\n if ($method === 'GET') {\n $response = $this->getInstance()->getClient()?->request(\n method: $method,\n endpoint: $endpoint,\n query_string: $queryString\n );\n } else {\n $response = $this->getInstance()->getClient()->request($method, $endpoint, [\n 'json' => ($payload),\n ]);\n }\n\n $max = $response->getHeaderLine('X-HubSpot-RateLimit-Max'); // \"110\"\n $remaining = $response->getHeaderLine('X-HubSpot-RateLimit-Remaining'); // \"109\"\n $interval = $response->getHeaderLine('X-HubSpot-RateLimit-Interval-Milliseconds'); // \"10000\"\n $body = json_decode((string) $response->getBody(), true);\n\n \\Illuminate\\Support\\Facades\\Log::channel('custom_channel')->info('$max ' . PHP_EOL . print_r($max, true));\n \\Illuminate\\Support\\Facades\\Log::channel('custom_channel')->info('$remaining ' . PHP_EOL . print_r($remaining, true));\n \\Illuminate\\Support\\Facades\\Log::channel('custom_channel')->info('$interval ' . PHP_EOL . print_r($interval, true));\n \\Illuminate\\Support\\Facades\\Log::channel('custom_channel')->info('$body ' . PHP_EOL . print_r($body, true));\n\n return $response;\n }\n\n /**\n * @throws BadRequest\n * @throws HubspotException\n */\n public function createMeeting(array $payload): Response\n {\n $endpoint = '/crm/v3/objects/meetings';\n\n return $this->makeRequest($endpoint, 'POST', $payload);\n }\n\n /**\n * @throws BadRequest\n * @throws HubspotException\n */\n public function updateMeeting(string $meetingId, array $payload): Response\n {\n $endpoint = '/crm/v3/objects/meetings/' . $meetingId;\n\n return $this->makeRequest($endpoint, 'PATCH', $payload);\n }\n\n /**\n * @throws \\Exception\n */\n public function createNote(\n string $body,\n string $ownerId,\n int $timestamp,\n string $objectId,\n NoteObject $noteObject\n ): ?string {\n try {\n $noteInput = new SimplePublicObjectInput([\n 'properties' => [\n 'hs_note_body' => $body,\n 'hubspot_owner_id' => $ownerId,\n 'hs_timestamp' => $timestamp,\n ],\n ]);\n\n // Create note\n $note = $this->getNewInstance()->crm()->objects()->basicApi()->create('note', $noteInput);\n\n $this->getNewInstance()->crm()->objects()->associationsApi()->create(\n 'note',\n $note->getId(),\n $this->getNoteObject($noteObject),\n $objectId,\n $this->getNoteAssociationType($noteObject),\n );\n\n return $note->getId();\n } catch (\\Exception $e) {\n $this->log->error('[Hubspot] Failed to create note', [\n 'objectId' => $objectId,\n 'noteObject' => $noteObject->getObjectType(),\n 'reason' => $e->getMessage(),\n ]);\n\n \\Sentry::captureException($e);\n }\n\n return null;\n }\n\n public function updateEngagement(string $objectId, array $engagement, array $metadata): void\n {\n $this->getInstance()->engagements()->update($objectId, $engagement, $metadata);\n }\n\n public function getEngagementData(string $engagementId): array\n {\n $engagement = $this->getInstance()->engagements()->get($engagementId);\n\n return $engagement->toArray();\n }\n\n public function createEngagement(array $engagement, array $associations, array $metadata): Response\n {\n return $this->getInstance()\n ->engagements()\n ->create($engagement, $associations, $metadata);\n }\n\n public function isUnauthorizedException(\\Exception $e): bool\n {\n // Check for specific HubSpot API exception types first\n if ($e instanceof BadRequest) {\n // BadRequest can contain 401 status codes\n return $e->getCode() === 401;\n }\n\n // Check for HTTP client exceptions with status codes\n if ($e instanceof \\GuzzleHttp\\Exception\\RequestException && $e->hasResponse()) {\n $response = $e->getResponse();\n if ($response !== null) {\n return $response->getStatusCode() === 401;\n }\n }\n\n // Check for Guzzle HTTP exceptions\n if ($e instanceof \\GuzzleHttp\\Exception\\ClientException) {\n return $e->getCode() === 401;\n }\n\n // Fallback to string matching as last resort, but be more specific\n $message = strtolower($e->getMessage());\n\n return str_contains($message, '401 unauthorized') ||\n str_contains($message, 'http 401') ||\n str_contains($message, 'status code 401') ||\n (preg_match('/\\b401\\b/', $message) && str_contains($message, 'unauthorized'));\n }\n\n /**\n * Validates and refreshes the access token if needed before API requests.\n * This ensures long-running processes don't fail due to token expiration.\n *\n * @throws SocialAccountTokenInvalidException\n */\n public function ensureValidToken(): void\n {\n if ($this->oauthAccount === null) {\n return;\n }\n\n $newToken = $this->tokenManager->ensureValidToken($this->oauthAccount);\n if ($newToken !== null) {\n $this->accessToken = $newToken;\n }\n }\n\n public function getConfig()\n {\n return $this->config;\n }\n\n // returns only active (archived=false)\n public function getOwners(): array\n {\n return $this->getNewInstance()->crm()->owners()->getAll();\n }\n\n /**\n * @param bool $archived\n *\n * @return array<Owner>|[]\n */\n public function getOwnersArchived(bool $archived = true): array\n {\n $endpoint = '/crm/v3/owners';\n $queryParams = [\n 'archived' => $archived ? 'true' : 'false',\n ];\n $queryString = http_build_query($queryParams);\n\n $owners = [];\n\n try {\n $response = $this->makeRequest(endpoint: $endpoint, queryString: $queryString);\n $responseData = $response?->toArray();\n\n foreach ($responseData['results'] as $result) {\n try {\n $owners[] = Owner::create($result);\n } catch (Throwable $e) {\n $this->log->error('[HubSpot] Failed to process owner data', [\n 'result' => $result,\n 'error' => $e->getMessage(),\n ]);\n\n continue;\n }\n }\n } catch (Throwable $e) {\n $this->log->error('HubSpot] Failed to fetch owners', [\n 'archived' => $archived,\n 'error' => $e->getMessage(),\n ]);\n\n return [];\n }\n\n return $owners;\n }\n\n public function getMeeting(string $engagementId): ObjectWithAssociations\n {\n return $this->getNewInstance()->crm()->objects()->basicApi()\n ->getById('meeting', $engagementId, null, 'contact,company,deal');\n }\n\n public function deleteEngagement(string $engagementId): void\n {\n $this->getInstance()->engagements()->delete((int) $engagementId);\n }\n\n public function getAssociationsData(array $ids, string $fromObject, string $toObject): array\n {\n $associationData = [];\n $idChunks = array_chunk($ids, self::ASSOCIATIONS_BATCH_SIZE_LIMIT);\n\n foreach ($idChunks as $idChunk) {\n try {\n $batchInput = new \\HubSpot\\Client\\Crm\\Associations\\Model\\BatchInputPublicObjectId();\n $batchInput->setInputs(array_map(function ($id) {\n $publicObjectId = new \\HubSpot\\Client\\Crm\\Associations\\Model\\PublicObjectId();\n $publicObjectId->setId($id);\n\n return $publicObjectId;\n }, $idChunk));\n\n $associatedObjectsData = $this\n ->getNewInstance()\n ->crm()\n ->associations()\n ->batchApi()\n ->read($fromObject, $toObject, $batchInput);\n\n if ($associatedObjectsData instanceof \\HubSpot\\Client\\Crm\\Associations\\Model\\BatchResponsePublicAssociationMulti) {\n foreach ($associatedObjectsData->getResults() as $association) {\n $from = $association->getFrom()->getId();\n $toAssociations = $association->getTo();\n\n if (! empty($toAssociations)) {\n $associationData[$from] = array_map(function ($item) {\n return $item->getId();\n }, $toAssociations);\n }\n }\n }\n } catch (\\Exception $e) {\n $this->log->error('[Hubspot] Failed to fetch associations', [\n 'from_object' => $fromObject,\n 'to_object' => $toObject,\n 'reason' => $e->getMessage(),\n ]);\n }\n }\n\n return $associationData;\n }\n\n /**\n * @throws \\Exception\n */\n private function getNoteAssociationType(NoteObject $noteObject): string\n {\n return match($noteObject) {\n NoteObject::Opportunity => 'note_to_deal',\n NoteObject::Lead, NoteObject::Contact => 'note_to_contact', // or 'note_to_lead' if your portal supports it\n NoteObject::Account => 'note_to_company',\n NoteObject::Call, NoteObject::Event => throw new \\Exception('Not supported'),\n };\n }\n\n /**\n * @throws \\Exception\n */\n private function getNoteObject(NoteObject $noteObject): string\n {\n return match($noteObject) {\n NoteObject::Opportunity => 'deal',\n NoteObject::Lead, NoteObject::Contact => 'contact',\n NoteObject::Account => 'company',\n NoteObject::Call, NoteObject::Event => throw new \\Exception('Not supported'),\n };\n }\n\n public function addAssociations(string $objectType, string $associationType, array $payload): Response\n {\n $endpoint = \"/crm/v4/associations/$objectType/$associationType/batch/create\";\n\n return $this->makeRequest($endpoint, 'POST', $payload);\n }\n\n public function removeAssociations(string $objectType, string $associationType, array $payload): Response\n {\n $endpoint = \"/crm/v4/associations/$objectType/$associationType/batch/archive\";\n\n return $this->makeRequest($endpoint, 'POST', $payload);\n }\n}","role_description":"text entry area","is_enabled":true,"is_focused":true,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"Project","depth":3,"on_screen":false,"role_description":"text"},{"role":"AXButton","text":"Project","depth":3,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"New File or Directory…","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.018055556,"height":0.026666667},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Expand Selected","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.018055556,"height":0.026666667},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Collapse All","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.018055556,"height":0.026666667},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Options","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.018055556,"height":0.026666667},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Hide","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.018055556,"height":0.026666667},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false}]...
|
860006888846957654
|
2072772790585792612
|
click
|
accessibility
|
NULL
|
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
Execute
Explain Plan
Browse Query History
View Parameters
Open Query Execution Settings…
In-Editor Results
Tx: Auto
Cancel Running Statements
Playground
jiminny
Sync Changes
Hide This Notification
Code changed:
Hide
6
1
6
Previous Highlighted Error
Next Highlighted Error
# [PASSWORD_DOTS] HS [PASSWORD_DOTS]
select * from teams where id = 2; # 2
select * from features; # 2
select * from team_features where team_id = 2; # 2
select * from crm_configurations where id = 2; # 2
select * from users where team_id = 2; #
select * from playbooks where team_id = 2; # event 38
select * from playbook_categories where playbook_id = 38; #
SELECT * FROM activities WHERE crm_configuration_id = 2 and crm_provider_id is not null order by id desc;
[URL_WITH_CREDENTIALS] CrmFieldOption array{id:string, label:string, value?:string}
*/
class Client extends BaseClient implements HubspotClientInterface
{
public const string MIN_API_VERSION = '2';
public const string BASE_URL = '[URL_WITH_CREDENTIALS] T
* @param callable(): T $apiCall
* @return T
*
* @throws RateLimitException
*/
private function executeRequest(callable $apiCall)
{
if (! $this->rateLimiter->canMakeRequest($this->config)) {
$retryAfter = $this->rateLimiter->requestAvailableIn($this->config);
$this->log->warning('[Hubspot] Rate limit exceeded, deferring request', [
'team_id' => $this->config->team_id,
'config_id' => $this->config->getId(),
'retry_after' => $retryAfter,
]);
throw new RateLimitException(
'Hubspot rate limit reached for configuration ' . $this->config->getId(),
$retryAfter,
);
}
$this->rateLimiter->incrementRequestCount($this->config);
try {
return $apiCall();
} catch (Throwable $e) {
if ($this->isHubspotRateLimit($e)) {
$retryAfter = $this->parseRetryAfter($e);
$this->log->warning('[Hubspot] Received 429 from API', [
'team_id' => $this->config->team_id,
'config_id' => $this->config->getId(),
'retry_after' => $retryAfter,
'reason' => $e->getMessage(),
]);
throw new RateLimitException('Hubspot returned 429', $retryAfter, $e);
}
throw $e;
}
}
private function isHubspotRateLimit(Throwable $e): bool
{
return method_exists($e, 'getCode') && (int) $e->getCode() === 429;
}
private function parseRetryAfter(Throwable $e): int
{
if (method_exists($e, 'getResponseHeaders')) {
$headers = $e->getResponseHeaders() ?: [];
$value = $headers['Retry-After'] ?? $headers['retry-after'] ?? null;
if (is_array($value)) {
$value = $value[0] ?? null;
}
if (is_numeric($value)) {
return (int) $value;
}
}
return 10;
}
public function getMinimumApiVersion(): string
{
return self::MIN_API_VERSION;
}
public function getInstance(): Factory
{
return new Factory([
'key' => $this->accessToken,
'oauth2' => true,
'base_url' => $this->baseUrl,
]);
}
public function getNewInstance(): Discovery
{
return \HubSpot\Factory::createWithAccessToken($this->accessToken);
}
/**
* Secondly and daily limits for Hubspot API
*
* Product Tier: Free & Starter | Professional & Enterprise | API add-on (any tier)
* Burst: 100/10 seconds | 150/10 seconds | 200/10 seconds
* Daily: 250,000 | 500,000 | 1,000,000
*
* Official documentation states: The search endpoints are rate limited to five requests per second.
* Since with 5 RPS were still hitting secondly rate limits we lowered it to 4
*/
public function getPaginatedData(array $payload, string $type, int $offset = 0): array
{
$total = 0;
$lastId = null;
$rows = [];
foreach ($this->getPaginatedDataGenerator($payload, $type, $offset, $total, $lastId) as $row) {
$rows[] = $row;
}
return ['results' => $rows, 'total' => $total, 'last_record' => $lastId];
}
/**
* @throws HubspotException
* @throws SocialAccountTokenInvalidException
* @throws BadRequest
*/
public function getPaginatedDataGenerator(
array $payload,
string $type,
int $offset = 0,
int &$total = 0,
?string &$lastRecordId = null
): \Generator {
return $this->paginationService->getPaginatedDataGenerator(
$this,
$payload,
$type,
$offset,
$total,
$lastRecordId
);
}
/**
* @throws DealApiException
* @throws CrmException
*/
public function getOpportunityById(string $crmId, array $fields): array
{
try {
$deal = $this->executeRequest(fn () => $this->getNewInstance()->crm()->deals()->basicApi()->getById(
$crmId,
implode(',', $fields),
'companies,contacts'
));
} catch (DealApiException $e) {
$this->log->info('[Hubspot] Failed to fetch opportunity', [
'crm_id' => $crmId,
'reason' => $e->getMessage(),
]);
throw $e;
}
if (! $deal instanceof DealWithAssociations) {
throw new CrmException('Deal not found');
}
return [
'id' => $deal->getId(),
'properties' => $deal->getProperties(),
'associations' => $deal->getAssociations(),
];
}
/**
* Generic batch read method for HubSpot objects
*
* @param string $objectType The object type ('deals', 'companies', 'contacts')
* @param array<string> $crmIds Array of HubSpot object IDs (max 100)
* @param array<string> $fields Array of property names to fetch
*
* @return array<string, array> Array keyed by CRM ID with object data
*/
private function batchReadObjects(string $objectType, array $crmIds, array $fields): array
{
if (empty($crmIds)) {
return [];
}
$this->validateBatchSize($objectType, $crmIds);
$this->ensureValidToken();
try {
$batchConfig = $this->createBatchConfiguration($objectType);
$batchReadRequest = $this->prepareBatchRequest($batchConfig, $crmIds, $fields);
$response = $batchConfig['api']->read($batchReadRequest);
$this->validateApiResponse($response, $objectType);
$results = $this->processApiResults($response);
$this->logBatchResults($objectType, $crmIds, $results);
return $results;
} catch (\Throwable $e) {
$this->handleBatchError($e, $objectType, $crmIds);
}
}
private function validateBatchSize(string $objectType, array $crmIds): void
{
if (count($crmIds) > 100) {
throw new \InvalidArgumentException("Batch size cannot exceed 100 {$objectType}");
}
}
private function createBatchConfiguration(string $objectType): array
{
$configurations = [
'deals' => [
'batchReadRequest' => new \HubSpot\Client\Crm\Deals\Model\BatchReadInputSimplePublicObjectId(),
'inputClass' => \HubSpot\Client\Crm\Deals\Model\SimplePublicObjectId::class,
'api' => $this->getNewInstance()->crm()->deals()->batchApi(),
],
'companies' => [
'batchReadRequest' => new \HubSpot\Client\Crm\Companies\Model\BatchReadInputSimplePublicObjectId(),
'inputClass' => \HubSpot\Client\Crm\Companies\Model\SimplePublicObjectId::class,
'api' => $this->getNewInstance()->crm()->companies()->batchApi(),
],
'contacts' => [
'batchReadRequest' => new \HubSpot\Client\Crm\Contacts\Model\BatchReadInputSimplePublicObjectId(),
'inputClass' => \HubSpot\Client\Crm\Contacts\Model\SimplePublicObjectId::class,
'api' => $this->getNewInstance()->crm()->contacts()->batchApi(),
],
];
if (! isset($configurations[$objectType])) {
throw new \InvalidArgumentException("Unsupported object type: {$objectType}");
}
return $configurations[$objectType];
}
private function prepareBatchRequest(array $batchConfig, array $crmIds, array $fields): object
{
$batchReadRequest = $batchConfig['batchReadRequest'];
$inputClass = $batchConfig['inputClass'];
$inputs = array_map(function ($crmId) use ($inputClass) {
$input = new $inputClass();
$input->setId($crmId);
return $input;
}, $crmIds);
$batchReadRequest->setInputs($inputs);
$batchReadRequest->setProperties($fields);
return $batchReadRequest;
}
private function validateApiResponse($response, string $objectType): void
{
if (! $response) {
throw new CrmException("HubSpot API returned null response for {$objectType} batch read");
}
}
private function processApiResults($response): array
{
$results = [];
$responseResults = $response->getResults();
if ($responseResults) {
foreach ($responseResults as $object) {
if ($object && $object->getId()) {
$results[$object->getId()] = [
'id' => $object->getId(),
'properties' => $object->getProperties() ?: [],
];
}
}
}
return $results;
}
private function logBatchResults(string $objectType, array $crmIds, array $results): void
{
$this->log->info("[HubSpot] Batch fetched {$objectType}", [
'requested_count' => count($crmIds),
'returned_count' => count($results),
'crm_ids' => $crmIds,
]);
}
private function handleBatchError(\Throwable $e, string $objectType, array $crmIds): void
{
$errorMessage = $e->getMessage() ?: 'Unknown error';
$errorTrace = $e->getTraceAsString() ?: 'No trace available';
$this->log->error("[HubSpot] Failed to batch fetch {$objectType}", [
'crm_ids' => $crmIds,
'error' => $errorMessage,
'trace' => $errorTrace,
]);
throw new CrmException("Failed to batch fetch {$objectType}: " . $errorMessage);
}
/**
* Batch read multiple opportunities by their CRM IDs
*
* @param array<string> $crmIds Array of HubSpot deal IDs (max 100)
* @param array<string> $fields Array of property names to fetch
*
* @return array<string, array> Array keyed by CRM ID with opportunity data
*/
public function getOpportunitiesByIds(array $crmIds, array $fields): array
{
return $this->batchReadObjects('deals', $crmIds, $fields);
}
/**
* Batch read multiple companies by their CRM IDs
*
* @param array<string> $crmIds Array of HubSpot company IDs (max 100)
* @param array<string> $fields Array of property names to fetch
*
* @return array<string, array> Array keyed by CRM ID with company data
*/
public function getCompaniesByIds(array $crmIds, array $fields): array
{
return $this->batchReadObjects('companies', $crmIds, $fields);
}
/**
* Batch read multiple contacts by their CRM IDs
*
* @param array<string> $crmIds Array of HubSpot contact IDs (max 100)
* @param array<string> $fields Array of property names to fetch
*
* @return array<string, array> Array keyed by CRM ID with contact data
*/
public function getContactsByIds(array $crmIds, array $fields): array
{
return $this->batchReadObjects('contacts', $crmIds, $fields);
}
/**
* @throws CompanyApiException
* @throws CrmException
*/
public function getAccountById(string $crmId, array $fields): array
{
try {
$company = $this->getNewInstance()->crm()->companies()->basicApi()->getById(
$crmId,
implode(',', $fields),
);
} catch (CompanyApiException $e) {
$this->log->info('[Hubspot] Failed to fetch account', [
'crm_id' => $crmId,
'reason' => $e->getMessage(),
]);
throw $e;
}
if (! $company instanceof CompaniesWithAssociations) {
throw new CrmException('Account not found');
}
return [
'id' => $company->getId(),
'properties' => $company->getProperties(),
];
}
/**
* @throws ContactApiException
* @throws CrmException
*/
public function getContactById(string $crmId, array $fields): array
{
try {
$contact = $this->getNewInstance()->crm()->contacts()->basicApi()->getById(
$crmId,
implode(',', $fields)
);
} catch (ContactApiException $e) {
$this->log->info('[Hubspot] Failed to fetch contact', [
'crm_id' => $crmId,
'reason' => $e->getMessage(),
]);
throw $e;
}
if (! $contact instanceof ContactsWithAssociations) {
throw new CrmException('Contact not found');
}
return [
'id' => $contact->getId(),
'properties' => $contact->getProperties(),
];
}
/**
* This is email search request that Hubspot offers as GET (more generous quota)
*/
public function getContactByEmail(string $email, array $fields = []): array
{
try {
$contact = $this->getNewInstance()->crm()->contacts()->basicApi()->getById(
$email,
implode(',', $fields),
null,
false,
'email'
);
return [
'id' => $contact->getId(),
'properties' => $contact->getProperties(),
];
} catch (ContactApiException $e) {
$this->log->info('[Hubspot] Failed to fetch contact', [
'email' => $email,
'reason' => $e->getMessage(),
]);
return [];
}
}
/**
* @throws CrmException
*/
public function fetchProperty(string $objectType, string $propertyId): Property
{
$result = $this->getNewInstance()->crm()->properties()->coreApi()->getByName($objectType, $propertyId);
if (! $result instanceof Property) {
$this->log->error('[Hubspot] Failed to fetch property', [
'object_type' => $objectType,
'property_id' => $propertyId,
'reason' => $result->getMessage(),
]);
throw new CrmException('Failed to fetch property');
}
return $result;
}
/**
* @return array<CrmFieldOption>
*/
public function fetchPropertyOptions(string $objectType, string $propertyId): array
{
/** @var array<CrmFieldOption> */
return $this->fetchProperty($objectType, $propertyId)->getOptions();
}
/**
* @return array<array{id:string, label:string, deleted:bool}>
*/
public function fetchCallDispositions(): array
{
/** @var Response $response */
$response = $this->getInstance()->engagements()->getCallDispositions();
/**
* @var array<array{
* id:string,
* label:string,
* deleted: bool
* }>
*/
return $response->toArray();
}
/**
* @return array<CrmFieldOption>
*/
public function fetchOpportunityPipelineStages(): array
{
$stages = [];
$apiResponse = $this->getNewInstance()->crm()->pipelines()->pipelinesApi()->getAll('deals');
if ($apiResponse instanceof Error) {
$this->log->error('[Hubspot] Failed to fetch opportunity pipelines', [
'reason' => $apiResponse->getMessage(),
]);
return [];
}
foreach ($apiResponse->getResults() as $pipeline) {
$pipelineStages = array_map(
static function (PipelineStage $stage) {
return [
'id' => $stage->getId(),
'label' => $stage->getLabel(),
];
},
$pipeline->getStages()
);
$stages = array_merge($stages, $pipelineStages);
}
return $stages;
}
public function fetchOpportunityPipelines(): array
{
$pipelines = [];
try {
$apiResponse = $this->makeRequest('/crm/v3/pipelines/deals');
} catch (\Exception $e) {
$this->log->info('[Hubspot] Failed to fetch opportunity pipelines', [
'reason' => $e->getMessage(),
]);
return [];
}
$response = $apiResponse->toArray();
foreach ($response['results'] as $pipeline) {
$pipelines[] = [
'id' => $pipeline['id'],
'label' => $pipeline['label'],
];
}
return $pipelines;
}
/**
* @return array<CrmFieldOption>
*/
public function fetchMeetingOutcomeFieldOptions(Field $field): array
{
return $field->getCrmProviderId() === 'meetingOutcome'
? $this->fetchMeetingOutcomeTypes()
: $this->fetchCallActivityTypes();
}
public function fetchMeetingOutcomeTypes(): array
{
return $this->extractMeetingTypeOptions(
'[URL_WITH_CREDENTIALS] Response $response */
$response = $this->getInstance()
->getClient()
->request('GET', $endpoint);
/**
* @var array<array{
* value: string,
* label: string,
* displayOrder: int
* }> $optionData
*/
$optionData = $response->toArray()['options'] ?? [];
$options = [];
foreach ($optionData as $item) {
$options[] = [
'id' => $item['value'],
'value' => $item['value'],
'label' => $item['label'],
'display_order' => $item['displayOrder'],
];
}
return $options;
}
/**
* @return array<CrmFieldOption>
*/
public function fetchDispositionFieldOptions(): array
{
$options = [];
$dispositions = $this->fetchCallDispositions();
foreach ($dispositions as $disposition) {
if ($disposition['deleted'] !== false) {
continue;
}
$option['value'] = $disposition['id'];
$option['id'] = $disposition['id'];
$option['label'] = $disposition['label'];
$options[] = $option;
}
return $options;
}
/**
* @return array<CrmFieldOption>
*/
public function fetchOpportunityFieldOptions(Field $field): array
{
if ($field->isStageField()) {
return $this->fetchOpportunityPipelineStages();
}
if ($field->isPipelineField()) {
return $this->fetchOpportunityPipelines();
}
return $this->fetchPropertyOptions('deals', $field->getCrmProviderId());
}
/**
* @throws BadRequest
* @throws HubspotException
*/
public function makeRequest(string $endpoint, $method = 'GET', $payload = [], ?string $queryString = null)
{
$endpoint = self::BASE_URL . $endpoint;
if ($method === 'GET') {
$response = $this->getInstance()->getClient()?->request(
method: $method,
endpoint: $endpoint,
query_string: $queryString
);
} else {
$response = $this->getInstance()->getClient()->request($method, $endpoint, [
'json' => ($payload),
]);
}
$max = $response->getHeaderLine('X-HubSpot-RateLimit-Max'); // "110"
$remaining = $response->getHeaderLine('X-HubSpot-RateLimit-Remaining'); // "109"
$interval = $response->getHeaderLine('X-HubSpot-RateLimit-Interval-Milliseconds'); // "10000"
$body = json_decode((string) $response->getBody(), true);
\Illuminate\Support\Facades\Log::channel('custom_channel')->info('$max ' . PHP_EOL . print_r($max, true));
\Illuminate\Support\Facades\Log::channel('custom_channel')->info('$remaining ' . PHP_EOL . print_r($remaining, true));
\Illuminate\Support\Facades\Log::channel('custom_channel')->info('$interval ' . PHP_EOL . print_r($interval, true));
\Illuminate\Support\Facades\Log::channel('custom_channel')->info('$body ' . PHP_EOL . print_r($body, true));
return $response;
}
/**
* @throws BadRequest
* @throws HubspotException
*/
public function createMeeting(array $payload): Response
{
$endpoint = '/crm/v3/objects/meetings';
return $this->makeRequest($endpoint, 'POST', $payload);
}
/**
* @throws BadRequest
* @throws HubspotException
*/
public function updateMeeting(string $meetingId, array $payload): Response
{
$endpoint = '/crm/v3/objects/meetings/' . $meetingId;
return $this->makeRequest($endpoint, 'PATCH', $payload);
}
/**
* @throws \Exception
*/
public function createNote(
string $body,
string $ownerId,
int $timestamp,
string $objectId,
NoteObject $noteObject
): ?string {
try {
$noteInput = new SimplePublicObjectInput([
'properties' => [
'hs_note_body' => $body,
'hubspot_owner_id' => $ownerId,
'hs_timestamp' => $timestamp,
],
]);
// Create note
$note = $this->getNewInstance()->crm()->objects()->basicApi()->create('note', $noteInput);
$this->getNewInstance()->crm()->objects()->associationsApi()->create(
'note',
$note->getId(),
$this->getNoteObject($noteObject),
$objectId,
$this->getNoteAssociationType($noteObject),
);
return $note->getId();
} catch (\Exception $e) {
$this->log->error('[Hubspot] Failed to create note', [
'objectId' => $objectId,
'noteObject' => $noteObject->getObjectType(),
'reason' => $e->getMessage(),
]);
\Sentry::captureException($e);
}
return null;
}
public function updateEngagement(string $objectId, array $engagement, array $metadata): void
{
$this->getInstance()->engagements()->update($objectId, $engagement, $metadata);
}
public function getEngagementData(string $engagementId): array
{
$engagement = $this->getInstance()->engagements()->get($engagementId);
return $engagement->toArray();
}
public function createEngagement(array $engagement, array $associations, array $metadata): Response
{
return $this->getInstance()
->engagements()
->create($engagement, $associations, $metadata);
}
public function isUnauthorizedException(\Exception $e): bool
{
// Check for specific HubSpot API exception types first
if ($e instanceof BadRequest) {
// BadRequest can contain 401 status codes
return $e->getCode() === 401;
}
// Check for HTTP client exceptions with status codes
if ($e instanceof \GuzzleHttp\Exception\RequestException && $e->hasResponse()) {
$response = $e->getResponse();
if ($response !== null) {
return $response->getStatusCode() === 401;
}
}
// Check for Guzzle HTTP exceptions
if ($e instanceof \GuzzleHttp\Exception\ClientException) {
return $e->getCode() === 401;
}
// Fallback to string matching as last resort, but be more specific
$message = strtolower($e->getMessage());
return str_contains($message, '401 unauthorized') ||
str_contains($message, 'http 401') ||
str_contains($message, 'status code 401') ||
(preg_match('/\b401\b/', $message) && str_contains($message, 'unauthorized'));
}
/**
* Validates and refreshes the access token if needed before API requests.
* This ensures long-running processes don't fail due to token expiration.
*
* @throws SocialAccountTokenInvalidException
*/
public function ensureValidToken(): void
{
if ($this->oauthAccount === null) {
return;
}
$newToken = $this->tokenManager->ensureValidToken($this->oauthAccount);
if ($newToken !== null) {
$this->accessToken = $newToken;
}
}
public function getConfig()
{
return $this->config;
}
// returns only active (archived=false)
public function getOwners(): array
{
return $this->getNewInstance()->crm()->owners()->getAll();
}
/**
* @param bool $archived
*
* @return array<Owner>|[]
*/
public function getOwnersArchived(bool $archived = true): array
{
$endpoint = '/crm/v3/owners';
$queryParams = [
'archived' => $archived ? 'true' : 'false',
];
$queryString = http_build_query($queryParams);
$owners = [];
try {
$response = $this->makeRequest(endpoint: $endpoint, queryString: $queryString);
$responseData = $response?->toArray();
foreach ($responseData['results'] as $result) {
try {
$owners[] = Owner::create($result);
} catch (Throwable $e) {
$this->log->error('[HubSpot] Failed to process owner data', [
'result' => $result,
'error' => $e->getMessage(),
]);
continue;
}
}
} catch (Throwable $e) {
$this->log->error('HubSpot] Failed to fetch owners', [
'archived' => $archived,
'error' => $e->getMessage(),
]);
return [];
}
return $owners;
}
public function getMeeting(string $engagementId): ObjectWithAssociations
{
return $this->getNewInstance()->crm()->objects()->basicApi()
->getById('meeting', $engagementId, null, 'contact,company,deal');
}
public function deleteEngagement(string $engagementId): void
{
$this->getInstance()->engagements()->delete((int) $engagementId);
}
public function getAssociationsData(array $ids, string $fromObject, string $toObject): array
{
$associationData = [];
$idChunks = array_chunk($ids, self::ASSOCIATIONS_BATCH_SIZE_LIMIT);
foreach ($idChunks as $idChunk) {
try {
$batchInput = new \HubSpot\Client\Crm\Associations\Model\BatchInputPublicObjectId();
$batchInput->setInputs(array_map(function ($id) {
$publicObjectId = new \HubSpot\Client\Crm\Associations\Model\PublicObjectId();
$publicObjectId->setId($id);
return $publicObjectId;
}, $idChunk));
$associatedObjectsData = $this
->getNewInstance()
->crm()
->associations()
->batchApi()
->read($fromObject, $toObject, $batchInput);
if ($associatedObjectsData instanceof \HubSpot\Client\Crm\Associations\Model\BatchResponsePublicAssociationMulti) {
foreach ($associatedObjectsData->getResults() as $association) {
$from = $association->getFrom()->getId();
$toAssociations = $association->getTo();
if (! empty($toAssociations)) {
$associationData[$from] = array_map(function ($item) {
return $item->getId();
}, $toAssociations);
}
}
}
} catch (\Exception $e) {
$this->log->error('[Hubspot] Failed to fetch associations', [
'from_object' => $fromObject,
'to_object' => $toObject,
'reason' => $e->getMessage(),
]);
}
}
return $associationData;
}
/**
* @throws \Exception
*/
private function getNoteAssociationType(NoteObject $noteObject): string
{
return match($noteObject) {
NoteObject::Opportunity => 'note_to_deal',
NoteObject::Lead, NoteObject::Contact => 'note_to_contact', // or 'note_to_lead' if your portal supports it
NoteObject::Account => 'note_to_company',
NoteObject::Call, NoteObject::Event => throw new \Exception('Not supported'),
};
}
/**
* @throws \Exception
*/
private function getNoteObject(NoteObject $noteObject): string
{
return match($noteObject) {
NoteObject::Opportunity => 'deal',
NoteObject::Lead, NoteObject::Contact => 'contact',
NoteObject::Account => 'company',
NoteObject::Call, NoteObject::Event => throw new \Exception('Not supported'),
};
}
public function addAssociations(string $objectType, string $associationType, array $payload): Response
{
$endpoint = "/crm/v4/associations/$objectType/$associationType/batch/create";
return $this->makeRequest($endpoint, 'POST', $payload);
}
public function removeAssociations(string $objectType, string $associationType, array $payload): Response
{
$endpoint = "/crm/v4/associations/$objectType/$associationType/batch/archive";
return $this->makeRequest($endpoint, 'POST', $payload);
}
}
Project
Project
New File or Directory…
Expand Selected
Collapse All
Options
Hide...
|
NULL
|
NULL
|
NULL
|
NULL
|
|
2691
|
112
|
18
|
2026-05-07T11:35:47.276872+00:00
|
/Users/lukas/.screenpipe/data/data/2026-05-07/1778 /Users/lukas/.screenpipe/data/data/2026-05-07/1778153747276_m2.jpg...
|
PhpStorm
|
faVsco.js – Hubspot/Client.php
|
True
|
NULL
|
monitor_2
|
NULL
|
NULL
|
NULL
|
NULL
|
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
Execute
Explain Plan
Browse Query History
View Parameters
Open Query Execution Settings…
In-Editor Results
Tx: Auto
Cancel Running Statements
Playground
jiminny
Sync Changes
Hide This Notification
Code changed:
Hide
6
1
6
Previous Highlighted Error
Next Highlighted Error
# [PASSWORD_DOTS] HS [PASSWORD_DOTS]
select * from teams where id = 2; # 2
select * from features; # 2
select * from team_features where team_id = 2; # 2
select * from crm_configurations where id = 2; # 2
select * from users where team_id = 2; #
select * from playbooks where team_id = 2; # event 38
select * from playbook_categories where playbook_id = 38; #
SELECT * FROM activities WHERE crm_configuration_id = 2 and crm_provider_id is not null order by id desc;
[URL_WITH_CREDENTIALS] CrmFieldOption array{id:string, label:string, value?:string}
*/
class Client extends BaseClient implements HubspotClientInterface
{
public const string MIN_API_VERSION = '2';
public const string BASE_URL = '[URL_WITH_CREDENTIALS] T
* @param callable(): T $apiCall
* @return T
*
* @throws RateLimitException
*/
private function executeRequest(callable $apiCall)
{
if (! $this->rateLimiter->canMakeRequest($this->config)) {
$retryAfter = $this->rateLimiter->requestAvailableIn($this->config);
$this->log->warning('[Hubspot] Rate limit exceeded, deferring request', [
'team_id' => $this->config->team_id,
'config_id' => $this->config->getId(),
'retry_after' => $retryAfter,
]);
throw new RateLimitException(
'Hubspot rate limit reached for configuration ' . $this->config->getId(),
$retryAfter,
);
}
$this->rateLimiter->incrementRequestCount($this->config);
try {
return $apiCall();
} catch (Throwable $e) {
if ($this->isHubspotRateLimit($e)) {
$retryAfter = $this->parseRetryAfter($e);
$this->log->warning('[Hubspot] Received 429 from API', [
'team_id' => $this->config->team_id,
'config_id' => $this->config->getId(),
'retry_after' => $retryAfter,
'reason' => $e->getMessage(),
]);
throw new RateLimitException('Hubspot returned 429', $retryAfter, $e);
}
throw $e;
}
}
private function isHubspotRateLimit(Throwable $e): bool
{
return method_exists($e, 'getCode') && (int) $e->getCode() === 429;
}
private function parseRetryAfter(Throwable $e): int
{
if (method_exists($e, 'getResponseHeaders')) {
$headers = $e->getResponseHeaders() ?: [];
$value = $headers['Retry-After'] ?? $headers['retry-after'] ?? null;
if (is_array($value)) {
$value = $value[0] ?? null;
}
if (is_numeric($value)) {
return (int) $value;
}
}
return 10;
}
public function getMinimumApiVersion(): string
{
return self::MIN_API_VERSION;
}
public function getInstance(): Factory
{
return new Factory([
'key' => $this->accessToken,
'oauth2' => true,
'base_url' => $this->baseUrl,
]);
}
public function getNewInstance(): Discovery
{
return \HubSpot\Factory::createWithAccessToken($this->accessToken);
}
/**
* Secondly and daily limits for Hubspot API
*
* Product Tier: Free & Starter | Professional & Enterprise | API add-on (any tier)
* Burst: 100/10 seconds | 150/10 seconds | 200/10 seconds
* Daily: 250,000 | 500,000 | 1,000,000
*
* Official documentation states: The search endpoints are rate limited to five requests per second.
* Since with 5 RPS were still hitting secondly rate limits we lowered it to 4
*/
public function getPaginatedData(array $payload, string $type, int $offset = 0): array
{
$total = 0;
$lastId = null;
$rows = [];
foreach ($this->getPaginatedDataGenerator($payload, $type, $offset, $total, $lastId) as $row) {
$rows[] = $row;
}
return ['results' => $rows, 'total' => $total, 'last_record' => $lastId];
}
/**
* @throws HubspotException
* @throws SocialAccountTokenInvalidException
* @throws BadRequest
*/
public function getPaginatedDataGenerator(
array $payload,
string $type,
int $offset = 0,
int &$total = 0,
?string &$lastRecordId = null
): \Generator {
return $this->paginationService->getPaginatedDataGenerator(
$this,
$payload,
$type,
$offset,
$total,
$lastRecordId
);
}
/**
* @throws DealApiException
* @throws CrmException
*/
public function getOpportunityById(string $crmId, array $fields): array
{
try {
$deal = $this->executeRequest(fn () => $this->getNewInstance()->crm()->deals()->basicApi()->getById(
$crmId,
implode(',', $fields),
'companies,contacts'
));
} catch (DealApiException $e) {
$this->log->info('[Hubspot] Failed to fetch opportunity', [
'crm_id' => $crmId,
'reason' => $e->getMessage(),
]);
throw $e;
}
if (! $deal instanceof DealWithAssociations) {
throw new CrmException('Deal not found');
}
return [
'id' => $deal->getId(),
'properties' => $deal->getProperties(),
'associations' => $deal->getAssociations(),
];
}
/**
* Generic batch read method for HubSpot objects
*
* @param string $objectType The object type ('deals', 'companies', 'contacts')
* @param array<string> $crmIds Array of HubSpot object IDs (max 100)
* @param array<string> $fields Array of property names to fetch
*
* @return array<string, array> Array keyed by CRM ID with object data
*/
private function batchReadObjects(string $objectType, array $crmIds, array $fields): array
{
if (empty($crmIds)) {
return [];
}
$this->validateBatchSize($objectType, $crmIds);
$this->ensureValidToken();
try {
$batchConfig = $this->createBatchConfiguration($objectType);
$batchReadRequest = $this->prepareBatchRequest($batchConfig, $crmIds, $fields);
$response = $batchConfig['api']->read($batchReadRequest);
$this->validateApiResponse($response, $objectType);
$results = $this->processApiResults($response);
$this->logBatchResults($objectType, $crmIds, $results);
return $results;
} catch (\Throwable $e) {
$this->handleBatchError($e, $objectType, $crmIds);
}
}
private function validateBatchSize(string $objectType, array $crmIds): void
{
if (count($crmIds) > 100) {
throw new \InvalidArgumentException("Batch size cannot exceed 100 {$objectType}");
}
}
private function createBatchConfiguration(string $objectType): array
{
$configurations = [
'deals' => [
'batchReadRequest' => new \HubSpot\Client\Crm\Deals\Model\BatchReadInputSimplePublicObjectId(),
'inputClass' => \HubSpot\Client\Crm\Deals\Model\SimplePublicObjectId::class,
'api' => $this->getNewInstance()->crm()->deals()->batchApi(),
],
'companies' => [
'batchReadRequest' => new \HubSpot\Client\Crm\Companies\Model\BatchReadInputSimplePublicObjectId(),
'inputClass' => \HubSpot\Client\Crm\Companies\Model\SimplePublicObjectId::class,
'api' => $this->getNewInstance()->crm()->companies()->batchApi(),
],
'contacts' => [
'batchReadRequest' => new \HubSpot\Client\Crm\Contacts\Model\BatchReadInputSimplePublicObjectId(),
'inputClass' => \HubSpot\Client\Crm\Contacts\Model\SimplePublicObjectId::class,
'api' => $this->getNewInstance()->crm()->contacts()->batchApi(),
],
];
if (! isset($configurations[$objectType])) {
throw new \InvalidArgumentException("Unsupported object type: {$objectType}");
}
return $configurations[$objectType];
}
private function prepareBatchRequest(array $batchConfig, array $crmIds, array $fields): object
{
$batchReadRequest = $batchConfig['batchReadRequest'];
$inputClass = $batchConfig['inputClass'];
$inputs = array_map(function ($crmId) use ($inputClass) {
$input = new $inputClass();
$input->setId($crmId);
return $input;
}, $crmIds);
$batchReadRequest->setInputs($inputs);
$batchReadRequest->setProperties($fields);
return $batchReadRequest;
}
private function validateApiResponse($response, string $objectType): void
{
if (! $response) {
throw new CrmException("HubSpot API returned null response for {$objectType} batch read");
}
}
private function processApiResults($response): array
{
$results = [];
$responseResults = $response->getResults();
if ($responseResults) {
foreach ($responseResults as $object) {
if ($object && $object->getId()) {
$results[$object->getId()] = [
'id' => $object->getId(),
'properties' => $object->getProperties() ?: [],
];
}
}
}
return $results;
}
private function logBatchResults(string $objectType, array $crmIds, array $results): void
{
$this->log->info("[HubSpot] Batch fetched {$objectType}", [
'requested_count' => count($crmIds),
'returned_count' => count($results),
'crm_ids' => $crmIds,
]);
}
private function handleBatchError(\Throwable $e, string $objectType, array $crmIds): void
{
$errorMessage = $e->getMessage() ?: 'Unknown error';
$errorTrace = $e->getTraceAsString() ?: 'No trace available';
$this->log->error("[HubSpot] Failed to batch fetch {$objectType}", [
'crm_ids' => $crmIds,
'error' => $errorMessage,
'trace' => $errorTrace,
]);
throw new CrmException("Failed to batch fetch {$objectType}: " . $errorMessage);
}
/**
* Batch read multiple opportunities by their CRM IDs
*
* @param array<string> $crmIds Array of HubSpot deal IDs (max 100)
* @param array<string> $fields Array of property names to fetch
*
* @return array<string, array> Array keyed by CRM ID with opportunity data
*/
public function getOpportunitiesByIds(array $crmIds, array $fields): array
{
return $this->batchReadObjects('deals', $crmIds, $fields);
}
/**
* Batch read multiple companies by their CRM IDs
*
* @param array<string> $crmIds Array of HubSpot company IDs (max 100)
* @param array<string> $fields Array of property names to fetch
*
* @return array<string, array> Array keyed by CRM ID with company data
*/
public function getCompaniesByIds(array $crmIds, array $fields): array
{
return $this->batchReadObjects('companies', $crmIds, $fields);
}
/**
* Batch read multiple contacts by their CRM IDs
*
* @param array<string> $crmIds Array of HubSpot contact IDs (max 100)
* @param array<string> $fields Array of property names to fetch
*
* @return array<string, array> Array keyed by CRM ID with contact data
*/
public function getContactsByIds(array $crmIds, array $fields): array
{
return $this->batchReadObjects('contacts', $crmIds, $fields);
}
/**
* @throws CompanyApiException
* @throws CrmException
*/
public function getAccountById(string $crmId, array $fields): array
{
try {
$company = $this->getNewInstance()->crm()->companies()->basicApi()->getById(
$crmId,
implode(',', $fields),
);
} catch (CompanyApiException $e) {
$this->log->info('[Hubspot] Failed to fetch account', [
'crm_id' => $crmId,
'reason' => $e->getMessage(),
]);
throw $e;
}
if (! $company instanceof CompaniesWithAssociations) {
throw new CrmException('Account not found');
}
return [
'id' => $company->getId(),
'properties' => $company->getProperties(),
];
}
/**
* @throws ContactApiException
* @throws CrmException
*/
public function getContactById(string $crmId, array $fields): array
{
try {
$contact = $this->getNewInstance()->crm()->contacts()->basicApi()->getById(
$crmId,
implode(',', $fields)
);
} catch (ContactApiException $e) {
$this->log->info('[Hubspot] Failed to fetch contact', [
'crm_id' => $crmId,
'reason' => $e->getMessage(),
]);
throw $e;
}
if (! $contact instanceof ContactsWithAssociations) {
throw new CrmException('Contact not found');
}
return [
'id' => $contact->getId(),
'properties' => $contact->getProperties(),
];
}
/**
* This is email search request that Hubspot offers as GET (more generous quota)
*/
public function getContactByEmail(string $email, array $fields = []): array
{
try {
$contact = $this->getNewInstance()->crm()->contacts()->basicApi()->getById(
$email,
implode(',', $fields),
null,
false,
'email'
);
return [
'id' => $contact->getId(),
'properties' => $contact->getProperties(),
];
} catch (ContactApiException $e) {
$this->log->info('[Hubspot] Failed to fetch contact', [
'email' => $email,
'reason' => $e->getMessage(),
]);
return [];
}
}
/**
* @throws CrmException
*/
public function fetchProperty(string $objectType, string $propertyId): Property
{
$result = $this->getNewInstance()->crm()->properties()->coreApi()->getByName($objectType, $propertyId);
if (! $result instanceof Property) {
$this->log->error('[Hubspot] Failed to fetch property', [
'object_type' => $objectType,
'property_id' => $propertyId,
'reason' => $result->getMessage(),
]);
throw new CrmException('Failed to fetch property');
}
return $result;
}
/**
* @return array<CrmFieldOption>
*/
public function fetchPropertyOptions(string $objectType, string $propertyId): array
{
/** @var array<CrmFieldOption> */
return $this->fetchProperty($objectType, $propertyId)->getOptions();
}
/**
* @return array<array{id:string, label:string, deleted:bool}>
*/
public function fetchCallDispositions(): array
{
/** @var Response $response */
$response = $this->getInstance()->engagements()->getCallDispositions();
/**
* @var array<array{
* id:string,
* label:string,
* deleted: bool
* }>
*/
return $response->toArray();
}
/**
* @return array<CrmFieldOption>
*/
public function fetchOpportunityPipelineStages(): array
{
$stages = [];
$apiResponse = $this->getNewInstance()->crm()->pipelines()->pipelinesApi()->getAll('deals');
if ($apiResponse instanceof Error) {
$this->log->error('[Hubspot] Failed to fetch opportunity pipelines', [
'reason' => $apiResponse->getMessage(),
]);
return [];
}
foreach ($apiResponse->getResults() as $pipeline) {
$pipelineStages = array_map(
static function (PipelineStage $stage) {
return [
'id' => $stage->getId(),
'label' => $stage->getLabel(),
];
},
$pipeline->getStages()
);
$stages = array_merge($stages, $pipelineStages);
}
return $stages;
}
public function fetchOpportunityPipelines(): array
{
$pipelines = [];
try {
$apiResponse = $this->makeRequest('/crm/v3/pipelines/deals');
} catch (\Exception $e) {
$this->log->info('[Hubspot] Failed to fetch opportunity pipelines', [
'reason' => $e->getMessage(),
]);
return [];
}
$response = $apiResponse->toArray();
foreach ($response['results'] as $pipeline) {
$pipelines[] = [
'id' => $pipeline['id'],
'label' => $pipeline['label'],
];
}
return $pipelines;
}
/**
* @return array<CrmFieldOption>
*/
public function fetchMeetingOutcomeFieldOptions(Field $field): array
{
return $field->getCrmProviderId() === 'meetingOutcome'
? $this->fetchMeetingOutcomeTypes()
: $this->fetchCallActivityTypes();
}
public function fetchMeetingOutcomeTypes(): array
{
return $this->extractMeetingTypeOptions(
'[URL_WITH_CREDENTIALS] Response $response */
$response = $this->getInstance()
->getClient()
->request('GET', $endpoint);
/**
* @var array<array{
* value: string,
* label: string,
* displayOrder: int
* }> $optionData
*/
$optionData = $response->toArray()['options'] ?? [];
$options = [];
foreach ($optionData as $item) {
$options[] = [
'id' => $item['value'],
'value' => $item['value'],
'label' => $item['label'],
'display_order' => $item['displayOrder'],
];
}
return $options;
}
/**
* @return array<CrmFieldOption>
*/
public function fetchDispositionFieldOptions(): array
{
$options = [];
$dispositions = $this->fetchCallDispositions();
foreach ($dispositions as $disposition) {
if ($disposition['deleted'] !== false) {
continue;
}
$option['value'] = $disposition['id'];
$option['id'] = $disposition['id'];
$option['label'] = $disposition['label'];
$options[] = $option;
}
return $options;
}
/**
* @return array<CrmFieldOption>
*/
public function fetchOpportunityFieldOptions(Field $field): array
{
if ($field->isStageField()) {
return $this->fetchOpportunityPipelineStages();
}
if ($field->isPipelineField()) {
return $this->fetchOpportunityPipelines();
}
return $this->fetchPropertyOptions('deals', $field->getCrmProviderId());
}
/**
* @throws BadRequest
* @throws HubspotException
*/
public function makeRequest(string $endpoint, $method = 'GET', $payload = [], ?string $queryString = null)
{
$endpoint = self::BASE_URL . $endpoint;
if ($method === 'GET') {
$response = $this->getInstance()->getClient()?->request(
method: $method,
endpoint: $endpoint,
query_string: $queryString
);
} else {
$response = $this->getInstance()->getClient()->request($method, $endpoint, [
'json' => ($payload),
]);
}
$max = $response->getHeaderLine('X-HubSpot-RateLimit-Max'); // "110"
$remaining = $response->getHeaderLine('X-HubSpot-RateLimit-Remaining'); // "109"
$interval = $response->getHeaderLine('X-HubSpot-RateLimit-Interval-Milliseconds'); // "10000"
$body = json_decode((string) $response->getBody(), true);
\Illuminate\Support\Facades\Log::channel('custom_channel')->info('$max ' . PHP_EOL . print_r($max, true));
\Illuminate\Support\Facades\Log::channel('custom_channel')->info('$remaining ' . PHP_EOL . print_r($remaining, true));
\Illuminate\Support\Facades\Log::channel('custom_channel')->info('$interval ' . PHP_EOL . print_r($interval, true));
\Illuminate\Support\Facades\Log::channel('custom_channel')->info('$body ' . PHP_EOL . print_r($body, true));
return $response;
}
/**
* @throws BadRequest
* @throws HubspotException
*/
public function createMeeting(array $payload): Response
{
$endpoint = '/crm/v3/objects/meetings';
return $this->makeRequest($endpoint, 'POST', $payload);
}
/**
* @throws BadRequest
* @throws HubspotException
*/
public function updateMeeting(string $meetingId, array $payload): Response
{
$endpoint = '/crm/v3/objects/meetings/' . $meetingId;
return $this->makeRequest($endpoint, 'PATCH', $payload);
}
/**
* @throws \Exception
*/
public function createNote(
string $body,
string $ownerId,
int $timestamp,
string $objectId,
NoteObject $noteObject
): ?string {
try {
$noteInput = new SimplePublicObjectInput([
'properties' => [
'hs_note_body' => $body,
'hubspot_owner_id' => $ownerId,
'hs_timestamp' => $timestamp,
],
]);
// Create note
$note = $this->getNewInstance()->crm()->objects()->basicApi()->create('note', $noteInput);
$this->getNewInstance()->crm()->objects()->associationsApi()->create(
'note',
$note->getId(),
$this->getNoteObject($noteObject),
$objectId,
$this->getNoteAssociationType($noteObject),
);
return $note->getId();
} catch (\Exception $e) {
$this->log->error('[Hubspot] Failed to create note', [
'objectId' => $objectId,
'noteObject' => $noteObject->getObjectType(),
'reason' => $e->getMessage(),
]);
\Sentry::captureException($e);
}
return null;
}
public function updateEngagement(string $objectId, array $engagement, array $metadata): void
{
$this->getInstance()->engagements()->update($objectId, $engagement, $metadata);
}
public function getEngagementData(string $engagementId): array
{
$engagement = $this->getInstance()->engagements()->get($engagementId);
return $engagement->toArray();
}
public function createEngagement(array $engagement, array $associations, array $metadata): Response
{
return $this->getInstance()
->engagements()
->create($engagement, $associations, $metadata);
}
public function isUnauthorizedException(\Exception $e): bool
{
// Check for specific HubSpot API exception types first
if ($e instanceof BadRequest) {
// BadRequest can contain 401 status codes
return $e->getCode() === 401;
}
// Check for HTTP client exceptions with status codes
if ($e instanceof \GuzzleHttp\Exception\RequestException && $e->hasResponse()) {
$response = $e->getResponse();
if ($response !== null) {
return $response->getStatusCode() === 401;
}
}
// Check for Guzzle HTTP exceptions
if ($e instanceof \GuzzleHttp\Exception\ClientException) {
return $e->getCode() === 401;
}
// Fallback to string matching as last resort, but be more specific
$message = strtolower($e->getMessage());
return str_contains($message, '401 unauthorized') ||
str_contains($message, 'http 401') ||
str_contains($message, 'status code 401') ||
(preg_match('/\b401\b/', $message) && str_contains($message, 'unauthorized'));
}
/**
* Validates and refreshes the access token if needed before API requests.
* This ensures long-running processes don't fail due to token expiration.
*
* @throws SocialAccountTokenInvalidException
*/
public function ensureValidToken(): void
{
if ($this->oauthAccount === null) {
return;
}
$newToken = $this->tokenManager->ensureValidToken($this->oauthAccount);
if ($newToken !== null) {
$this->accessToken = $newToken;
}
}
public function getConfig()
{
return $this->config;
}
// returns only active (archived=false)
public function getOwners(): array
{
return $this->getNewInstance()->crm()->owners()->getAll();
}
/**
* @param bool $archived
*
* @return array<Owner>|[]
*/
public function getOwnersArchived(bool $archived = true): array
{
$endpoint = '/crm/v3/owners';
$queryParams = [
'archived' => $archived ? 'true' : 'false',
];
$queryString = http_build_query($queryParams);
$owners = [];
try {
$response = $this->makeRequest(endpoint: $endpoint, queryString: $queryString);
$responseData = $response?->toArray();
foreach ($responseData['results'] as $result) {
try {
$owners[] = Owner::create($result);
} catch (Throwable $e) {
$this->log->error('[HubSpot] Failed to process owner data', [
'result' => $result,
'error' => $e->getMessage(),
]);
continue;
}
}
} catch (Throwable $e) {
$this->log->error('HubSpot] Failed to fetch owners', [
'archived' => $archived,
'error' => $e->getMessage(),
]);
return [];
}
return $owners;
}
public function getMeeting(string $engagementId): ObjectWithAssociations
{
return $this->getNewInstance()->crm()->objects()->basicApi()
->getById('meeting', $engagementId, null, 'contact,company,deal');
}
public function deleteEngagement(string $engagementId): void
{
$this->getInstance()->engagements()->delete((int) $engagementId);
}
public function getAssociationsData(array $ids, string $fromObject, string $toObject): array
{
$associationData = [];
$idChunks = array_chunk($ids, self::ASSOCIATIONS_BATCH_SIZE_LIMIT);
foreach ($idChunks as $idChunk) {
try {
$batchInput = new \HubSpot\Client\Crm\Associations\Model\BatchInputPublicObjectId();
$batchInput->setInputs(array_map(function ($id) {
$publicObjectId = new \HubSpot\Client\Crm\Associations\Model\PublicObjectId();
$publicObjectId->setId($id);
return $publicObjectId;
}, $idChunk));
$associatedObjectsData = $this
->getNewInstance()
->crm()
->associations()
->batchApi()
->read($fromObject, $toObject, $batchInput);
if ($associatedObjectsData instanceof \HubSpot\Client\Crm\Associations\Model\BatchResponsePublicAssociationMulti) {
foreach ($associatedObjectsData->getResults() as $association) {
$from = $association->getFrom()->getId();
$toAssociations = $association->getTo();
if (! empty($toAssociations)) {
$associationData[$from] = array_map(function ($item) {
return $item->getId();
}, $toAssociations);
}
}
}
} catch (\Exception $e) {
$this->log->error('[Hubspot] Failed to fetch associations', [
'from_object' => $fromObject,
'to_object' => $toObject,
'reason' => $e->getMessage(),
]);
}
}
return $associationData;
}
/**
* @throws \Exception
*/
private function getNoteAssociationType(NoteObject $noteObject): string
{
return match($noteObject) {
NoteObject::Opportunity => 'note_to_deal',
NoteObject::Lead, NoteObject::Contact => 'note_to_contact', // or 'note_to_lead' if your portal supports it
NoteObject::Account => 'note_to_company',
NoteObject::Call, NoteObject::Event => throw new \Exception('Not supported'),
};
}
/**
* @throws \Exception
*/
private function getNoteObject(NoteObject $noteObject): string
{
return match($noteObject) {
NoteObject::Opportunity => 'deal',
NoteObject::Lead, NoteObject::Contact => 'contact',
NoteObject::Account => 'company',
NoteObject::Call, NoteObject::Event => throw new \Exception('Not supported'),
};
}
public function addAssociations(string $objectType, string $associationType, array $payload): Response
{
$endpoint = "/crm/v4/associations/$objectType/$associationType/batch/create";
return $this->makeRequest($endpoint, 'POST', $payload);
}
public function removeAssociations(string $objectType, string $associationType, array $payload): Response
{
$endpoint = "/crm/v4/associations/$objectType/$associationType/batch/archive";
return $this->makeRequest($endpoint, 'POST', $payload);
}
}
Project
Project
New File or Directory…
Expand Selected
Collapse All
Options
Hide...
|
[{"role":"AXButton","text" [{"role":"AXButton","text":"Project: faVsco.js, menu","depth":5,"bounds":{"left":0.025930852,"top":0.019952115,"width":0.03856383,"height":0.025538707},"on_screen":true,"help_text":"~/jiminny/app","role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"master, menu","depth":5,"bounds":{"left":0.064494684,"top":0.019952115,"width":0.034242023,"height":0.025538707},"on_screen":true,"help_text":"Git Branch: master","role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Start Listening for PHP Debug Connections","depth":5,"bounds":{"left":0.8081782,"top":0.019952115,"width":0.011303191,"height":0.025538707},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"AskJiminnyReportActivityServiceTest","depth":6,"bounds":{"left":0.8234708,"top":0.019952115,"width":0.09208777,"height":0.025538707},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Run 'AskJiminnyReportActivityServiceTest'","depth":6,"bounds":{"left":0.9155585,"top":0.019952115,"width":0.011303191,"height":0.025538707},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Debug 'AskJiminnyReportActivityServiceTest'","depth":6,"bounds":{"left":0.9268617,"top":0.019952115,"width":0.011303191,"height":0.025538707},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"More Actions","depth":6,"bounds":{"left":0.9381649,"top":0.019952115,"width":0.011303191,"height":0.025538707},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"JetBrains AI","depth":5,"bounds":{"left":0.96609044,"top":0.019952115,"width":0.011303191,"height":0.025538707},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Search Everywhere","depth":5,"bounds":{"left":0.9773936,"top":0.019952115,"width":0.011303191,"height":0.025538707},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"IDE and Project Settings","depth":5,"bounds":{"left":0.9886968,"top":0.019952115,"width":0.011303186,"height":0.025538707},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Execute","depth":4,"bounds":{"left":0.38297874,"top":0.09896249,"width":0.008643617,"height":0.01915403},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Explain Plan","depth":4,"bounds":{"left":0.39162233,"top":0.09896249,"width":0.008643617,"height":0.01915403},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Browse Query History","depth":4,"bounds":{"left":0.40259308,"top":0.09896249,"width":0.008643617,"height":0.01915403},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"View Parameters","depth":4,"bounds":{"left":0.4112367,"top":0.09896249,"width":0.008643617,"height":0.01915403},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Open Query Execution Settings…","depth":4,"bounds":{"left":0.41988033,"top":0.09896249,"width":0.008643617,"height":0.01915403},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"In-Editor Results","depth":4,"bounds":{"left":0.43085107,"top":0.09896249,"width":0.008643617,"height":0.01915403},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Tx: Auto","depth":4,"bounds":{"left":0.4418218,"top":0.09896249,"width":0.024268618,"height":0.01915403},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Cancel Running Statements","depth":4,"bounds":{"left":0.46841756,"top":0.09896249,"width":0.008643617,"height":0.01915403},"on_screen":true,"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Playground","depth":4,"bounds":{"left":0.4793883,"top":0.09896249,"width":0.029587766,"height":0.01915403},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"jiminny","depth":4,"bounds":{"left":0.65359044,"top":0.09896249,"width":0.02825798,"height":0.01915403},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Sync Changes","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.008643617,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Hide This Notification","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.008643617,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Code changed:","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.042220745,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Hide","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.008643617,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"6","depth":4,"bounds":{"left":0.6399601,"top":0.123703115,"width":0.007978723,"height":0.015163607},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"1","depth":4,"bounds":{"left":0.6499335,"top":0.123703115,"width":0.00731383,"height":0.015163607},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"6","depth":4,"bounds":{"left":0.65924203,"top":0.123703115,"width":0.007978723,"height":0.015163607},"on_screen":true,"role_description":"text"},{"role":"AXButton","text":"Previous Highlighted Error","depth":4,"bounds":{"left":0.66888297,"top":0.12210695,"width":0.00731383,"height":0.018355945},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Next Highlighted Error","depth":4,"bounds":{"left":0.6761968,"top":0.12210695,"width":0.006981383,"height":0.018355945},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXTextArea","text":"# **************************** HS **************************************\n\nselect * from teams where id = 2; # 2\nselect * from features; # 2\nselect * from team_features where team_id = 2; # 2\nselect * from crm_configurations where id = 2; # 2\nselect * from users where team_id = 2; #\nselect * from playbooks where team_id = 2; # event 38\nselect * from playbook_categories where playbook_id = 38; #\n\nSELECT * FROM activities WHERE crm_configuration_id = 2 and crm_provider_id is not null order by id desc;\nhttps://app.hubspot.com/contacts/4392066/deal/16964514951/?engagement=96069102624\n https://app.staging.jiminny.com/playback/d5df34dc-bd66-4ff5-a7b3-8d3be30322a0\n\nSELECT * FROM activities WHERE uuid_to_bin('04fdcd0d-818f-4c53-92dc-6f18bc753ffd') = uuid;\n# 609126 softphone tr. 11241\n\nSELECT * FROM activities WHERE uuid_to_bin('6521bfcd-5a30-46e5-9f74-5440fd48befd') = uuid;\n# 608874 conference tr. 11226 crmId: 103422236596\n\nselect * from ai_prompts where transcription_id IN (11241, 11226);\nselect * from activity_summary_logs where activity_id = 608874;\n\nselect * from sidekick_settings;\nselect * from default_activity_types;\n\nselect * from crm_field_data where activity_id = 1223;\n\nselect * from crm_layouts where crm_configuration_id = 2;\nSELECT * FROM crm_layout_entities WHERE crm_layout_id IN (554);\nselect * from crm_fields where crm_configuration_id = 11 and object_type = 'event';\nSELECT * FROM crm_field_values WHERE crm_field_id IN (1455,1450);\n\nSELECT * FROM crm_field_data WHERE crm_layout_entity_id = 971;\nSELECT * FROM crm_field_data WHERE crm_layout_entity_id IN (6494,6495,6496,6497,6498,6499);\n\nSELECT\n CONCAT(u.id, CASE WHEN u.id = t.owner_id THEN ' (owner)' ELSE '' END) AS user_id,\n u.email,\n sa.*,\n t.owner_id FROM social_accounts sa\nJOIN users u\n on u.id = sa.sociable_id\nJOIN teams t on t.id = u.team_id\nWHERE u.team_id = 2 and sa.provider = 'hubspot';\n\nselect * from social_accounts where id = 1499;\n\nselect * from opportunities where team_id = 2\nand crm_provider_id IN ('51317301383');\n\nselect * from contacts where id = 85;\n\nselect * from opportunities where team_id = 2 order by id desc;\nselect * from opportunities where team_id = 2 and crm_provider_id = '51317301383'; # 5112\nselect * from opportunities where team_id = 2 and crm_provider_id = '55976759904'; # 5112\nselect * from opportunity_contacts where opportunity_id = 5117;\nselect * from crm_field_data where object_id = 1365;\nSELECT * FROM crm_fields WHERE id IN (1405, 1407, 1972, 2128);\n\nselect * from features;\nselect * from team_features where team_id IN (1);\nselect * from team_features where feature_id IN (36);\n\nSHOW CREATE TABLE opportunity_contacts;\nSELECT * FROM opportunity_contacts WHERE crm_provider_id = '111751';\n\n# $slug = 'HUBSPOT_WEBHOOK_SYNC';\n# $team = Jiminny\\Models\\Team::find(2);\n# $feature = Feature::query()->where('slug', $slug)->first();\n# TeamFeature::query()->create(['feature_id' => $feature->getId(),'team_id' => $team->getId()]);\n\n# hubspot_webhook_metrics\n\nselect * from opportunities where team_id = 2 and crm_provider_id IN ('374720564','14527423589','49908861993','50435771779'); # 1365\nSELECT * FROM opportunity_contacts WHERE opportunity_id = '414';\nSELECT * FROM opportunity_contacts WHERE crm_provider_id = '131501';\nselect * from contacts where id in (414, 464);\n\nselect * from activities where crm_configuration_id = 2;\n\nselect settings from crm_configurations where id = 11;\n\nselect * from teams; # 1, 2\nselect * from users;\nselect * from crm_configurations where id = 39;\nselect * from team_features where team_id = 2;\nselect * from features;\n# SELECT * FROM opportunities WHERE crm_configuration_id = 2\n# order by id desc;\n# and crm_provider_id = '49908861993';\n\n\nselect * from activity_providers where id IN (443, 202, 203, 227);\n\nselect * from activity_imports where id = 795889;\n\nselect c.id, c.provider, c.settings, t.* from teams t join crm_configurations c on t.id = c.team_id\nwhere c.provider = 'hubspot';\n\nselect * from crm_configurations crm JOIN teams t on crm.team_id = t.id\nwhere provider = 'hubspot';\nSELECT * FROM teams WHERE id = 31;\nSELECT * FROM users WHERE id = 257;\nSELECT * FROM opportunities WHERE team_id = 2;\n\nselect * from opportunity_contacts where opportunity_id = 5124;\nselect * from contacts where id IN (3850,3853,3851,4073,4140,4155,4480,4530,4623,5986,513,687,1806,1523,3613)\n\nselect * from activities where crm_configuration_id = 13;\n\nSELECT * FROM activities WHERE uuid_to_bin('826619ce-ec8e-4e59-8467-a01f5f6ad71e') = uuid; # 418141\n\n\nselect id, team_id, crm_provider_id from crm_configurations where provider = 'hubspot' and crm_provider_id IS NOT NULL;\nSELECT * FROM accounts WHERE team_id = 2 and crm_provider_id = '1212213464' order by id desc;\nSELECT * FROM contacts WHERE team_id = 2 and account_id = 5189 order by id desc;\nSELECT * FROM contacts WHERE team_id = 2 order by id desc;\nselect * from opportunity_contacts where contact_id = 6223;\nSELECT * FROM opportunities WHERE team_id = 2 and account_id = 5189 order by id desc;\n\nselect * from crm_profiles where crm_configuration_id = 2;\n\nselect * from activities where account_id = 46;","depth":4,"on_screen":true,"value":"# **************************** HS **************************************\n\nselect * from teams where id = 2; # 2\nselect * from features; # 2\nselect * from team_features where team_id = 2; # 2\nselect * from crm_configurations where id = 2; # 2\nselect * from users where team_id = 2; #\nselect * from playbooks where team_id = 2; # event 38\nselect * from playbook_categories where playbook_id = 38; #\n\nSELECT * FROM activities WHERE crm_configuration_id = 2 and crm_provider_id is not null order by id desc;\nhttps://app.hubspot.com/contacts/4392066/deal/16964514951/?engagement=96069102624\n https://app.staging.jiminny.com/playback/d5df34dc-bd66-4ff5-a7b3-8d3be30322a0\n\nSELECT * FROM activities WHERE uuid_to_bin('04fdcd0d-818f-4c53-92dc-6f18bc753ffd') = uuid;\n# 609126 softphone tr. 11241\n\nSELECT * FROM activities WHERE uuid_to_bin('6521bfcd-5a30-46e5-9f74-5440fd48befd') = uuid;\n# 608874 conference tr. 11226 crmId: 103422236596\n\nselect * from ai_prompts where transcription_id IN (11241, 11226);\nselect * from activity_summary_logs where activity_id = 608874;\n\nselect * from sidekick_settings;\nselect * from default_activity_types;\n\nselect * from crm_field_data where activity_id = 1223;\n\nselect * from crm_layouts where crm_configuration_id = 2;\nSELECT * FROM crm_layout_entities WHERE crm_layout_id IN (554);\nselect * from crm_fields where crm_configuration_id = 11 and object_type = 'event';\nSELECT * FROM crm_field_values WHERE crm_field_id IN (1455,1450);\n\nSELECT * FROM crm_field_data WHERE crm_layout_entity_id = 971;\nSELECT * FROM crm_field_data WHERE crm_layout_entity_id IN (6494,6495,6496,6497,6498,6499);\n\nSELECT\n CONCAT(u.id, CASE WHEN u.id = t.owner_id THEN ' (owner)' ELSE '' END) AS user_id,\n u.email,\n sa.*,\n t.owner_id FROM social_accounts sa\nJOIN users u\n on u.id = sa.sociable_id\nJOIN teams t on t.id = u.team_id\nWHERE u.team_id = 2 and sa.provider = 'hubspot';\n\nselect * from social_accounts where id = 1499;\n\nselect * from opportunities where team_id = 2\nand crm_provider_id IN ('51317301383');\n\nselect * from contacts where id = 85;\n\nselect * from opportunities where team_id = 2 order by id desc;\nselect * from opportunities where team_id = 2 and crm_provider_id = '51317301383'; # 5112\nselect * from opportunities where team_id = 2 and crm_provider_id = '55976759904'; # 5112\nselect * from opportunity_contacts where opportunity_id = 5117;\nselect * from crm_field_data where object_id = 1365;\nSELECT * FROM crm_fields WHERE id IN (1405, 1407, 1972, 2128);\n\nselect * from features;\nselect * from team_features where team_id IN (1);\nselect * from team_features where feature_id IN (36);\n\nSHOW CREATE TABLE opportunity_contacts;\nSELECT * FROM opportunity_contacts WHERE crm_provider_id = '111751';\n\n# $slug = 'HUBSPOT_WEBHOOK_SYNC';\n# $team = Jiminny\\Models\\Team::find(2);\n# $feature = Feature::query()->where('slug', $slug)->first();\n# TeamFeature::query()->create(['feature_id' => $feature->getId(),'team_id' => $team->getId()]);\n\n# hubspot_webhook_metrics\n\nselect * from opportunities where team_id = 2 and crm_provider_id IN ('374720564','14527423589','49908861993','50435771779'); # 1365\nSELECT * FROM opportunity_contacts WHERE opportunity_id = '414';\nSELECT * FROM opportunity_contacts WHERE crm_provider_id = '131501';\nselect * from contacts where id in (414, 464);\n\nselect * from activities where crm_configuration_id = 2;\n\nselect settings from crm_configurations where id = 11;\n\nselect * from teams; # 1, 2\nselect * from users;\nselect * from crm_configurations where id = 39;\nselect * from team_features where team_id = 2;\nselect * from features;\n# SELECT * FROM opportunities WHERE crm_configuration_id = 2\n# order by id desc;\n# and crm_provider_id = '49908861993';\n\n\nselect * from activity_providers where id IN (443, 202, 203, 227);\n\nselect * from activity_imports where id = 795889;\n\nselect c.id, c.provider, c.settings, t.* from teams t join crm_configurations c on t.id = c.team_id\nwhere c.provider = 'hubspot';\n\nselect * from crm_configurations crm JOIN teams t on crm.team_id = t.id\nwhere provider = 'hubspot';\nSELECT * FROM teams WHERE id = 31;\nSELECT * FROM users WHERE id = 257;\nSELECT * FROM opportunities WHERE team_id = 2;\n\nselect * from opportunity_contacts where opportunity_id = 5124;\nselect * from contacts where id IN (3850,3853,3851,4073,4140,4155,4480,4530,4623,5986,513,687,1806,1523,3613)\n\nselect * from activities where crm_configuration_id = 13;\n\nSELECT * FROM activities WHERE uuid_to_bin('826619ce-ec8e-4e59-8467-a01f5f6ad71e') = uuid; # 418141\n\n\nselect id, team_id, crm_provider_id from crm_configurations where provider = 'hubspot' and crm_provider_id IS NOT NULL;\nSELECT * FROM accounts WHERE team_id = 2 and crm_provider_id = '1212213464' order by id desc;\nSELECT * FROM contacts WHERE team_id = 2 and account_id = 5189 order by id desc;\nSELECT * FROM contacts WHERE team_id = 2 order by id desc;\nselect * from opportunity_contacts where contact_id = 6223;\nSELECT * FROM opportunities WHERE team_id = 2 and account_id = 5189 order by id desc;\n\nselect * from crm_profiles where crm_configuration_id = 2;\n\nselect * from activities where account_id = 46;","role_description":"text entry area","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Sync Changes","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.008643617,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Hide This Notification","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.008643617,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Code changed:","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.042220745,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Hide","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.008643617,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"2","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.007978723,"height":0.0},"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"60","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.010305851,"height":0.0},"on_screen":false,"role_description":"text"},{"role":"AXButton","text":"Previous Highlighted Error","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.00731383,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Next Highlighted Error","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.006981383,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXTextArea","text":"<?php\n\ndeclare(strict_types=1);\n\nnamespace Jiminny\\Services\\Crm\\Hubspot;\n\nuse HubSpot\\Client\\Crm\\Deals\\ApiException as DealApiException;\nuse HubSpot\\Client\\Crm\\Contacts\\ApiException as ContactApiException;\nuse HubSpot\\Client\\Crm\\Companies\\ApiException as CompanyApiException;\nuse HubSpot\\Client\\Crm\\Contacts\\Model\\SimplePublicObjectWithAssociations as ContactsWithAssociations;\nuse HubSpot\\Client\\Crm\\Companies\\Model\\SimplePublicObjectWithAssociations as CompaniesWithAssociations;\nuse HubSpot\\Client\\Crm\\Deals\\Model\\SimplePublicObjectWithAssociations as DealWithAssociations;\nuse HubSpot\\Client\\Crm\\Objects\\Model\\SimplePublicObjectInput;\nuse HubSpot\\Client\\Crm\\Objects\\Model\\SimplePublicObjectWithAssociations as ObjectWithAssociations;\nuse HubSpot\\Client\\Crm\\Pipelines\\Model\\Error;\nuse HubSpot\\Client\\Crm\\Pipelines\\Model\\PipelineStage;\nuse HubSpot\\Client\\Crm\\Properties\\Model\\Property;\nuse HubSpot\\Discovery\\Discovery;\nuse Jiminny\\Component\\Utility\\Service\\ProviderRateLimiter;\nuse Jiminny\\Exceptions\\CrmException;\nuse Jiminny\\Exceptions\\RateLimitException;\nuse Jiminny\\Exceptions\\SocialAccountTokenInvalidException;\nuse Jiminny\\Jobs\\Crm\\NoteObject;\nuse Jiminny\\Models\\Crm\\Field;\nuse Jiminny\\Services\\Crm\\BaseClient;\nuse Jiminny\\Services\\Crm\\Hubspot\\DTO\\Response\\Owner;\nuse Jiminny\\Services\\SocialAccountService;\nuse SevenShores\\Hubspot\\Exceptions\\BadRequest;\nuse SevenShores\\Hubspot\\Exceptions\\HubspotException;\nuse SevenShores\\Hubspot\\Factory;\nuse SevenShores\\Hubspot\\Http\\Response;\nuse Jiminny\\Services\\Crm\\Hubspot\\Pagination\\HubspotPaginationService;\nuse Throwable;\n\n/**\n * @phpstan-type CrmFieldOption array{id:string, label:string, value?:string}\n */\nclass Client extends BaseClient implements HubspotClientInterface\n{\n public const string MIN_API_VERSION = '2';\n\n public const string BASE_URL = 'https://api.hubapi.com';\n\n public const int ASSOCIATIONS_BATCH_SIZE_LIMIT = 1000;\n\n private HubspotPaginationService $paginationService;\n private HubspotTokenManager $tokenManager;\n private ProviderRateLimiter $rateLimiter;\n\n public function __construct(\n SocialAccountService $socialAccountService,\n HubspotPaginationService $paginationService,\n HubspotTokenManager $tokenManager,\n ProviderRateLimiter $rateLimiter,\n ) {\n parent::__construct($socialAccountService);\n $this->paginationService = $paginationService;\n $this->tokenManager = $tokenManager;\n $this->rateLimiter = $rateLimiter;\n\n $this->setBaseUrl(self::BASE_URL);\n $this->setVersion(self::MIN_API_VERSION);\n }\n\n /**\n * Single entry point for every HubSpot API call. Enforces the per-portal\n * rate limit configured in the rate_limits table (morphed to the current\n * Configuration) and reacts to a real 429 from HubSpot by translating it\n * into a RateLimitException carrying Retry-After.\n *\n * Wrap any outbound HubSpot call (SDK or raw HTTP) like:\n *\n * $this->executeRequest(fn () => $this->getNewInstance()->crm()->...);\n *\n * @template T\n * @param callable(): T $apiCall\n * @return T\n *\n * @throws RateLimitException\n */\n private function executeRequest(callable $apiCall)\n {\n if (! $this->rateLimiter->canMakeRequest($this->config)) {\n $retryAfter = $this->rateLimiter->requestAvailableIn($this->config);\n\n $this->log->warning('[Hubspot] Rate limit exceeded, deferring request', [\n 'team_id' => $this->config->team_id,\n 'config_id' => $this->config->getId(),\n 'retry_after' => $retryAfter,\n ]);\n\n throw new RateLimitException(\n 'Hubspot rate limit reached for configuration ' . $this->config->getId(),\n $retryAfter,\n );\n }\n\n $this->rateLimiter->incrementRequestCount($this->config);\n\n try {\n return $apiCall();\n } catch (Throwable $e) {\n if ($this->isHubspotRateLimit($e)) {\n $retryAfter = $this->parseRetryAfter($e);\n\n $this->log->warning('[Hubspot] Received 429 from API', [\n 'team_id' => $this->config->team_id,\n 'config_id' => $this->config->getId(),\n 'retry_after' => $retryAfter,\n 'reason' => $e->getMessage(),\n ]);\n\n throw new RateLimitException('Hubspot returned 429', $retryAfter, $e);\n }\n\n throw $e;\n }\n }\n\n private function isHubspotRateLimit(Throwable $e): bool\n {\n return method_exists($e, 'getCode') && (int) $e->getCode() === 429;\n }\n\n private function parseRetryAfter(Throwable $e): int\n {\n if (method_exists($e, 'getResponseHeaders')) {\n $headers = $e->getResponseHeaders() ?: [];\n $value = $headers['Retry-After'] ?? $headers['retry-after'] ?? null;\n if (is_array($value)) {\n $value = $value[0] ?? null;\n }\n if (is_numeric($value)) {\n return (int) $value;\n }\n }\n\n return 10;\n }\n\n public function getMinimumApiVersion(): string\n {\n return self::MIN_API_VERSION;\n }\n\n public function getInstance(): Factory\n {\n return new Factory([\n 'key' => $this->accessToken,\n 'oauth2' => true,\n 'base_url' => $this->baseUrl,\n ]);\n }\n\n public function getNewInstance(): Discovery\n {\n return \\HubSpot\\Factory::createWithAccessToken($this->accessToken);\n }\n\n /**\n * Secondly and daily limits for Hubspot API\n *\n * Product Tier: Free & Starter | Professional & Enterprise | API add-on (any tier)\n * Burst: 100/10 seconds | 150/10 seconds | 200/10 seconds\n * Daily: 250,000 | 500,000 | 1,000,000\n *\n * Official documentation states: The search endpoints are rate limited to five requests per second.\n * Since with 5 RPS were still hitting secondly rate limits we lowered it to 4\n */\n public function getPaginatedData(array $payload, string $type, int $offset = 0): array\n {\n $total = 0;\n $lastId = null;\n $rows = [];\n foreach ($this->getPaginatedDataGenerator($payload, $type, $offset, $total, $lastId) as $row) {\n $rows[] = $row;\n }\n\n return ['results' => $rows, 'total' => $total, 'last_record' => $lastId];\n }\n\n /**\n * @throws HubspotException\n * @throws SocialAccountTokenInvalidException\n * @throws BadRequest\n */\n public function getPaginatedDataGenerator(\n array $payload,\n string $type,\n int $offset = 0,\n int &$total = 0,\n ?string &$lastRecordId = null\n ): \\Generator {\n return $this->paginationService->getPaginatedDataGenerator(\n $this,\n $payload,\n $type,\n $offset,\n $total,\n $lastRecordId\n );\n }\n\n /**\n * @throws DealApiException\n * @throws CrmException\n */\n public function getOpportunityById(string $crmId, array $fields): array\n {\n try {\n $deal = $this->executeRequest(fn () => $this->getNewInstance()->crm()->deals()->basicApi()->getById(\n $crmId,\n implode(',', $fields),\n 'companies,contacts'\n ));\n } catch (DealApiException $e) {\n $this->log->info('[Hubspot] Failed to fetch opportunity', [\n 'crm_id' => $crmId,\n 'reason' => $e->getMessage(),\n ]);\n\n throw $e;\n }\n\n if (! $deal instanceof DealWithAssociations) {\n throw new CrmException('Deal not found');\n }\n\n return [\n 'id' => $deal->getId(),\n 'properties' => $deal->getProperties(),\n 'associations' => $deal->getAssociations(),\n ];\n }\n\n /**\n * Generic batch read method for HubSpot objects\n *\n * @param string $objectType The object type ('deals', 'companies', 'contacts')\n * @param array<string> $crmIds Array of HubSpot object IDs (max 100)\n * @param array<string> $fields Array of property names to fetch\n *\n * @return array<string, array> Array keyed by CRM ID with object data\n */\n private function batchReadObjects(string $objectType, array $crmIds, array $fields): array\n {\n if (empty($crmIds)) {\n return [];\n }\n\n $this->validateBatchSize($objectType, $crmIds);\n $this->ensureValidToken();\n\n try {\n $batchConfig = $this->createBatchConfiguration($objectType);\n $batchReadRequest = $this->prepareBatchRequest($batchConfig, $crmIds, $fields);\n $response = $batchConfig['api']->read($batchReadRequest);\n\n $this->validateApiResponse($response, $objectType);\n\n $results = $this->processApiResults($response);\n $this->logBatchResults($objectType, $crmIds, $results);\n\n return $results;\n } catch (\\Throwable $e) {\n $this->handleBatchError($e, $objectType, $crmIds);\n }\n }\n\n private function validateBatchSize(string $objectType, array $crmIds): void\n {\n if (count($crmIds) > 100) {\n throw new \\InvalidArgumentException(\"Batch size cannot exceed 100 {$objectType}\");\n }\n }\n\n private function createBatchConfiguration(string $objectType): array\n {\n $configurations = [\n 'deals' => [\n 'batchReadRequest' => new \\HubSpot\\Client\\Crm\\Deals\\Model\\BatchReadInputSimplePublicObjectId(),\n 'inputClass' => \\HubSpot\\Client\\Crm\\Deals\\Model\\SimplePublicObjectId::class,\n 'api' => $this->getNewInstance()->crm()->deals()->batchApi(),\n ],\n 'companies' => [\n 'batchReadRequest' => new \\HubSpot\\Client\\Crm\\Companies\\Model\\BatchReadInputSimplePublicObjectId(),\n 'inputClass' => \\HubSpot\\Client\\Crm\\Companies\\Model\\SimplePublicObjectId::class,\n 'api' => $this->getNewInstance()->crm()->companies()->batchApi(),\n ],\n 'contacts' => [\n 'batchReadRequest' => new \\HubSpot\\Client\\Crm\\Contacts\\Model\\BatchReadInputSimplePublicObjectId(),\n 'inputClass' => \\HubSpot\\Client\\Crm\\Contacts\\Model\\SimplePublicObjectId::class,\n 'api' => $this->getNewInstance()->crm()->contacts()->batchApi(),\n ],\n ];\n\n if (! isset($configurations[$objectType])) {\n throw new \\InvalidArgumentException(\"Unsupported object type: {$objectType}\");\n }\n\n return $configurations[$objectType];\n }\n\n private function prepareBatchRequest(array $batchConfig, array $crmIds, array $fields): object\n {\n $batchReadRequest = $batchConfig['batchReadRequest'];\n $inputClass = $batchConfig['inputClass'];\n\n $inputs = array_map(function ($crmId) use ($inputClass) {\n $input = new $inputClass();\n $input->setId($crmId);\n\n return $input;\n }, $crmIds);\n\n $batchReadRequest->setInputs($inputs);\n $batchReadRequest->setProperties($fields);\n\n return $batchReadRequest;\n }\n\n private function validateApiResponse($response, string $objectType): void\n {\n if (! $response) {\n throw new CrmException(\"HubSpot API returned null response for {$objectType} batch read\");\n }\n }\n\n private function processApiResults($response): array\n {\n $results = [];\n $responseResults = $response->getResults();\n\n if ($responseResults) {\n foreach ($responseResults as $object) {\n if ($object && $object->getId()) {\n $results[$object->getId()] = [\n 'id' => $object->getId(),\n 'properties' => $object->getProperties() ?: [],\n ];\n }\n }\n }\n\n return $results;\n }\n\n private function logBatchResults(string $objectType, array $crmIds, array $results): void\n {\n $this->log->info(\"[HubSpot] Batch fetched {$objectType}\", [\n 'requested_count' => count($crmIds),\n 'returned_count' => count($results),\n 'crm_ids' => $crmIds,\n ]);\n }\n\n private function handleBatchError(\\Throwable $e, string $objectType, array $crmIds): void\n {\n $errorMessage = $e->getMessage() ?: 'Unknown error';\n $errorTrace = $e->getTraceAsString() ?: 'No trace available';\n\n $this->log->error(\"[HubSpot] Failed to batch fetch {$objectType}\", [\n 'crm_ids' => $crmIds,\n 'error' => $errorMessage,\n 'trace' => $errorTrace,\n ]);\n\n throw new CrmException(\"Failed to batch fetch {$objectType}: \" . $errorMessage);\n }\n\n /**\n * Batch read multiple opportunities by their CRM IDs\n *\n * @param array<string> $crmIds Array of HubSpot deal IDs (max 100)\n * @param array<string> $fields Array of property names to fetch\n *\n * @return array<string, array> Array keyed by CRM ID with opportunity data\n */\n public function getOpportunitiesByIds(array $crmIds, array $fields): array\n {\n return $this->batchReadObjects('deals', $crmIds, $fields);\n }\n\n /**\n * Batch read multiple companies by their CRM IDs\n *\n * @param array<string> $crmIds Array of HubSpot company IDs (max 100)\n * @param array<string> $fields Array of property names to fetch\n *\n * @return array<string, array> Array keyed by CRM ID with company data\n */\n public function getCompaniesByIds(array $crmIds, array $fields): array\n {\n return $this->batchReadObjects('companies', $crmIds, $fields);\n }\n\n /**\n * Batch read multiple contacts by their CRM IDs\n *\n * @param array<string> $crmIds Array of HubSpot contact IDs (max 100)\n * @param array<string> $fields Array of property names to fetch\n *\n * @return array<string, array> Array keyed by CRM ID with contact data\n */\n public function getContactsByIds(array $crmIds, array $fields): array\n {\n return $this->batchReadObjects('contacts', $crmIds, $fields);\n }\n\n /**\n * @throws CompanyApiException\n * @throws CrmException\n */\n public function getAccountById(string $crmId, array $fields): array\n {\n try {\n $company = $this->getNewInstance()->crm()->companies()->basicApi()->getById(\n $crmId,\n implode(',', $fields),\n );\n } catch (CompanyApiException $e) {\n $this->log->info('[Hubspot] Failed to fetch account', [\n 'crm_id' => $crmId,\n 'reason' => $e->getMessage(),\n ]);\n\n throw $e;\n }\n\n if (! $company instanceof CompaniesWithAssociations) {\n throw new CrmException('Account not found');\n }\n\n return [\n 'id' => $company->getId(),\n 'properties' => $company->getProperties(),\n ];\n }\n\n /**\n * @throws ContactApiException\n * @throws CrmException\n */\n public function getContactById(string $crmId, array $fields): array\n {\n try {\n $contact = $this->getNewInstance()->crm()->contacts()->basicApi()->getById(\n $crmId,\n implode(',', $fields)\n );\n } catch (ContactApiException $e) {\n $this->log->info('[Hubspot] Failed to fetch contact', [\n 'crm_id' => $crmId,\n 'reason' => $e->getMessage(),\n ]);\n\n throw $e;\n }\n\n if (! $contact instanceof ContactsWithAssociations) {\n throw new CrmException('Contact not found');\n }\n\n return [\n 'id' => $contact->getId(),\n 'properties' => $contact->getProperties(),\n ];\n }\n\n /**\n * This is email search request that Hubspot offers as GET (more generous quota)\n */\n public function getContactByEmail(string $email, array $fields = []): array\n {\n try {\n $contact = $this->getNewInstance()->crm()->contacts()->basicApi()->getById(\n $email,\n implode(',', $fields),\n null,\n false,\n 'email'\n );\n\n return [\n 'id' => $contact->getId(),\n 'properties' => $contact->getProperties(),\n ];\n } catch (ContactApiException $e) {\n $this->log->info('[Hubspot] Failed to fetch contact', [\n 'email' => $email,\n 'reason' => $e->getMessage(),\n ]);\n\n return [];\n }\n }\n\n /**\n * @throws CrmException\n */\n public function fetchProperty(string $objectType, string $propertyId): Property\n {\n $result = $this->getNewInstance()->crm()->properties()->coreApi()->getByName($objectType, $propertyId);\n\n if (! $result instanceof Property) {\n $this->log->error('[Hubspot] Failed to fetch property', [\n 'object_type' => $objectType,\n 'property_id' => $propertyId,\n 'reason' => $result->getMessage(),\n ]);\n\n throw new CrmException('Failed to fetch property');\n }\n\n return $result;\n }\n\n /**\n * @return array<CrmFieldOption>\n */\n public function fetchPropertyOptions(string $objectType, string $propertyId): array\n {\n /** @var array<CrmFieldOption> */\n return $this->fetchProperty($objectType, $propertyId)->getOptions();\n }\n\n /**\n * @return array<array{id:string, label:string, deleted:bool}>\n */\n public function fetchCallDispositions(): array\n {\n /** @var Response $response */\n $response = $this->getInstance()->engagements()->getCallDispositions();\n\n /**\n * @var array<array{\n * id:string,\n * label:string,\n * deleted: bool\n * }>\n */\n return $response->toArray();\n }\n\n /**\n * @return array<CrmFieldOption>\n */\n public function fetchOpportunityPipelineStages(): array\n {\n $stages = [];\n $apiResponse = $this->getNewInstance()->crm()->pipelines()->pipelinesApi()->getAll('deals');\n\n if ($apiResponse instanceof Error) {\n $this->log->error('[Hubspot] Failed to fetch opportunity pipelines', [\n 'reason' => $apiResponse->getMessage(),\n ]);\n\n return [];\n }\n\n foreach ($apiResponse->getResults() as $pipeline) {\n $pipelineStages = array_map(\n static function (PipelineStage $stage) {\n return [\n 'id' => $stage->getId(),\n 'label' => $stage->getLabel(),\n ];\n },\n $pipeline->getStages()\n );\n\n $stages = array_merge($stages, $pipelineStages);\n }\n\n return $stages;\n }\n\n public function fetchOpportunityPipelines(): array\n {\n $pipelines = [];\n\n try {\n $apiResponse = $this->makeRequest('/crm/v3/pipelines/deals');\n } catch (\\Exception $e) {\n $this->log->info('[Hubspot] Failed to fetch opportunity pipelines', [\n 'reason' => $e->getMessage(),\n ]);\n\n return [];\n }\n\n $response = $apiResponse->toArray();\n\n foreach ($response['results'] as $pipeline) {\n $pipelines[] = [\n 'id' => $pipeline['id'],\n 'label' => $pipeline['label'],\n ];\n }\n\n return $pipelines;\n }\n\n /**\n * @return array<CrmFieldOption>\n */\n public function fetchMeetingOutcomeFieldOptions(Field $field): array\n {\n return $field->getCrmProviderId() === 'meetingOutcome'\n ? $this->fetchMeetingOutcomeTypes()\n : $this->fetchCallActivityTypes();\n }\n\n public function fetchMeetingOutcomeTypes(): array\n {\n return $this->extractMeetingTypeOptions(\n 'https://api.hubapi.com/crm/v3/properties/meeting/hs_meeting_outcome'\n );\n }\n\n public function fetchCallActivityTypes(): array\n {\n return $this->extractMeetingTypeOptions(\n 'https://api.hubapi.com/crm/v3/properties/call/hs_activity_type'\n );\n }\n\n private function extractMeetingTypeOptions(string $endpoint): array\n {\n /** @var Response $response */\n $response = $this->getInstance()\n ->getClient()\n ->request('GET', $endpoint);\n\n /**\n * @var array<array{\n * value: string,\n * label: string,\n * displayOrder: int\n * }> $optionData\n */\n $optionData = $response->toArray()['options'] ?? [];\n\n $options = [];\n foreach ($optionData as $item) {\n $options[] = [\n 'id' => $item['value'],\n 'value' => $item['value'],\n 'label' => $item['label'],\n 'display_order' => $item['displayOrder'],\n ];\n }\n\n return $options;\n }\n\n /**\n * @return array<CrmFieldOption>\n */\n public function fetchDispositionFieldOptions(): array\n {\n $options = [];\n\n $dispositions = $this->fetchCallDispositions();\n\n foreach ($dispositions as $disposition) {\n if ($disposition['deleted'] !== false) {\n continue;\n }\n\n $option['value'] = $disposition['id'];\n $option['id'] = $disposition['id'];\n $option['label'] = $disposition['label'];\n\n $options[] = $option;\n }\n\n return $options;\n }\n\n /**\n * @return array<CrmFieldOption>\n */\n public function fetchOpportunityFieldOptions(Field $field): array\n {\n if ($field->isStageField()) {\n return $this->fetchOpportunityPipelineStages();\n }\n\n if ($field->isPipelineField()) {\n return $this->fetchOpportunityPipelines();\n }\n\n return $this->fetchPropertyOptions('deals', $field->getCrmProviderId());\n }\n\n /**\n * @throws BadRequest\n * @throws HubspotException\n */\n public function makeRequest(string $endpoint, $method = 'GET', $payload = [], ?string $queryString = null)\n {\n $endpoint = self::BASE_URL . $endpoint;\n\n if ($method === 'GET') {\n $response = $this->getInstance()->getClient()?->request(\n method: $method,\n endpoint: $endpoint,\n query_string: $queryString\n );\n } else {\n $response = $this->getInstance()->getClient()->request($method, $endpoint, [\n 'json' => ($payload),\n ]);\n }\n\n $max = $response->getHeaderLine('X-HubSpot-RateLimit-Max'); // \"110\"\n $remaining = $response->getHeaderLine('X-HubSpot-RateLimit-Remaining'); // \"109\"\n $interval = $response->getHeaderLine('X-HubSpot-RateLimit-Interval-Milliseconds'); // \"10000\"\n $body = json_decode((string) $response->getBody(), true);\n\n \\Illuminate\\Support\\Facades\\Log::channel('custom_channel')->info('$max ' . PHP_EOL . print_r($max, true));\n \\Illuminate\\Support\\Facades\\Log::channel('custom_channel')->info('$remaining ' . PHP_EOL . print_r($remaining, true));\n \\Illuminate\\Support\\Facades\\Log::channel('custom_channel')->info('$interval ' . PHP_EOL . print_r($interval, true));\n \\Illuminate\\Support\\Facades\\Log::channel('custom_channel')->info('$body ' . PHP_EOL . print_r($body, true));\n\n return $response;\n }\n\n /**\n * @throws BadRequest\n * @throws HubspotException\n */\n public function createMeeting(array $payload): Response\n {\n $endpoint = '/crm/v3/objects/meetings';\n\n return $this->makeRequest($endpoint, 'POST', $payload);\n }\n\n /**\n * @throws BadRequest\n * @throws HubspotException\n */\n public function updateMeeting(string $meetingId, array $payload): Response\n {\n $endpoint = '/crm/v3/objects/meetings/' . $meetingId;\n\n return $this->makeRequest($endpoint, 'PATCH', $payload);\n }\n\n /**\n * @throws \\Exception\n */\n public function createNote(\n string $body,\n string $ownerId,\n int $timestamp,\n string $objectId,\n NoteObject $noteObject\n ): ?string {\n try {\n $noteInput = new SimplePublicObjectInput([\n 'properties' => [\n 'hs_note_body' => $body,\n 'hubspot_owner_id' => $ownerId,\n 'hs_timestamp' => $timestamp,\n ],\n ]);\n\n // Create note\n $note = $this->getNewInstance()->crm()->objects()->basicApi()->create('note', $noteInput);\n\n $this->getNewInstance()->crm()->objects()->associationsApi()->create(\n 'note',\n $note->getId(),\n $this->getNoteObject($noteObject),\n $objectId,\n $this->getNoteAssociationType($noteObject),\n );\n\n return $note->getId();\n } catch (\\Exception $e) {\n $this->log->error('[Hubspot] Failed to create note', [\n 'objectId' => $objectId,\n 'noteObject' => $noteObject->getObjectType(),\n 'reason' => $e->getMessage(),\n ]);\n\n \\Sentry::captureException($e);\n }\n\n return null;\n }\n\n public function updateEngagement(string $objectId, array $engagement, array $metadata): void\n {\n $this->getInstance()->engagements()->update($objectId, $engagement, $metadata);\n }\n\n public function getEngagementData(string $engagementId): array\n {\n $engagement = $this->getInstance()->engagements()->get($engagementId);\n\n return $engagement->toArray();\n }\n\n public function createEngagement(array $engagement, array $associations, array $metadata): Response\n {\n return $this->getInstance()\n ->engagements()\n ->create($engagement, $associations, $metadata);\n }\n\n public function isUnauthorizedException(\\Exception $e): bool\n {\n // Check for specific HubSpot API exception types first\n if ($e instanceof BadRequest) {\n // BadRequest can contain 401 status codes\n return $e->getCode() === 401;\n }\n\n // Check for HTTP client exceptions with status codes\n if ($e instanceof \\GuzzleHttp\\Exception\\RequestException && $e->hasResponse()) {\n $response = $e->getResponse();\n if ($response !== null) {\n return $response->getStatusCode() === 401;\n }\n }\n\n // Check for Guzzle HTTP exceptions\n if ($e instanceof \\GuzzleHttp\\Exception\\ClientException) {\n return $e->getCode() === 401;\n }\n\n // Fallback to string matching as last resort, but be more specific\n $message = strtolower($e->getMessage());\n\n return str_contains($message, '401 unauthorized') ||\n str_contains($message, 'http 401') ||\n str_contains($message, 'status code 401') ||\n (preg_match('/\\b401\\b/', $message) && str_contains($message, 'unauthorized'));\n }\n\n /**\n * Validates and refreshes the access token if needed before API requests.\n * This ensures long-running processes don't fail due to token expiration.\n *\n * @throws SocialAccountTokenInvalidException\n */\n public function ensureValidToken(): void\n {\n if ($this->oauthAccount === null) {\n return;\n }\n\n $newToken = $this->tokenManager->ensureValidToken($this->oauthAccount);\n if ($newToken !== null) {\n $this->accessToken = $newToken;\n }\n }\n\n public function getConfig()\n {\n return $this->config;\n }\n\n // returns only active (archived=false)\n public function getOwners(): array\n {\n return $this->getNewInstance()->crm()->owners()->getAll();\n }\n\n /**\n * @param bool $archived\n *\n * @return array<Owner>|[]\n */\n public function getOwnersArchived(bool $archived = true): array\n {\n $endpoint = '/crm/v3/owners';\n $queryParams = [\n 'archived' => $archived ? 'true' : 'false',\n ];\n $queryString = http_build_query($queryParams);\n\n $owners = [];\n\n try {\n $response = $this->makeRequest(endpoint: $endpoint, queryString: $queryString);\n $responseData = $response?->toArray();\n\n foreach ($responseData['results'] as $result) {\n try {\n $owners[] = Owner::create($result);\n } catch (Throwable $e) {\n $this->log->error('[HubSpot] Failed to process owner data', [\n 'result' => $result,\n 'error' => $e->getMessage(),\n ]);\n\n continue;\n }\n }\n } catch (Throwable $e) {\n $this->log->error('HubSpot] Failed to fetch owners', [\n 'archived' => $archived,\n 'error' => $e->getMessage(),\n ]);\n\n return [];\n }\n\n return $owners;\n }\n\n public function getMeeting(string $engagementId): ObjectWithAssociations\n {\n return $this->getNewInstance()->crm()->objects()->basicApi()\n ->getById('meeting', $engagementId, null, 'contact,company,deal');\n }\n\n public function deleteEngagement(string $engagementId): void\n {\n $this->getInstance()->engagements()->delete((int) $engagementId);\n }\n\n public function getAssociationsData(array $ids, string $fromObject, string $toObject): array\n {\n $associationData = [];\n $idChunks = array_chunk($ids, self::ASSOCIATIONS_BATCH_SIZE_LIMIT);\n\n foreach ($idChunks as $idChunk) {\n try {\n $batchInput = new \\HubSpot\\Client\\Crm\\Associations\\Model\\BatchInputPublicObjectId();\n $batchInput->setInputs(array_map(function ($id) {\n $publicObjectId = new \\HubSpot\\Client\\Crm\\Associations\\Model\\PublicObjectId();\n $publicObjectId->setId($id);\n\n return $publicObjectId;\n }, $idChunk));\n\n $associatedObjectsData = $this\n ->getNewInstance()\n ->crm()\n ->associations()\n ->batchApi()\n ->read($fromObject, $toObject, $batchInput);\n\n if ($associatedObjectsData instanceof \\HubSpot\\Client\\Crm\\Associations\\Model\\BatchResponsePublicAssociationMulti) {\n foreach ($associatedObjectsData->getResults() as $association) {\n $from = $association->getFrom()->getId();\n $toAssociations = $association->getTo();\n\n if (! empty($toAssociations)) {\n $associationData[$from] = array_map(function ($item) {\n return $item->getId();\n }, $toAssociations);\n }\n }\n }\n } catch (\\Exception $e) {\n $this->log->error('[Hubspot] Failed to fetch associations', [\n 'from_object' => $fromObject,\n 'to_object' => $toObject,\n 'reason' => $e->getMessage(),\n ]);\n }\n }\n\n return $associationData;\n }\n\n /**\n * @throws \\Exception\n */\n private function getNoteAssociationType(NoteObject $noteObject): string\n {\n return match($noteObject) {\n NoteObject::Opportunity => 'note_to_deal',\n NoteObject::Lead, NoteObject::Contact => 'note_to_contact', // or 'note_to_lead' if your portal supports it\n NoteObject::Account => 'note_to_company',\n NoteObject::Call, NoteObject::Event => throw new \\Exception('Not supported'),\n };\n }\n\n /**\n * @throws \\Exception\n */\n private function getNoteObject(NoteObject $noteObject): string\n {\n return match($noteObject) {\n NoteObject::Opportunity => 'deal',\n NoteObject::Lead, NoteObject::Contact => 'contact',\n NoteObject::Account => 'company',\n NoteObject::Call, NoteObject::Event => throw new \\Exception('Not supported'),\n };\n }\n\n public function addAssociations(string $objectType, string $associationType, array $payload): Response\n {\n $endpoint = \"/crm/v4/associations/$objectType/$associationType/batch/create\";\n\n return $this->makeRequest($endpoint, 'POST', $payload);\n }\n\n public function removeAssociations(string $objectType, string $associationType, array $payload): Response\n {\n $endpoint = \"/crm/v4/associations/$objectType/$associationType/batch/archive\";\n\n return $this->makeRequest($endpoint, 'POST', $payload);\n }\n}","depth":4,"on_screen":true,"value":"<?php\n\ndeclare(strict_types=1);\n\nnamespace Jiminny\\Services\\Crm\\Hubspot;\n\nuse HubSpot\\Client\\Crm\\Deals\\ApiException as DealApiException;\nuse HubSpot\\Client\\Crm\\Contacts\\ApiException as ContactApiException;\nuse HubSpot\\Client\\Crm\\Companies\\ApiException as CompanyApiException;\nuse HubSpot\\Client\\Crm\\Contacts\\Model\\SimplePublicObjectWithAssociations as ContactsWithAssociations;\nuse HubSpot\\Client\\Crm\\Companies\\Model\\SimplePublicObjectWithAssociations as CompaniesWithAssociations;\nuse HubSpot\\Client\\Crm\\Deals\\Model\\SimplePublicObjectWithAssociations as DealWithAssociations;\nuse HubSpot\\Client\\Crm\\Objects\\Model\\SimplePublicObjectInput;\nuse HubSpot\\Client\\Crm\\Objects\\Model\\SimplePublicObjectWithAssociations as ObjectWithAssociations;\nuse HubSpot\\Client\\Crm\\Pipelines\\Model\\Error;\nuse HubSpot\\Client\\Crm\\Pipelines\\Model\\PipelineStage;\nuse HubSpot\\Client\\Crm\\Properties\\Model\\Property;\nuse HubSpot\\Discovery\\Discovery;\nuse Jiminny\\Component\\Utility\\Service\\ProviderRateLimiter;\nuse Jiminny\\Exceptions\\CrmException;\nuse Jiminny\\Exceptions\\RateLimitException;\nuse Jiminny\\Exceptions\\SocialAccountTokenInvalidException;\nuse Jiminny\\Jobs\\Crm\\NoteObject;\nuse Jiminny\\Models\\Crm\\Field;\nuse Jiminny\\Services\\Crm\\BaseClient;\nuse Jiminny\\Services\\Crm\\Hubspot\\DTO\\Response\\Owner;\nuse Jiminny\\Services\\SocialAccountService;\nuse SevenShores\\Hubspot\\Exceptions\\BadRequest;\nuse SevenShores\\Hubspot\\Exceptions\\HubspotException;\nuse SevenShores\\Hubspot\\Factory;\nuse SevenShores\\Hubspot\\Http\\Response;\nuse Jiminny\\Services\\Crm\\Hubspot\\Pagination\\HubspotPaginationService;\nuse Throwable;\n\n/**\n * @phpstan-type CrmFieldOption array{id:string, label:string, value?:string}\n */\nclass Client extends BaseClient implements HubspotClientInterface\n{\n public const string MIN_API_VERSION = '2';\n\n public const string BASE_URL = 'https://api.hubapi.com';\n\n public const int ASSOCIATIONS_BATCH_SIZE_LIMIT = 1000;\n\n private HubspotPaginationService $paginationService;\n private HubspotTokenManager $tokenManager;\n private ProviderRateLimiter $rateLimiter;\n\n public function __construct(\n SocialAccountService $socialAccountService,\n HubspotPaginationService $paginationService,\n HubspotTokenManager $tokenManager,\n ProviderRateLimiter $rateLimiter,\n ) {\n parent::__construct($socialAccountService);\n $this->paginationService = $paginationService;\n $this->tokenManager = $tokenManager;\n $this->rateLimiter = $rateLimiter;\n\n $this->setBaseUrl(self::BASE_URL);\n $this->setVersion(self::MIN_API_VERSION);\n }\n\n /**\n * Single entry point for every HubSpot API call. Enforces the per-portal\n * rate limit configured in the rate_limits table (morphed to the current\n * Configuration) and reacts to a real 429 from HubSpot by translating it\n * into a RateLimitException carrying Retry-After.\n *\n * Wrap any outbound HubSpot call (SDK or raw HTTP) like:\n *\n * $this->executeRequest(fn () => $this->getNewInstance()->crm()->...);\n *\n * @template T\n * @param callable(): T $apiCall\n * @return T\n *\n * @throws RateLimitException\n */\n private function executeRequest(callable $apiCall)\n {\n if (! $this->rateLimiter->canMakeRequest($this->config)) {\n $retryAfter = $this->rateLimiter->requestAvailableIn($this->config);\n\n $this->log->warning('[Hubspot] Rate limit exceeded, deferring request', [\n 'team_id' => $this->config->team_id,\n 'config_id' => $this->config->getId(),\n 'retry_after' => $retryAfter,\n ]);\n\n throw new RateLimitException(\n 'Hubspot rate limit reached for configuration ' . $this->config->getId(),\n $retryAfter,\n );\n }\n\n $this->rateLimiter->incrementRequestCount($this->config);\n\n try {\n return $apiCall();\n } catch (Throwable $e) {\n if ($this->isHubspotRateLimit($e)) {\n $retryAfter = $this->parseRetryAfter($e);\n\n $this->log->warning('[Hubspot] Received 429 from API', [\n 'team_id' => $this->config->team_id,\n 'config_id' => $this->config->getId(),\n 'retry_after' => $retryAfter,\n 'reason' => $e->getMessage(),\n ]);\n\n throw new RateLimitException('Hubspot returned 429', $retryAfter, $e);\n }\n\n throw $e;\n }\n }\n\n private function isHubspotRateLimit(Throwable $e): bool\n {\n return method_exists($e, 'getCode') && (int) $e->getCode() === 429;\n }\n\n private function parseRetryAfter(Throwable $e): int\n {\n if (method_exists($e, 'getResponseHeaders')) {\n $headers = $e->getResponseHeaders() ?: [];\n $value = $headers['Retry-After'] ?? $headers['retry-after'] ?? null;\n if (is_array($value)) {\n $value = $value[0] ?? null;\n }\n if (is_numeric($value)) {\n return (int) $value;\n }\n }\n\n return 10;\n }\n\n public function getMinimumApiVersion(): string\n {\n return self::MIN_API_VERSION;\n }\n\n public function getInstance(): Factory\n {\n return new Factory([\n 'key' => $this->accessToken,\n 'oauth2' => true,\n 'base_url' => $this->baseUrl,\n ]);\n }\n\n public function getNewInstance(): Discovery\n {\n return \\HubSpot\\Factory::createWithAccessToken($this->accessToken);\n }\n\n /**\n * Secondly and daily limits for Hubspot API\n *\n * Product Tier: Free & Starter | Professional & Enterprise | API add-on (any tier)\n * Burst: 100/10 seconds | 150/10 seconds | 200/10 seconds\n * Daily: 250,000 | 500,000 | 1,000,000\n *\n * Official documentation states: The search endpoints are rate limited to five requests per second.\n * Since with 5 RPS were still hitting secondly rate limits we lowered it to 4\n */\n public function getPaginatedData(array $payload, string $type, int $offset = 0): array\n {\n $total = 0;\n $lastId = null;\n $rows = [];\n foreach ($this->getPaginatedDataGenerator($payload, $type, $offset, $total, $lastId) as $row) {\n $rows[] = $row;\n }\n\n return ['results' => $rows, 'total' => $total, 'last_record' => $lastId];\n }\n\n /**\n * @throws HubspotException\n * @throws SocialAccountTokenInvalidException\n * @throws BadRequest\n */\n public function getPaginatedDataGenerator(\n array $payload,\n string $type,\n int $offset = 0,\n int &$total = 0,\n ?string &$lastRecordId = null\n ): \\Generator {\n return $this->paginationService->getPaginatedDataGenerator(\n $this,\n $payload,\n $type,\n $offset,\n $total,\n $lastRecordId\n );\n }\n\n /**\n * @throws DealApiException\n * @throws CrmException\n */\n public function getOpportunityById(string $crmId, array $fields): array\n {\n try {\n $deal = $this->executeRequest(fn () => $this->getNewInstance()->crm()->deals()->basicApi()->getById(\n $crmId,\n implode(',', $fields),\n 'companies,contacts'\n ));\n } catch (DealApiException $e) {\n $this->log->info('[Hubspot] Failed to fetch opportunity', [\n 'crm_id' => $crmId,\n 'reason' => $e->getMessage(),\n ]);\n\n throw $e;\n }\n\n if (! $deal instanceof DealWithAssociations) {\n throw new CrmException('Deal not found');\n }\n\n return [\n 'id' => $deal->getId(),\n 'properties' => $deal->getProperties(),\n 'associations' => $deal->getAssociations(),\n ];\n }\n\n /**\n * Generic batch read method for HubSpot objects\n *\n * @param string $objectType The object type ('deals', 'companies', 'contacts')\n * @param array<string> $crmIds Array of HubSpot object IDs (max 100)\n * @param array<string> $fields Array of property names to fetch\n *\n * @return array<string, array> Array keyed by CRM ID with object data\n */\n private function batchReadObjects(string $objectType, array $crmIds, array $fields): array\n {\n if (empty($crmIds)) {\n return [];\n }\n\n $this->validateBatchSize($objectType, $crmIds);\n $this->ensureValidToken();\n\n try {\n $batchConfig = $this->createBatchConfiguration($objectType);\n $batchReadRequest = $this->prepareBatchRequest($batchConfig, $crmIds, $fields);\n $response = $batchConfig['api']->read($batchReadRequest);\n\n $this->validateApiResponse($response, $objectType);\n\n $results = $this->processApiResults($response);\n $this->logBatchResults($objectType, $crmIds, $results);\n\n return $results;\n } catch (\\Throwable $e) {\n $this->handleBatchError($e, $objectType, $crmIds);\n }\n }\n\n private function validateBatchSize(string $objectType, array $crmIds): void\n {\n if (count($crmIds) > 100) {\n throw new \\InvalidArgumentException(\"Batch size cannot exceed 100 {$objectType}\");\n }\n }\n\n private function createBatchConfiguration(string $objectType): array\n {\n $configurations = [\n 'deals' => [\n 'batchReadRequest' => new \\HubSpot\\Client\\Crm\\Deals\\Model\\BatchReadInputSimplePublicObjectId(),\n 'inputClass' => \\HubSpot\\Client\\Crm\\Deals\\Model\\SimplePublicObjectId::class,\n 'api' => $this->getNewInstance()->crm()->deals()->batchApi(),\n ],\n 'companies' => [\n 'batchReadRequest' => new \\HubSpot\\Client\\Crm\\Companies\\Model\\BatchReadInputSimplePublicObjectId(),\n 'inputClass' => \\HubSpot\\Client\\Crm\\Companies\\Model\\SimplePublicObjectId::class,\n 'api' => $this->getNewInstance()->crm()->companies()->batchApi(),\n ],\n 'contacts' => [\n 'batchReadRequest' => new \\HubSpot\\Client\\Crm\\Contacts\\Model\\BatchReadInputSimplePublicObjectId(),\n 'inputClass' => \\HubSpot\\Client\\Crm\\Contacts\\Model\\SimplePublicObjectId::class,\n 'api' => $this->getNewInstance()->crm()->contacts()->batchApi(),\n ],\n ];\n\n if (! isset($configurations[$objectType])) {\n throw new \\InvalidArgumentException(\"Unsupported object type: {$objectType}\");\n }\n\n return $configurations[$objectType];\n }\n\n private function prepareBatchRequest(array $batchConfig, array $crmIds, array $fields): object\n {\n $batchReadRequest = $batchConfig['batchReadRequest'];\n $inputClass = $batchConfig['inputClass'];\n\n $inputs = array_map(function ($crmId) use ($inputClass) {\n $input = new $inputClass();\n $input->setId($crmId);\n\n return $input;\n }, $crmIds);\n\n $batchReadRequest->setInputs($inputs);\n $batchReadRequest->setProperties($fields);\n\n return $batchReadRequest;\n }\n\n private function validateApiResponse($response, string $objectType): void\n {\n if (! $response) {\n throw new CrmException(\"HubSpot API returned null response for {$objectType} batch read\");\n }\n }\n\n private function processApiResults($response): array\n {\n $results = [];\n $responseResults = $response->getResults();\n\n if ($responseResults) {\n foreach ($responseResults as $object) {\n if ($object && $object->getId()) {\n $results[$object->getId()] = [\n 'id' => $object->getId(),\n 'properties' => $object->getProperties() ?: [],\n ];\n }\n }\n }\n\n return $results;\n }\n\n private function logBatchResults(string $objectType, array $crmIds, array $results): void\n {\n $this->log->info(\"[HubSpot] Batch fetched {$objectType}\", [\n 'requested_count' => count($crmIds),\n 'returned_count' => count($results),\n 'crm_ids' => $crmIds,\n ]);\n }\n\n private function handleBatchError(\\Throwable $e, string $objectType, array $crmIds): void\n {\n $errorMessage = $e->getMessage() ?: 'Unknown error';\n $errorTrace = $e->getTraceAsString() ?: 'No trace available';\n\n $this->log->error(\"[HubSpot] Failed to batch fetch {$objectType}\", [\n 'crm_ids' => $crmIds,\n 'error' => $errorMessage,\n 'trace' => $errorTrace,\n ]);\n\n throw new CrmException(\"Failed to batch fetch {$objectType}: \" . $errorMessage);\n }\n\n /**\n * Batch read multiple opportunities by their CRM IDs\n *\n * @param array<string> $crmIds Array of HubSpot deal IDs (max 100)\n * @param array<string> $fields Array of property names to fetch\n *\n * @return array<string, array> Array keyed by CRM ID with opportunity data\n */\n public function getOpportunitiesByIds(array $crmIds, array $fields): array\n {\n return $this->batchReadObjects('deals', $crmIds, $fields);\n }\n\n /**\n * Batch read multiple companies by their CRM IDs\n *\n * @param array<string> $crmIds Array of HubSpot company IDs (max 100)\n * @param array<string> $fields Array of property names to fetch\n *\n * @return array<string, array> Array keyed by CRM ID with company data\n */\n public function getCompaniesByIds(array $crmIds, array $fields): array\n {\n return $this->batchReadObjects('companies', $crmIds, $fields);\n }\n\n /**\n * Batch read multiple contacts by their CRM IDs\n *\n * @param array<string> $crmIds Array of HubSpot contact IDs (max 100)\n * @param array<string> $fields Array of property names to fetch\n *\n * @return array<string, array> Array keyed by CRM ID with contact data\n */\n public function getContactsByIds(array $crmIds, array $fields): array\n {\n return $this->batchReadObjects('contacts', $crmIds, $fields);\n }\n\n /**\n * @throws CompanyApiException\n * @throws CrmException\n */\n public function getAccountById(string $crmId, array $fields): array\n {\n try {\n $company = $this->getNewInstance()->crm()->companies()->basicApi()->getById(\n $crmId,\n implode(',', $fields),\n );\n } catch (CompanyApiException $e) {\n $this->log->info('[Hubspot] Failed to fetch account', [\n 'crm_id' => $crmId,\n 'reason' => $e->getMessage(),\n ]);\n\n throw $e;\n }\n\n if (! $company instanceof CompaniesWithAssociations) {\n throw new CrmException('Account not found');\n }\n\n return [\n 'id' => $company->getId(),\n 'properties' => $company->getProperties(),\n ];\n }\n\n /**\n * @throws ContactApiException\n * @throws CrmException\n */\n public function getContactById(string $crmId, array $fields): array\n {\n try {\n $contact = $this->getNewInstance()->crm()->contacts()->basicApi()->getById(\n $crmId,\n implode(',', $fields)\n );\n } catch (ContactApiException $e) {\n $this->log->info('[Hubspot] Failed to fetch contact', [\n 'crm_id' => $crmId,\n 'reason' => $e->getMessage(),\n ]);\n\n throw $e;\n }\n\n if (! $contact instanceof ContactsWithAssociations) {\n throw new CrmException('Contact not found');\n }\n\n return [\n 'id' => $contact->getId(),\n 'properties' => $contact->getProperties(),\n ];\n }\n\n /**\n * This is email search request that Hubspot offers as GET (more generous quota)\n */\n public function getContactByEmail(string $email, array $fields = []): array\n {\n try {\n $contact = $this->getNewInstance()->crm()->contacts()->basicApi()->getById(\n $email,\n implode(',', $fields),\n null,\n false,\n 'email'\n );\n\n return [\n 'id' => $contact->getId(),\n 'properties' => $contact->getProperties(),\n ];\n } catch (ContactApiException $e) {\n $this->log->info('[Hubspot] Failed to fetch contact', [\n 'email' => $email,\n 'reason' => $e->getMessage(),\n ]);\n\n return [];\n }\n }\n\n /**\n * @throws CrmException\n */\n public function fetchProperty(string $objectType, string $propertyId): Property\n {\n $result = $this->getNewInstance()->crm()->properties()->coreApi()->getByName($objectType, $propertyId);\n\n if (! $result instanceof Property) {\n $this->log->error('[Hubspot] Failed to fetch property', [\n 'object_type' => $objectType,\n 'property_id' => $propertyId,\n 'reason' => $result->getMessage(),\n ]);\n\n throw new CrmException('Failed to fetch property');\n }\n\n return $result;\n }\n\n /**\n * @return array<CrmFieldOption>\n */\n public function fetchPropertyOptions(string $objectType, string $propertyId): array\n {\n /** @var array<CrmFieldOption> */\n return $this->fetchProperty($objectType, $propertyId)->getOptions();\n }\n\n /**\n * @return array<array{id:string, label:string, deleted:bool}>\n */\n public function fetchCallDispositions(): array\n {\n /** @var Response $response */\n $response = $this->getInstance()->engagements()->getCallDispositions();\n\n /**\n * @var array<array{\n * id:string,\n * label:string,\n * deleted: bool\n * }>\n */\n return $response->toArray();\n }\n\n /**\n * @return array<CrmFieldOption>\n */\n public function fetchOpportunityPipelineStages(): array\n {\n $stages = [];\n $apiResponse = $this->getNewInstance()->crm()->pipelines()->pipelinesApi()->getAll('deals');\n\n if ($apiResponse instanceof Error) {\n $this->log->error('[Hubspot] Failed to fetch opportunity pipelines', [\n 'reason' => $apiResponse->getMessage(),\n ]);\n\n return [];\n }\n\n foreach ($apiResponse->getResults() as $pipeline) {\n $pipelineStages = array_map(\n static function (PipelineStage $stage) {\n return [\n 'id' => $stage->getId(),\n 'label' => $stage->getLabel(),\n ];\n },\n $pipeline->getStages()\n );\n\n $stages = array_merge($stages, $pipelineStages);\n }\n\n return $stages;\n }\n\n public function fetchOpportunityPipelines(): array\n {\n $pipelines = [];\n\n try {\n $apiResponse = $this->makeRequest('/crm/v3/pipelines/deals');\n } catch (\\Exception $e) {\n $this->log->info('[Hubspot] Failed to fetch opportunity pipelines', [\n 'reason' => $e->getMessage(),\n ]);\n\n return [];\n }\n\n $response = $apiResponse->toArray();\n\n foreach ($response['results'] as $pipeline) {\n $pipelines[] = [\n 'id' => $pipeline['id'],\n 'label' => $pipeline['label'],\n ];\n }\n\n return $pipelines;\n }\n\n /**\n * @return array<CrmFieldOption>\n */\n public function fetchMeetingOutcomeFieldOptions(Field $field): array\n {\n return $field->getCrmProviderId() === 'meetingOutcome'\n ? $this->fetchMeetingOutcomeTypes()\n : $this->fetchCallActivityTypes();\n }\n\n public function fetchMeetingOutcomeTypes(): array\n {\n return $this->extractMeetingTypeOptions(\n 'https://api.hubapi.com/crm/v3/properties/meeting/hs_meeting_outcome'\n );\n }\n\n public function fetchCallActivityTypes(): array\n {\n return $this->extractMeetingTypeOptions(\n 'https://api.hubapi.com/crm/v3/properties/call/hs_activity_type'\n );\n }\n\n private function extractMeetingTypeOptions(string $endpoint): array\n {\n /** @var Response $response */\n $response = $this->getInstance()\n ->getClient()\n ->request('GET', $endpoint);\n\n /**\n * @var array<array{\n * value: string,\n * label: string,\n * displayOrder: int\n * }> $optionData\n */\n $optionData = $response->toArray()['options'] ?? [];\n\n $options = [];\n foreach ($optionData as $item) {\n $options[] = [\n 'id' => $item['value'],\n 'value' => $item['value'],\n 'label' => $item['label'],\n 'display_order' => $item['displayOrder'],\n ];\n }\n\n return $options;\n }\n\n /**\n * @return array<CrmFieldOption>\n */\n public function fetchDispositionFieldOptions(): array\n {\n $options = [];\n\n $dispositions = $this->fetchCallDispositions();\n\n foreach ($dispositions as $disposition) {\n if ($disposition['deleted'] !== false) {\n continue;\n }\n\n $option['value'] = $disposition['id'];\n $option['id'] = $disposition['id'];\n $option['label'] = $disposition['label'];\n\n $options[] = $option;\n }\n\n return $options;\n }\n\n /**\n * @return array<CrmFieldOption>\n */\n public function fetchOpportunityFieldOptions(Field $field): array\n {\n if ($field->isStageField()) {\n return $this->fetchOpportunityPipelineStages();\n }\n\n if ($field->isPipelineField()) {\n return $this->fetchOpportunityPipelines();\n }\n\n return $this->fetchPropertyOptions('deals', $field->getCrmProviderId());\n }\n\n /**\n * @throws BadRequest\n * @throws HubspotException\n */\n public function makeRequest(string $endpoint, $method = 'GET', $payload = [], ?string $queryString = null)\n {\n $endpoint = self::BASE_URL . $endpoint;\n\n if ($method === 'GET') {\n $response = $this->getInstance()->getClient()?->request(\n method: $method,\n endpoint: $endpoint,\n query_string: $queryString\n );\n } else {\n $response = $this->getInstance()->getClient()->request($method, $endpoint, [\n 'json' => ($payload),\n ]);\n }\n\n $max = $response->getHeaderLine('X-HubSpot-RateLimit-Max'); // \"110\"\n $remaining = $response->getHeaderLine('X-HubSpot-RateLimit-Remaining'); // \"109\"\n $interval = $response->getHeaderLine('X-HubSpot-RateLimit-Interval-Milliseconds'); // \"10000\"\n $body = json_decode((string) $response->getBody(), true);\n\n \\Illuminate\\Support\\Facades\\Log::channel('custom_channel')->info('$max ' . PHP_EOL . print_r($max, true));\n \\Illuminate\\Support\\Facades\\Log::channel('custom_channel')->info('$remaining ' . PHP_EOL . print_r($remaining, true));\n \\Illuminate\\Support\\Facades\\Log::channel('custom_channel')->info('$interval ' . PHP_EOL . print_r($interval, true));\n \\Illuminate\\Support\\Facades\\Log::channel('custom_channel')->info('$body ' . PHP_EOL . print_r($body, true));\n\n return $response;\n }\n\n /**\n * @throws BadRequest\n * @throws HubspotException\n */\n public function createMeeting(array $payload): Response\n {\n $endpoint = '/crm/v3/objects/meetings';\n\n return $this->makeRequest($endpoint, 'POST', $payload);\n }\n\n /**\n * @throws BadRequest\n * @throws HubspotException\n */\n public function updateMeeting(string $meetingId, array $payload): Response\n {\n $endpoint = '/crm/v3/objects/meetings/' . $meetingId;\n\n return $this->makeRequest($endpoint, 'PATCH', $payload);\n }\n\n /**\n * @throws \\Exception\n */\n public function createNote(\n string $body,\n string $ownerId,\n int $timestamp,\n string $objectId,\n NoteObject $noteObject\n ): ?string {\n try {\n $noteInput = new SimplePublicObjectInput([\n 'properties' => [\n 'hs_note_body' => $body,\n 'hubspot_owner_id' => $ownerId,\n 'hs_timestamp' => $timestamp,\n ],\n ]);\n\n // Create note\n $note = $this->getNewInstance()->crm()->objects()->basicApi()->create('note', $noteInput);\n\n $this->getNewInstance()->crm()->objects()->associationsApi()->create(\n 'note',\n $note->getId(),\n $this->getNoteObject($noteObject),\n $objectId,\n $this->getNoteAssociationType($noteObject),\n );\n\n return $note->getId();\n } catch (\\Exception $e) {\n $this->log->error('[Hubspot] Failed to create note', [\n 'objectId' => $objectId,\n 'noteObject' => $noteObject->getObjectType(),\n 'reason' => $e->getMessage(),\n ]);\n\n \\Sentry::captureException($e);\n }\n\n return null;\n }\n\n public function updateEngagement(string $objectId, array $engagement, array $metadata): void\n {\n $this->getInstance()->engagements()->update($objectId, $engagement, $metadata);\n }\n\n public function getEngagementData(string $engagementId): array\n {\n $engagement = $this->getInstance()->engagements()->get($engagementId);\n\n return $engagement->toArray();\n }\n\n public function createEngagement(array $engagement, array $associations, array $metadata): Response\n {\n return $this->getInstance()\n ->engagements()\n ->create($engagement, $associations, $metadata);\n }\n\n public function isUnauthorizedException(\\Exception $e): bool\n {\n // Check for specific HubSpot API exception types first\n if ($e instanceof BadRequest) {\n // BadRequest can contain 401 status codes\n return $e->getCode() === 401;\n }\n\n // Check for HTTP client exceptions with status codes\n if ($e instanceof \\GuzzleHttp\\Exception\\RequestException && $e->hasResponse()) {\n $response = $e->getResponse();\n if ($response !== null) {\n return $response->getStatusCode() === 401;\n }\n }\n\n // Check for Guzzle HTTP exceptions\n if ($e instanceof \\GuzzleHttp\\Exception\\ClientException) {\n return $e->getCode() === 401;\n }\n\n // Fallback to string matching as last resort, but be more specific\n $message = strtolower($e->getMessage());\n\n return str_contains($message, '401 unauthorized') ||\n str_contains($message, 'http 401') ||\n str_contains($message, 'status code 401') ||\n (preg_match('/\\b401\\b/', $message) && str_contains($message, 'unauthorized'));\n }\n\n /**\n * Validates and refreshes the access token if needed before API requests.\n * This ensures long-running processes don't fail due to token expiration.\n *\n * @throws SocialAccountTokenInvalidException\n */\n public function ensureValidToken(): void\n {\n if ($this->oauthAccount === null) {\n return;\n }\n\n $newToken = $this->tokenManager->ensureValidToken($this->oauthAccount);\n if ($newToken !== null) {\n $this->accessToken = $newToken;\n }\n }\n\n public function getConfig()\n {\n return $this->config;\n }\n\n // returns only active (archived=false)\n public function getOwners(): array\n {\n return $this->getNewInstance()->crm()->owners()->getAll();\n }\n\n /**\n * @param bool $archived\n *\n * @return array<Owner>|[]\n */\n public function getOwnersArchived(bool $archived = true): array\n {\n $endpoint = '/crm/v3/owners';\n $queryParams = [\n 'archived' => $archived ? 'true' : 'false',\n ];\n $queryString = http_build_query($queryParams);\n\n $owners = [];\n\n try {\n $response = $this->makeRequest(endpoint: $endpoint, queryString: $queryString);\n $responseData = $response?->toArray();\n\n foreach ($responseData['results'] as $result) {\n try {\n $owners[] = Owner::create($result);\n } catch (Throwable $e) {\n $this->log->error('[HubSpot] Failed to process owner data', [\n 'result' => $result,\n 'error' => $e->getMessage(),\n ]);\n\n continue;\n }\n }\n } catch (Throwable $e) {\n $this->log->error('HubSpot] Failed to fetch owners', [\n 'archived' => $archived,\n 'error' => $e->getMessage(),\n ]);\n\n return [];\n }\n\n return $owners;\n }\n\n public function getMeeting(string $engagementId): ObjectWithAssociations\n {\n return $this->getNewInstance()->crm()->objects()->basicApi()\n ->getById('meeting', $engagementId, null, 'contact,company,deal');\n }\n\n public function deleteEngagement(string $engagementId): void\n {\n $this->getInstance()->engagements()->delete((int) $engagementId);\n }\n\n public function getAssociationsData(array $ids, string $fromObject, string $toObject): array\n {\n $associationData = [];\n $idChunks = array_chunk($ids, self::ASSOCIATIONS_BATCH_SIZE_LIMIT);\n\n foreach ($idChunks as $idChunk) {\n try {\n $batchInput = new \\HubSpot\\Client\\Crm\\Associations\\Model\\BatchInputPublicObjectId();\n $batchInput->setInputs(array_map(function ($id) {\n $publicObjectId = new \\HubSpot\\Client\\Crm\\Associations\\Model\\PublicObjectId();\n $publicObjectId->setId($id);\n\n return $publicObjectId;\n }, $idChunk));\n\n $associatedObjectsData = $this\n ->getNewInstance()\n ->crm()\n ->associations()\n ->batchApi()\n ->read($fromObject, $toObject, $batchInput);\n\n if ($associatedObjectsData instanceof \\HubSpot\\Client\\Crm\\Associations\\Model\\BatchResponsePublicAssociationMulti) {\n foreach ($associatedObjectsData->getResults() as $association) {\n $from = $association->getFrom()->getId();\n $toAssociations = $association->getTo();\n\n if (! empty($toAssociations)) {\n $associationData[$from] = array_map(function ($item) {\n return $item->getId();\n }, $toAssociations);\n }\n }\n }\n } catch (\\Exception $e) {\n $this->log->error('[Hubspot] Failed to fetch associations', [\n 'from_object' => $fromObject,\n 'to_object' => $toObject,\n 'reason' => $e->getMessage(),\n ]);\n }\n }\n\n return $associationData;\n }\n\n /**\n * @throws \\Exception\n */\n private function getNoteAssociationType(NoteObject $noteObject): string\n {\n return match($noteObject) {\n NoteObject::Opportunity => 'note_to_deal',\n NoteObject::Lead, NoteObject::Contact => 'note_to_contact', // or 'note_to_lead' if your portal supports it\n NoteObject::Account => 'note_to_company',\n NoteObject::Call, NoteObject::Event => throw new \\Exception('Not supported'),\n };\n }\n\n /**\n * @throws \\Exception\n */\n private function getNoteObject(NoteObject $noteObject): string\n {\n return match($noteObject) {\n NoteObject::Opportunity => 'deal',\n NoteObject::Lead, NoteObject::Contact => 'contact',\n NoteObject::Account => 'company',\n NoteObject::Call, NoteObject::Event => throw new \\Exception('Not supported'),\n };\n }\n\n public function addAssociations(string $objectType, string $associationType, array $payload): Response\n {\n $endpoint = \"/crm/v4/associations/$objectType/$associationType/batch/create\";\n\n return $this->makeRequest($endpoint, 'POST', $payload);\n }\n\n public function removeAssociations(string $objectType, string $associationType, array $payload): Response\n {\n $endpoint = \"/crm/v4/associations/$objectType/$associationType/batch/archive\";\n\n return $this->makeRequest($endpoint, 'POST', $payload);\n }\n}","role_description":"text entry area","is_enabled":true,"is_focused":true,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"Project","depth":3,"on_screen":false,"role_description":"text"},{"role":"AXButton","text":"Project","depth":3,"bounds":{"left":0.011968086,"top":0.047885075,"width":0.024268618,"height":0.024740623},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"New File or Directory…","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.008643617,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Expand Selected","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.008643617,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Collapse All","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.008643617,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Options","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.008643617,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Hide","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.008643617,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false}]...
|
860006888846957654
|
2072772790585792612
|
visual_change
|
accessibility
|
NULL
|
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
Execute
Explain Plan
Browse Query History
View Parameters
Open Query Execution Settings…
In-Editor Results
Tx: Auto
Cancel Running Statements
Playground
jiminny
Sync Changes
Hide This Notification
Code changed:
Hide
6
1
6
Previous Highlighted Error
Next Highlighted Error
# [PASSWORD_DOTS] HS [PASSWORD_DOTS]
select * from teams where id = 2; # 2
select * from features; # 2
select * from team_features where team_id = 2; # 2
select * from crm_configurations where id = 2; # 2
select * from users where team_id = 2; #
select * from playbooks where team_id = 2; # event 38
select * from playbook_categories where playbook_id = 38; #
SELECT * FROM activities WHERE crm_configuration_id = 2 and crm_provider_id is not null order by id desc;
[URL_WITH_CREDENTIALS] CrmFieldOption array{id:string, label:string, value?:string}
*/
class Client extends BaseClient implements HubspotClientInterface
{
public const string MIN_API_VERSION = '2';
public const string BASE_URL = '[URL_WITH_CREDENTIALS] T
* @param callable(): T $apiCall
* @return T
*
* @throws RateLimitException
*/
private function executeRequest(callable $apiCall)
{
if (! $this->rateLimiter->canMakeRequest($this->config)) {
$retryAfter = $this->rateLimiter->requestAvailableIn($this->config);
$this->log->warning('[Hubspot] Rate limit exceeded, deferring request', [
'team_id' => $this->config->team_id,
'config_id' => $this->config->getId(),
'retry_after' => $retryAfter,
]);
throw new RateLimitException(
'Hubspot rate limit reached for configuration ' . $this->config->getId(),
$retryAfter,
);
}
$this->rateLimiter->incrementRequestCount($this->config);
try {
return $apiCall();
} catch (Throwable $e) {
if ($this->isHubspotRateLimit($e)) {
$retryAfter = $this->parseRetryAfter($e);
$this->log->warning('[Hubspot] Received 429 from API', [
'team_id' => $this->config->team_id,
'config_id' => $this->config->getId(),
'retry_after' => $retryAfter,
'reason' => $e->getMessage(),
]);
throw new RateLimitException('Hubspot returned 429', $retryAfter, $e);
}
throw $e;
}
}
private function isHubspotRateLimit(Throwable $e): bool
{
return method_exists($e, 'getCode') && (int) $e->getCode() === 429;
}
private function parseRetryAfter(Throwable $e): int
{
if (method_exists($e, 'getResponseHeaders')) {
$headers = $e->getResponseHeaders() ?: [];
$value = $headers['Retry-After'] ?? $headers['retry-after'] ?? null;
if (is_array($value)) {
$value = $value[0] ?? null;
}
if (is_numeric($value)) {
return (int) $value;
}
}
return 10;
}
public function getMinimumApiVersion(): string
{
return self::MIN_API_VERSION;
}
public function getInstance(): Factory
{
return new Factory([
'key' => $this->accessToken,
'oauth2' => true,
'base_url' => $this->baseUrl,
]);
}
public function getNewInstance(): Discovery
{
return \HubSpot\Factory::createWithAccessToken($this->accessToken);
}
/**
* Secondly and daily limits for Hubspot API
*
* Product Tier: Free & Starter | Professional & Enterprise | API add-on (any tier)
* Burst: 100/10 seconds | 150/10 seconds | 200/10 seconds
* Daily: 250,000 | 500,000 | 1,000,000
*
* Official documentation states: The search endpoints are rate limited to five requests per second.
* Since with 5 RPS were still hitting secondly rate limits we lowered it to 4
*/
public function getPaginatedData(array $payload, string $type, int $offset = 0): array
{
$total = 0;
$lastId = null;
$rows = [];
foreach ($this->getPaginatedDataGenerator($payload, $type, $offset, $total, $lastId) as $row) {
$rows[] = $row;
}
return ['results' => $rows, 'total' => $total, 'last_record' => $lastId];
}
/**
* @throws HubspotException
* @throws SocialAccountTokenInvalidException
* @throws BadRequest
*/
public function getPaginatedDataGenerator(
array $payload,
string $type,
int $offset = 0,
int &$total = 0,
?string &$lastRecordId = null
): \Generator {
return $this->paginationService->getPaginatedDataGenerator(
$this,
$payload,
$type,
$offset,
$total,
$lastRecordId
);
}
/**
* @throws DealApiException
* @throws CrmException
*/
public function getOpportunityById(string $crmId, array $fields): array
{
try {
$deal = $this->executeRequest(fn () => $this->getNewInstance()->crm()->deals()->basicApi()->getById(
$crmId,
implode(',', $fields),
'companies,contacts'
));
} catch (DealApiException $e) {
$this->log->info('[Hubspot] Failed to fetch opportunity', [
'crm_id' => $crmId,
'reason' => $e->getMessage(),
]);
throw $e;
}
if (! $deal instanceof DealWithAssociations) {
throw new CrmException('Deal not found');
}
return [
'id' => $deal->getId(),
'properties' => $deal->getProperties(),
'associations' => $deal->getAssociations(),
];
}
/**
* Generic batch read method for HubSpot objects
*
* @param string $objectType The object type ('deals', 'companies', 'contacts')
* @param array<string> $crmIds Array of HubSpot object IDs (max 100)
* @param array<string> $fields Array of property names to fetch
*
* @return array<string, array> Array keyed by CRM ID with object data
*/
private function batchReadObjects(string $objectType, array $crmIds, array $fields): array
{
if (empty($crmIds)) {
return [];
}
$this->validateBatchSize($objectType, $crmIds);
$this->ensureValidToken();
try {
$batchConfig = $this->createBatchConfiguration($objectType);
$batchReadRequest = $this->prepareBatchRequest($batchConfig, $crmIds, $fields);
$response = $batchConfig['api']->read($batchReadRequest);
$this->validateApiResponse($response, $objectType);
$results = $this->processApiResults($response);
$this->logBatchResults($objectType, $crmIds, $results);
return $results;
} catch (\Throwable $e) {
$this->handleBatchError($e, $objectType, $crmIds);
}
}
private function validateBatchSize(string $objectType, array $crmIds): void
{
if (count($crmIds) > 100) {
throw new \InvalidArgumentException("Batch size cannot exceed 100 {$objectType}");
}
}
private function createBatchConfiguration(string $objectType): array
{
$configurations = [
'deals' => [
'batchReadRequest' => new \HubSpot\Client\Crm\Deals\Model\BatchReadInputSimplePublicObjectId(),
'inputClass' => \HubSpot\Client\Crm\Deals\Model\SimplePublicObjectId::class,
'api' => $this->getNewInstance()->crm()->deals()->batchApi(),
],
'companies' => [
'batchReadRequest' => new \HubSpot\Client\Crm\Companies\Model\BatchReadInputSimplePublicObjectId(),
'inputClass' => \HubSpot\Client\Crm\Companies\Model\SimplePublicObjectId::class,
'api' => $this->getNewInstance()->crm()->companies()->batchApi(),
],
'contacts' => [
'batchReadRequest' => new \HubSpot\Client\Crm\Contacts\Model\BatchReadInputSimplePublicObjectId(),
'inputClass' => \HubSpot\Client\Crm\Contacts\Model\SimplePublicObjectId::class,
'api' => $this->getNewInstance()->crm()->contacts()->batchApi(),
],
];
if (! isset($configurations[$objectType])) {
throw new \InvalidArgumentException("Unsupported object type: {$objectType}");
}
return $configurations[$objectType];
}
private function prepareBatchRequest(array $batchConfig, array $crmIds, array $fields): object
{
$batchReadRequest = $batchConfig['batchReadRequest'];
$inputClass = $batchConfig['inputClass'];
$inputs = array_map(function ($crmId) use ($inputClass) {
$input = new $inputClass();
$input->setId($crmId);
return $input;
}, $crmIds);
$batchReadRequest->setInputs($inputs);
$batchReadRequest->setProperties($fields);
return $batchReadRequest;
}
private function validateApiResponse($response, string $objectType): void
{
if (! $response) {
throw new CrmException("HubSpot API returned null response for {$objectType} batch read");
}
}
private function processApiResults($response): array
{
$results = [];
$responseResults = $response->getResults();
if ($responseResults) {
foreach ($responseResults as $object) {
if ($object && $object->getId()) {
$results[$object->getId()] = [
'id' => $object->getId(),
'properties' => $object->getProperties() ?: [],
];
}
}
}
return $results;
}
private function logBatchResults(string $objectType, array $crmIds, array $results): void
{
$this->log->info("[HubSpot] Batch fetched {$objectType}", [
'requested_count' => count($crmIds),
'returned_count' => count($results),
'crm_ids' => $crmIds,
]);
}
private function handleBatchError(\Throwable $e, string $objectType, array $crmIds): void
{
$errorMessage = $e->getMessage() ?: 'Unknown error';
$errorTrace = $e->getTraceAsString() ?: 'No trace available';
$this->log->error("[HubSpot] Failed to batch fetch {$objectType}", [
'crm_ids' => $crmIds,
'error' => $errorMessage,
'trace' => $errorTrace,
]);
throw new CrmException("Failed to batch fetch {$objectType}: " . $errorMessage);
}
/**
* Batch read multiple opportunities by their CRM IDs
*
* @param array<string> $crmIds Array of HubSpot deal IDs (max 100)
* @param array<string> $fields Array of property names to fetch
*
* @return array<string, array> Array keyed by CRM ID with opportunity data
*/
public function getOpportunitiesByIds(array $crmIds, array $fields): array
{
return $this->batchReadObjects('deals', $crmIds, $fields);
}
/**
* Batch read multiple companies by their CRM IDs
*
* @param array<string> $crmIds Array of HubSpot company IDs (max 100)
* @param array<string> $fields Array of property names to fetch
*
* @return array<string, array> Array keyed by CRM ID with company data
*/
public function getCompaniesByIds(array $crmIds, array $fields): array
{
return $this->batchReadObjects('companies', $crmIds, $fields);
}
/**
* Batch read multiple contacts by their CRM IDs
*
* @param array<string> $crmIds Array of HubSpot contact IDs (max 100)
* @param array<string> $fields Array of property names to fetch
*
* @return array<string, array> Array keyed by CRM ID with contact data
*/
public function getContactsByIds(array $crmIds, array $fields): array
{
return $this->batchReadObjects('contacts', $crmIds, $fields);
}
/**
* @throws CompanyApiException
* @throws CrmException
*/
public function getAccountById(string $crmId, array $fields): array
{
try {
$company = $this->getNewInstance()->crm()->companies()->basicApi()->getById(
$crmId,
implode(',', $fields),
);
} catch (CompanyApiException $e) {
$this->log->info('[Hubspot] Failed to fetch account', [
'crm_id' => $crmId,
'reason' => $e->getMessage(),
]);
throw $e;
}
if (! $company instanceof CompaniesWithAssociations) {
throw new CrmException('Account not found');
}
return [
'id' => $company->getId(),
'properties' => $company->getProperties(),
];
}
/**
* @throws ContactApiException
* @throws CrmException
*/
public function getContactById(string $crmId, array $fields): array
{
try {
$contact = $this->getNewInstance()->crm()->contacts()->basicApi()->getById(
$crmId,
implode(',', $fields)
);
} catch (ContactApiException $e) {
$this->log->info('[Hubspot] Failed to fetch contact', [
'crm_id' => $crmId,
'reason' => $e->getMessage(),
]);
throw $e;
}
if (! $contact instanceof ContactsWithAssociations) {
throw new CrmException('Contact not found');
}
return [
'id' => $contact->getId(),
'properties' => $contact->getProperties(),
];
}
/**
* This is email search request that Hubspot offers as GET (more generous quota)
*/
public function getContactByEmail(string $email, array $fields = []): array
{
try {
$contact = $this->getNewInstance()->crm()->contacts()->basicApi()->getById(
$email,
implode(',', $fields),
null,
false,
'email'
);
return [
'id' => $contact->getId(),
'properties' => $contact->getProperties(),
];
} catch (ContactApiException $e) {
$this->log->info('[Hubspot] Failed to fetch contact', [
'email' => $email,
'reason' => $e->getMessage(),
]);
return [];
}
}
/**
* @throws CrmException
*/
public function fetchProperty(string $objectType, string $propertyId): Property
{
$result = $this->getNewInstance()->crm()->properties()->coreApi()->getByName($objectType, $propertyId);
if (! $result instanceof Property) {
$this->log->error('[Hubspot] Failed to fetch property', [
'object_type' => $objectType,
'property_id' => $propertyId,
'reason' => $result->getMessage(),
]);
throw new CrmException('Failed to fetch property');
}
return $result;
}
/**
* @return array<CrmFieldOption>
*/
public function fetchPropertyOptions(string $objectType, string $propertyId): array
{
/** @var array<CrmFieldOption> */
return $this->fetchProperty($objectType, $propertyId)->getOptions();
}
/**
* @return array<array{id:string, label:string, deleted:bool}>
*/
public function fetchCallDispositions(): array
{
/** @var Response $response */
$response = $this->getInstance()->engagements()->getCallDispositions();
/**
* @var array<array{
* id:string,
* label:string,
* deleted: bool
* }>
*/
return $response->toArray();
}
/**
* @return array<CrmFieldOption>
*/
public function fetchOpportunityPipelineStages(): array
{
$stages = [];
$apiResponse = $this->getNewInstance()->crm()->pipelines()->pipelinesApi()->getAll('deals');
if ($apiResponse instanceof Error) {
$this->log->error('[Hubspot] Failed to fetch opportunity pipelines', [
'reason' => $apiResponse->getMessage(),
]);
return [];
}
foreach ($apiResponse->getResults() as $pipeline) {
$pipelineStages = array_map(
static function (PipelineStage $stage) {
return [
'id' => $stage->getId(),
'label' => $stage->getLabel(),
];
},
$pipeline->getStages()
);
$stages = array_merge($stages, $pipelineStages);
}
return $stages;
}
public function fetchOpportunityPipelines(): array
{
$pipelines = [];
try {
$apiResponse = $this->makeRequest('/crm/v3/pipelines/deals');
} catch (\Exception $e) {
$this->log->info('[Hubspot] Failed to fetch opportunity pipelines', [
'reason' => $e->getMessage(),
]);
return [];
}
$response = $apiResponse->toArray();
foreach ($response['results'] as $pipeline) {
$pipelines[] = [
'id' => $pipeline['id'],
'label' => $pipeline['label'],
];
}
return $pipelines;
}
/**
* @return array<CrmFieldOption>
*/
public function fetchMeetingOutcomeFieldOptions(Field $field): array
{
return $field->getCrmProviderId() === 'meetingOutcome'
? $this->fetchMeetingOutcomeTypes()
: $this->fetchCallActivityTypes();
}
public function fetchMeetingOutcomeTypes(): array
{
return $this->extractMeetingTypeOptions(
'[URL_WITH_CREDENTIALS] Response $response */
$response = $this->getInstance()
->getClient()
->request('GET', $endpoint);
/**
* @var array<array{
* value: string,
* label: string,
* displayOrder: int
* }> $optionData
*/
$optionData = $response->toArray()['options'] ?? [];
$options = [];
foreach ($optionData as $item) {
$options[] = [
'id' => $item['value'],
'value' => $item['value'],
'label' => $item['label'],
'display_order' => $item['displayOrder'],
];
}
return $options;
}
/**
* @return array<CrmFieldOption>
*/
public function fetchDispositionFieldOptions(): array
{
$options = [];
$dispositions = $this->fetchCallDispositions();
foreach ($dispositions as $disposition) {
if ($disposition['deleted'] !== false) {
continue;
}
$option['value'] = $disposition['id'];
$option['id'] = $disposition['id'];
$option['label'] = $disposition['label'];
$options[] = $option;
}
return $options;
}
/**
* @return array<CrmFieldOption>
*/
public function fetchOpportunityFieldOptions(Field $field): array
{
if ($field->isStageField()) {
return $this->fetchOpportunityPipelineStages();
}
if ($field->isPipelineField()) {
return $this->fetchOpportunityPipelines();
}
return $this->fetchPropertyOptions('deals', $field->getCrmProviderId());
}
/**
* @throws BadRequest
* @throws HubspotException
*/
public function makeRequest(string $endpoint, $method = 'GET', $payload = [], ?string $queryString = null)
{
$endpoint = self::BASE_URL . $endpoint;
if ($method === 'GET') {
$response = $this->getInstance()->getClient()?->request(
method: $method,
endpoint: $endpoint,
query_string: $queryString
);
} else {
$response = $this->getInstance()->getClient()->request($method, $endpoint, [
'json' => ($payload),
]);
}
$max = $response->getHeaderLine('X-HubSpot-RateLimit-Max'); // "110"
$remaining = $response->getHeaderLine('X-HubSpot-RateLimit-Remaining'); // "109"
$interval = $response->getHeaderLine('X-HubSpot-RateLimit-Interval-Milliseconds'); // "10000"
$body = json_decode((string) $response->getBody(), true);
\Illuminate\Support\Facades\Log::channel('custom_channel')->info('$max ' . PHP_EOL . print_r($max, true));
\Illuminate\Support\Facades\Log::channel('custom_channel')->info('$remaining ' . PHP_EOL . print_r($remaining, true));
\Illuminate\Support\Facades\Log::channel('custom_channel')->info('$interval ' . PHP_EOL . print_r($interval, true));
\Illuminate\Support\Facades\Log::channel('custom_channel')->info('$body ' . PHP_EOL . print_r($body, true));
return $response;
}
/**
* @throws BadRequest
* @throws HubspotException
*/
public function createMeeting(array $payload): Response
{
$endpoint = '/crm/v3/objects/meetings';
return $this->makeRequest($endpoint, 'POST', $payload);
}
/**
* @throws BadRequest
* @throws HubspotException
*/
public function updateMeeting(string $meetingId, array $payload): Response
{
$endpoint = '/crm/v3/objects/meetings/' . $meetingId;
return $this->makeRequest($endpoint, 'PATCH', $payload);
}
/**
* @throws \Exception
*/
public function createNote(
string $body,
string $ownerId,
int $timestamp,
string $objectId,
NoteObject $noteObject
): ?string {
try {
$noteInput = new SimplePublicObjectInput([
'properties' => [
'hs_note_body' => $body,
'hubspot_owner_id' => $ownerId,
'hs_timestamp' => $timestamp,
],
]);
// Create note
$note = $this->getNewInstance()->crm()->objects()->basicApi()->create('note', $noteInput);
$this->getNewInstance()->crm()->objects()->associationsApi()->create(
'note',
$note->getId(),
$this->getNoteObject($noteObject),
$objectId,
$this->getNoteAssociationType($noteObject),
);
return $note->getId();
} catch (\Exception $e) {
$this->log->error('[Hubspot] Failed to create note', [
'objectId' => $objectId,
'noteObject' => $noteObject->getObjectType(),
'reason' => $e->getMessage(),
]);
\Sentry::captureException($e);
}
return null;
}
public function updateEngagement(string $objectId, array $engagement, array $metadata): void
{
$this->getInstance()->engagements()->update($objectId, $engagement, $metadata);
}
public function getEngagementData(string $engagementId): array
{
$engagement = $this->getInstance()->engagements()->get($engagementId);
return $engagement->toArray();
}
public function createEngagement(array $engagement, array $associations, array $metadata): Response
{
return $this->getInstance()
->engagements()
->create($engagement, $associations, $metadata);
}
public function isUnauthorizedException(\Exception $e): bool
{
// Check for specific HubSpot API exception types first
if ($e instanceof BadRequest) {
// BadRequest can contain 401 status codes
return $e->getCode() === 401;
}
// Check for HTTP client exceptions with status codes
if ($e instanceof \GuzzleHttp\Exception\RequestException && $e->hasResponse()) {
$response = $e->getResponse();
if ($response !== null) {
return $response->getStatusCode() === 401;
}
}
// Check for Guzzle HTTP exceptions
if ($e instanceof \GuzzleHttp\Exception\ClientException) {
return $e->getCode() === 401;
}
// Fallback to string matching as last resort, but be more specific
$message = strtolower($e->getMessage());
return str_contains($message, '401 unauthorized') ||
str_contains($message, 'http 401') ||
str_contains($message, 'status code 401') ||
(preg_match('/\b401\b/', $message) && str_contains($message, 'unauthorized'));
}
/**
* Validates and refreshes the access token if needed before API requests.
* This ensures long-running processes don't fail due to token expiration.
*
* @throws SocialAccountTokenInvalidException
*/
public function ensureValidToken(): void
{
if ($this->oauthAccount === null) {
return;
}
$newToken = $this->tokenManager->ensureValidToken($this->oauthAccount);
if ($newToken !== null) {
$this->accessToken = $newToken;
}
}
public function getConfig()
{
return $this->config;
}
// returns only active (archived=false)
public function getOwners(): array
{
return $this->getNewInstance()->crm()->owners()->getAll();
}
/**
* @param bool $archived
*
* @return array<Owner>|[]
*/
public function getOwnersArchived(bool $archived = true): array
{
$endpoint = '/crm/v3/owners';
$queryParams = [
'archived' => $archived ? 'true' : 'false',
];
$queryString = http_build_query($queryParams);
$owners = [];
try {
$response = $this->makeRequest(endpoint: $endpoint, queryString: $queryString);
$responseData = $response?->toArray();
foreach ($responseData['results'] as $result) {
try {
$owners[] = Owner::create($result);
} catch (Throwable $e) {
$this->log->error('[HubSpot] Failed to process owner data', [
'result' => $result,
'error' => $e->getMessage(),
]);
continue;
}
}
} catch (Throwable $e) {
$this->log->error('HubSpot] Failed to fetch owners', [
'archived' => $archived,
'error' => $e->getMessage(),
]);
return [];
}
return $owners;
}
public function getMeeting(string $engagementId): ObjectWithAssociations
{
return $this->getNewInstance()->crm()->objects()->basicApi()
->getById('meeting', $engagementId, null, 'contact,company,deal');
}
public function deleteEngagement(string $engagementId): void
{
$this->getInstance()->engagements()->delete((int) $engagementId);
}
public function getAssociationsData(array $ids, string $fromObject, string $toObject): array
{
$associationData = [];
$idChunks = array_chunk($ids, self::ASSOCIATIONS_BATCH_SIZE_LIMIT);
foreach ($idChunks as $idChunk) {
try {
$batchInput = new \HubSpot\Client\Crm\Associations\Model\BatchInputPublicObjectId();
$batchInput->setInputs(array_map(function ($id) {
$publicObjectId = new \HubSpot\Client\Crm\Associations\Model\PublicObjectId();
$publicObjectId->setId($id);
return $publicObjectId;
}, $idChunk));
$associatedObjectsData = $this
->getNewInstance()
->crm()
->associations()
->batchApi()
->read($fromObject, $toObject, $batchInput);
if ($associatedObjectsData instanceof \HubSpot\Client\Crm\Associations\Model\BatchResponsePublicAssociationMulti) {
foreach ($associatedObjectsData->getResults() as $association) {
$from = $association->getFrom()->getId();
$toAssociations = $association->getTo();
if (! empty($toAssociations)) {
$associationData[$from] = array_map(function ($item) {
return $item->getId();
}, $toAssociations);
}
}
}
} catch (\Exception $e) {
$this->log->error('[Hubspot] Failed to fetch associations', [
'from_object' => $fromObject,
'to_object' => $toObject,
'reason' => $e->getMessage(),
]);
}
}
return $associationData;
}
/**
* @throws \Exception
*/
private function getNoteAssociationType(NoteObject $noteObject): string
{
return match($noteObject) {
NoteObject::Opportunity => 'note_to_deal',
NoteObject::Lead, NoteObject::Contact => 'note_to_contact', // or 'note_to_lead' if your portal supports it
NoteObject::Account => 'note_to_company',
NoteObject::Call, NoteObject::Event => throw new \Exception('Not supported'),
};
}
/**
* @throws \Exception
*/
private function getNoteObject(NoteObject $noteObject): string
{
return match($noteObject) {
NoteObject::Opportunity => 'deal',
NoteObject::Lead, NoteObject::Contact => 'contact',
NoteObject::Account => 'company',
NoteObject::Call, NoteObject::Event => throw new \Exception('Not supported'),
};
}
public function addAssociations(string $objectType, string $associationType, array $payload): Response
{
$endpoint = "/crm/v4/associations/$objectType/$associationType/batch/create";
return $this->makeRequest($endpoint, 'POST', $payload);
}
public function removeAssociations(string $objectType, string $associationType, array $payload): Response
{
$endpoint = "/crm/v4/associations/$objectType/$associationType/batch/archive";
return $this->makeRequest($endpoint, 'POST', $payload);
}
}
Project
Project
New File or Directory…
Expand Selected
Collapse All
Options
Hide...
|
2689
|
NULL
|
NULL
|
NULL
|
|
2692
|
111
|
16
|
2026-05-07T11:35:50.730697+00:00
|
/Users/lukas/.screenpipe/data/data/2026-05-07/1778 /Users/lukas/.screenpipe/data/data/2026-05-07/1778153750730_m1.jpg...
|
iTerm2
|
DEV (docker)
|
True
|
NULL
|
monitor_1
|
NULL
|
NULL
|
NULL
|
NULL
|
Last login: Thu May 7 09:44:56 on ttys006
Poetry Last login: Thu May 7 09:44:56 on ttys006
Poetry could not find a pyproject.toml file in /Users/lukas/jiminny/app or its parents
Poetry could not find a pyproject.toml file in /Users/lukas/jiminny/app or its parents
lukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/jiminny/app (JY-20773-fix-automated-reports-user-pilot-tracking) $ dev
root@docker_lamp_1:/home/jiminny# php artisan crm:sync-opportunity --teamId=2 --from='2026-05-01 00:00:00'
Syncing opportunity for Hubspot
Your HubSpot account has become disconnected. Please login to Jiminny to reconnect. skipping...
root@docker_lamp_1:/home/jiminny# php artisan jiminny:token-info -A 1499 -R
----------------------------------------------------------------------------------------------------
access_token => CNeR-JHgMxIZQlNQMl8kQEwrAgwACAkUAhIJBB4BAQEDBxiCiYwCIN7Y_Qwo0qwCMhTnG549n-YtNuc1jgj-2AsLPSmw3DoyQlNQMl8kQEwrAiUACBkGawEFThwBARIBAQEEATEEAQEBAQEBAQEBAQUBEggBAQEBAYlCFPAsBNZxoDp5kAcRyeBlQoE5SM7DSgNuYTFSAFoAYABo3tj9DHAAeAA
----------------------------------------------------------------------------------------------------
access_token_expires_at => 2026-05-07 11:41:20
----------------------------------------------------------------------------------------------------
refresh_token => d5ab04e2-2109-4c0b-b513-8cba1dd54371
----------------------------------------------------------------------------------------------------
refresh_token_expires_at =>
----------------------------------------------------------------------------------------------------
root@docker_lamp_1:/home/jiminny#
DOCKER
Close Tab
DEV (docker)
Close Tab
APP (-zsh)
Close Tab
-zsh
Close Tab
screenpipe"
Close Tab
-zsh
Close Tab
⌥⌘1
DEV (docker)...
|
[{"role":"AXTextArea","text [{"role":"AXTextArea","text":"Last login: Thu May 7 09:44:56 on ttys006\n\nPoetry could not find a pyproject.toml file in /Users/lukas/jiminny/app or its parents\n\nPoetry could not find a pyproject.toml file in /Users/lukas/jiminny/app or its parents\nlukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/jiminny/app (JY-20773-fix-automated-reports-user-pilot-tracking) $ dev\nroot@docker_lamp_1:/home/jiminny# php artisan crm:sync-opportunity --teamId=2 --from='2026-05-01 00:00:00'\nSyncing opportunity for Hubspot\nYour HubSpot account has become disconnected. Please login to Jiminny to reconnect. skipping...\nroot@docker_lamp_1:/home/jiminny# php artisan jiminny:token-info -A 1499 -R\n----------------------------------------------------------------------------------------------------\naccess_token => CNeR-JHgMxIZQlNQMl8kQEwrAgwACAkUAhIJBB4BAQEDBxiCiYwCIN7Y_Qwo0qwCMhTnG549n-YtNuc1jgj-2AsLPSmw3DoyQlNQMl8kQEwrAiUACBkGawEFThwBARIBAQEEATEEAQEBAQEBAQEBAQUBEggBAQEBAYlCFPAsBNZxoDp5kAcRyeBlQoE5SM7DSgNuYTFSAFoAYABo3tj9DHAAeAA\n----------------------------------------------------------------------------------------------------\naccess_token_expires_at => 2026-05-07 11:41:20\n----------------------------------------------------------------------------------------------------\nrefresh_token => d5ab04e2-2109-4c0b-b513-8cba1dd54371\n----------------------------------------------------------------------------------------------------\nrefresh_token_expires_at => \n----------------------------------------------------------------------------------------------------\nroot@docker_lamp_1:/home/jiminny#","depth":4,"bounds":{"left":0.0,"top":0.08777778,"width":1.0,"height":0.9122222},"on_screen":true,"lines":[{"char_start":0,"char_count":43,"bounds":{"left":0.0034722222,"top":0.08777778,"width":0.23888889,"height":0.02}},{"char_start":43,"char_count":1,"bounds":{"left":0.0034722222,"top":0.107777774,"width":0.0055555557,"height":0.02}},{"char_start":44,"char_count":87,"bounds":{"left":0.0034722222,"top":0.12777779,"width":0.48333332,"height":0.02}},{"char_start":131,"char_count":1,"bounds":{"left":0.0034722222,"top":0.14777778,"width":0.0055555557,"height":0.02}},{"char_start":132,"char_count":87,"bounds":{"left":0.0034722222,"top":0.16777778,"width":0.48333332,"height":0.02}},{"char_start":219,"char_count":114,"bounds":{"left":0.0034722222,"top":0.18777777,"width":0.6333333,"height":0.02}},{"char_start":333,"char_count":107,"bounds":{"left":0.0034722222,"top":0.20777778,"width":0.59444445,"height":0.02}},{"char_start":440,"char_count":32,"bounds":{"left":0.0034722222,"top":0.22777778,"width":0.17777778,"height":0.02}},{"char_start":472,"char_count":97,"bounds":{"left":0.0034722222,"top":0.24777777,"width":0.5388889,"height":0.02}},{"char_start":569,"char_count":76,"bounds":{"left":0.0034722222,"top":0.26777777,"width":0.42222223,"height":0.02}},{"char_start":645,"char_count":101,"bounds":{"left":0.0034722222,"top":0.28777778,"width":0.5611111,"height":0.02}},{"char_start":925,"char_count":57,"bounds":{"left":0.0034722222,"top":0.32777777,"width":0.31666666,"height":0.02}},{"char_start":982,"char_count":101,"bounds":{"left":0.0034722222,"top":0.34777778,"width":0.5611111,"height":0.02}},{"char_start":1083,"char_count":47,"bounds":{"left":0.0034722222,"top":0.36777776,"width":0.2611111,"height":0.02}},{"char_start":1130,"char_count":101,"bounds":{"left":0.0034722222,"top":0.38777778,"width":0.5611111,"height":0.02}},{"char_start":1231,"char_count":54,"bounds":{"left":0.0034722222,"top":0.4077778,"width":0.3,"height":0.02}},{"char_start":1285,"char_count":101,"bounds":{"left":0.0034722222,"top":0.42777777,"width":0.5611111,"height":0.02}},{"char_start":1386,"char_count":29,"bounds":{"left":0.0034722222,"top":0.44777778,"width":0.16111112,"height":0.02}},{"char_start":1415,"char_count":101,"bounds":{"left":0.0034722222,"top":0.4677778,"width":0.5611111,"height":0.02}},{"char_start":1516,"char_count":33,"bounds":{"left":0.0034722222,"top":0.48777777,"width":0.18333334,"height":0.02}}],"value":"Last login: Thu May 7 09:44:56 on ttys006\n\nPoetry could not find a pyproject.toml file in /Users/lukas/jiminny/app or its parents\n\nPoetry could not find a pyproject.toml file in /Users/lukas/jiminny/app or its parents\nlukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/jiminny/app (JY-20773-fix-automated-reports-user-pilot-tracking) $ dev\nroot@docker_lamp_1:/home/jiminny# php artisan crm:sync-opportunity --teamId=2 --from='2026-05-01 00:00:00'\nSyncing opportunity for Hubspot\nYour HubSpot account has become disconnected. Please login to Jiminny to reconnect. skipping...\nroot@docker_lamp_1:/home/jiminny# php artisan jiminny:token-info -A 1499 -R\n----------------------------------------------------------------------------------------------------\naccess_token => CNeR-JHgMxIZQlNQMl8kQEwrAgwACAkUAhIJBB4BAQEDBxiCiYwCIN7Y_Qwo0qwCMhTnG549n-YtNuc1jgj-2AsLPSmw3DoyQlNQMl8kQEwrAiUACBkGawEFThwBARIBAQEEATEEAQEBAQEBAQEBAQUBEggBAQEBAYlCFPAsBNZxoDp5kAcRyeBlQoE5SM7DSgNuYTFSAFoAYABo3tj9DHAAeAA\n----------------------------------------------------------------------------------------------------\naccess_token_expires_at => 2026-05-07 11:41:20\n----------------------------------------------------------------------------------------------------\nrefresh_token => d5ab04e2-2109-4c0b-b513-8cba1dd54371\n----------------------------------------------------------------------------------------------------\nrefresh_token_expires_at => \n----------------------------------------------------------------------------------------------------\nroot@docker_lamp_1:/home/jiminny#","is_focused":true},{"role":"AXRadioButton","text":"DOCKER","depth":2,"bounds":{"left":0.0,"top":0.05888889,"width":0.16458334,"height":0.026666667},"on_screen":true,"role_description":"radio button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Close Tab","depth":3,"bounds":{"left":0.004166667,"top":0.06333333,"width":0.011111111,"height":0.017777778},"on_screen":true,"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXRadioButton","text":"DEV (docker)","depth":2,"bounds":{"left":0.16458334,"top":0.05888889,"width":0.16458334,"height":0.026666667},"on_screen":true,"role_description":"radio button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Close Tab","depth":3,"bounds":{"left":0.16875,"top":0.06333333,"width":0.011111111,"height":0.017777778},"on_screen":true,"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXRadioButton","text":"APP (-zsh)","depth":2,"bounds":{"left":0.32916668,"top":0.05888889,"width":0.16423611,"height":0.026666667},"on_screen":true,"role_description":"radio button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Close Tab","depth":3,"bounds":{"left":0.33333334,"top":0.06333333,"width":0.011111111,"height":0.017777778},"on_screen":true,"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXRadioButton","text":"-zsh","depth":2,"bounds":{"left":0.49340278,"top":0.05888889,"width":0.16423611,"height":0.026666667},"on_screen":true,"role_description":"radio button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Close Tab","depth":3,"bounds":{"left":0.49756944,"top":0.06333333,"width":0.011111111,"height":0.017777778},"on_screen":true,"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXRadioButton","text":"screenpipe\"","depth":2,"bounds":{"left":0.6576389,"top":0.05888889,"width":0.16423611,"height":0.026666667},"on_screen":true,"role_description":"radio button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Close Tab","depth":3,"bounds":{"left":0.66180557,"top":0.06333333,"width":0.011111111,"height":0.017777778},"on_screen":true,"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXRadioButton","text":"-zsh","depth":2,"bounds":{"left":0.821875,"top":0.05888889,"width":0.16423611,"height":0.026666667},"on_screen":true,"role_description":"radio button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Close Tab","depth":3,"bounds":{"left":0.82604164,"top":0.06333333,"width":0.011111111,"height":0.017777778},"on_screen":true,"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"⌥⌘1","depth":1,"bounds":{"left":0.95763886,"top":0.032222223,"width":0.03888889,"height":0.018888889},"on_screen":true,"automation_id":"_NS:8","role_description":"text"},{"role":"AXStaticText","text":"DEV (docker)","depth":1,"bounds":{"left":0.47013888,"top":0.033333335,"width":0.0625,"height":0.017777778},"on_screen":true,"role_description":"text"}]...
|
6952889929047603526
|
4792633381515062291
|
click
|
accessibility
|
NULL
|
Last login: Thu May 7 09:44:56 on ttys006
Poetry Last login: Thu May 7 09:44:56 on ttys006
Poetry could not find a pyproject.toml file in /Users/lukas/jiminny/app or its parents
Poetry could not find a pyproject.toml file in /Users/lukas/jiminny/app or its parents
lukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/jiminny/app (JY-20773-fix-automated-reports-user-pilot-tracking) $ dev
root@docker_lamp_1:/home/jiminny# php artisan crm:sync-opportunity --teamId=2 --from='2026-05-01 00:00:00'
Syncing opportunity for Hubspot
Your HubSpot account has become disconnected. Please login to Jiminny to reconnect. skipping...
root@docker_lamp_1:/home/jiminny# php artisan jiminny:token-info -A 1499 -R
----------------------------------------------------------------------------------------------------
access_token => CNeR-JHgMxIZQlNQMl8kQEwrAgwACAkUAhIJBB4BAQEDBxiCiYwCIN7Y_Qwo0qwCMhTnG549n-YtNuc1jgj-2AsLPSmw3DoyQlNQMl8kQEwrAiUACBkGawEFThwBARIBAQEEATEEAQEBAQEBAQEBAQUBEggBAQEBAYlCFPAsBNZxoDp5kAcRyeBlQoE5SM7DSgNuYTFSAFoAYABo3tj9DHAAeAA
----------------------------------------------------------------------------------------------------
access_token_expires_at => 2026-05-07 11:41:20
----------------------------------------------------------------------------------------------------
refresh_token => d5ab04e2-2109-4c0b-b513-8cba1dd54371
----------------------------------------------------------------------------------------------------
refresh_token_expires_at =>
----------------------------------------------------------------------------------------------------
root@docker_lamp_1:/home/jiminny#
DOCKER
Close Tab
DEV (docker)
Close Tab
APP (-zsh)
Close Tab
-zsh
Close Tab
screenpipe"
Close Tab
-zsh
Close Tab
⌥⌘1
DEV (docker)...
|
2690
|
NULL
|
NULL
|
NULL
|
|
2693
|
112
|
19
|
2026-05-07T11:35:50.823457+00:00
|
/Users/lukas/.screenpipe/data/data/2026-05-07/1778 /Users/lukas/.screenpipe/data/data/2026-05-07/1778153750823_m2.jpg...
|
iTerm2
|
DEV (docker)
|
True
|
NULL
|
monitor_2
|
NULL
|
NULL
|
NULL
|
NULL
|
Last login: Thu May 7 09:44:56 on ttys006
Poetry Last login: Thu May 7 09:44:56 on ttys006
Poetry could not find a pyproject.toml file in /Users/lukas/jiminny/app or its parents
Poetry could not find a pyproject.toml file in /Users/lukas/jiminny/app or its parents
lukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/jiminny/app (JY-20773-fix-automated-reports-user-pilot-tracking) $ dev
root@docker_lamp_1:/home/jiminny# php artisan crm:sync-opportunity --teamId=2 --from='2026-05-01 00:00:00'
Syncing opportunity for Hubspot
Your HubSpot account has become disconnected. Please login to Jiminny to reconnect. skipping...
root@docker_lamp_1:/home/jiminny# php artisan jiminny:token-info -A 1499 -R
----------------------------------------------------------------------------------------------------
access_token => CNeR-JHgMxIZQlNQMl8kQEwrAgwACAkUAhIJBB4BAQEDBxiCiYwCIN7Y_Qwo0qwCMhTnG549n-YtNuc1jgj-2AsLPSmw3DoyQlNQMl8kQEwrAiUACBkGawEFThwBARIBAQEEATEEAQEBAQEBAQEBAQUBEggBAQEBAYlCFPAsBNZxoDp5kAcRyeBlQoE5SM7DSgNuYTFSAFoAYABo3tj9DHAAeAA
----------------------------------------------------------------------------------------------------
access_token_expires_at => 2026-05-07 11:41:20
----------------------------------------------------------------------------------------------------
refresh_token => d5ab04e2-2109-4c0b-b513-8cba1dd54371
----------------------------------------------------------------------------------------------------
refresh_token_expires_at =>
----------------------------------------------------------------------------------------------------
root@docker_lamp_1:/home/jiminny#
DOCKER
Close Tab
DEV (docker)
Close Tab
APP (-zsh)
Close Tab
-zsh
Close Tab
screenpipe"
Close Tab
-zsh
Close Tab
⌥⌘1
DEV (docker)...
|
[{"role":"AXTextArea","text [{"role":"AXTextArea","text":"Last login: Thu May 7 09:44:56 on ttys006\n\nPoetry could not find a pyproject.toml file in /Users/lukas/jiminny/app or its parents\n\nPoetry could not find a pyproject.toml file in /Users/lukas/jiminny/app or its parents\nlukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/jiminny/app (JY-20773-fix-automated-reports-user-pilot-tracking) $ dev\nroot@docker_lamp_1:/home/jiminny# php artisan crm:sync-opportunity --teamId=2 --from='2026-05-01 00:00:00'\nSyncing opportunity for Hubspot\nYour HubSpot account has become disconnected. Please login to Jiminny to reconnect. skipping...\nroot@docker_lamp_1:/home/jiminny# php artisan jiminny:token-info -A 1499 -R\n----------------------------------------------------------------------------------------------------\naccess_token => CNeR-JHgMxIZQlNQMl8kQEwrAgwACAkUAhIJBB4BAQEDBxiCiYwCIN7Y_Qwo0qwCMhTnG549n-YtNuc1jgj-2AsLPSmw3DoyQlNQMl8kQEwrAiUACBkGawEFThwBARIBAQEEATEEAQEBAQEBAQEBAQUBEggBAQEBAYlCFPAsBNZxoDp5kAcRyeBlQoE5SM7DSgNuYTFSAFoAYABo3tj9DHAAeAA\n----------------------------------------------------------------------------------------------------\naccess_token_expires_at => 2026-05-07 11:41:20\n----------------------------------------------------------------------------------------------------\nrefresh_token => d5ab04e2-2109-4c0b-b513-8cba1dd54371\n----------------------------------------------------------------------------------------------------\nrefresh_token_expires_at => \n----------------------------------------------------------------------------------------------------\nroot@docker_lamp_1:/home/jiminny#","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.4800532,"height":-0.06304872},"on_screen":true,"value":"Last login: Thu May 7 09:44:56 on ttys006\n\nPoetry could not find a pyproject.toml file in /Users/lukas/jiminny/app or its parents\n\nPoetry could not find a pyproject.toml file in /Users/lukas/jiminny/app or its parents\nlukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/jiminny/app (JY-20773-fix-automated-reports-user-pilot-tracking) $ dev\nroot@docker_lamp_1:/home/jiminny# php artisan crm:sync-opportunity --teamId=2 --from='2026-05-01 00:00:00'\nSyncing opportunity for Hubspot\nYour HubSpot account has become disconnected. Please login to Jiminny to reconnect. skipping...\nroot@docker_lamp_1:/home/jiminny# php artisan jiminny:token-info -A 1499 -R\n----------------------------------------------------------------------------------------------------\naccess_token => CNeR-JHgMxIZQlNQMl8kQEwrAgwACAkUAhIJBB4BAQEDBxiCiYwCIN7Y_Qwo0qwCMhTnG549n-YtNuc1jgj-2AsLPSmw3DoyQlNQMl8kQEwrAiUACBkGawEFThwBARIBAQEEATEEAQEBAQEBAQEBAQUBEggBAQEBAYlCFPAsBNZxoDp5kAcRyeBlQoE5SM7DSgNuYTFSAFoAYABo3tj9DHAAeAA\n----------------------------------------------------------------------------------------------------\naccess_token_expires_at => 2026-05-07 11:41:20\n----------------------------------------------------------------------------------------------------\nrefresh_token => d5ab04e2-2109-4c0b-b513-8cba1dd54371\n----------------------------------------------------------------------------------------------------\nrefresh_token_expires_at => \n----------------------------------------------------------------------------------------------------\nroot@docker_lamp_1:/home/jiminny#","is_focused":true},{"role":"AXRadioButton","text":"DOCKER","depth":2,"bounds":{"left":0.27027926,"top":1.0,"width":0.0787899,"height":-0.042298436},"on_screen":true,"role_description":"radio button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Close Tab","depth":3,"bounds":{"left":0.27227393,"top":1.0,"width":0.005319149,"height":-0.04549086},"on_screen":true,"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXRadioButton","text":"DEV (docker)","depth":2,"bounds":{"left":0.34906915,"top":1.0,"width":0.0787899,"height":-0.042298436},"on_screen":true,"role_description":"radio button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Close Tab","depth":3,"bounds":{"left":0.35106382,"top":1.0,"width":0.005319149,"height":-0.04549086},"on_screen":true,"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXRadioButton","text":"APP (-zsh)","depth":2,"bounds":{"left":0.42785904,"top":1.0,"width":0.07862367,"height":-0.042298436},"on_screen":true,"role_description":"radio button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Close Tab","depth":3,"bounds":{"left":0.42985374,"top":1.0,"width":0.005319149,"height":-0.04549086},"on_screen":true,"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXRadioButton","text":"-zsh","depth":2,"bounds":{"left":0.5064827,"top":1.0,"width":0.07862367,"height":-0.042298436},"on_screen":true,"role_description":"radio button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Close Tab","depth":3,"bounds":{"left":0.5084774,"top":1.0,"width":0.005319149,"height":-0.04549086},"on_screen":true,"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXRadioButton","text":"screenpipe\"","depth":2,"bounds":{"left":0.5851064,"top":1.0,"width":0.07862367,"height":-0.042298436},"on_screen":true,"role_description":"radio button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Close Tab","depth":3,"bounds":{"left":0.58710104,"top":1.0,"width":0.005319149,"height":-0.04549086},"on_screen":true,"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXRadioButton","text":"-zsh","depth":2,"bounds":{"left":0.66373,"top":1.0,"width":0.07862367,"height":-0.042298436},"on_screen":true,"role_description":"radio button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Close Tab","depth":3,"bounds":{"left":0.66572475,"top":1.0,"width":0.005319149,"height":-0.04549086},"on_screen":true,"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"⌥⌘1","depth":1,"bounds":{"left":0.7287234,"top":1.0,"width":0.01861702,"height":-0.023144484},"on_screen":true,"automation_id":"_NS:8","role_description":"text"},{"role":"AXStaticText","text":"DEV (docker)","depth":1,"bounds":{"left":0.49534574,"top":1.0,"width":0.029920213,"height":-0.02394259},"on_screen":true,"role_description":"text"}]...
|
6952889929047603526
|
4792633381515062291
|
click
|
accessibility
|
NULL
|
Last login: Thu May 7 09:44:56 on ttys006
Poetry Last login: Thu May 7 09:44:56 on ttys006
Poetry could not find a pyproject.toml file in /Users/lukas/jiminny/app or its parents
Poetry could not find a pyproject.toml file in /Users/lukas/jiminny/app or its parents
lukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/jiminny/app (JY-20773-fix-automated-reports-user-pilot-tracking) $ dev
root@docker_lamp_1:/home/jiminny# php artisan crm:sync-opportunity --teamId=2 --from='2026-05-01 00:00:00'
Syncing opportunity for Hubspot
Your HubSpot account has become disconnected. Please login to Jiminny to reconnect. skipping...
root@docker_lamp_1:/home/jiminny# php artisan jiminny:token-info -A 1499 -R
----------------------------------------------------------------------------------------------------
access_token => CNeR-JHgMxIZQlNQMl8kQEwrAgwACAkUAhIJBB4BAQEDBxiCiYwCIN7Y_Qwo0qwCMhTnG549n-YtNuc1jgj-2AsLPSmw3DoyQlNQMl8kQEwrAiUACBkGawEFThwBARIBAQEEATEEAQEBAQEBAQEBAQUBEggBAQEBAYlCFPAsBNZxoDp5kAcRyeBlQoE5SM7DSgNuYTFSAFoAYABo3tj9DHAAeAA
----------------------------------------------------------------------------------------------------
access_token_expires_at => 2026-05-07 11:41:20
----------------------------------------------------------------------------------------------------
refresh_token => d5ab04e2-2109-4c0b-b513-8cba1dd54371
----------------------------------------------------------------------------------------------------
refresh_token_expires_at =>
----------------------------------------------------------------------------------------------------
root@docker_lamp_1:/home/jiminny#
DOCKER
Close Tab
DEV (docker)
Close Tab
APP (-zsh)
Close Tab
-zsh
Close Tab
screenpipe"
Close Tab
-zsh
Close Tab
⌥⌘1
DEV (docker)...
|
NULL
|
NULL
|
NULL
|
NULL
|
|
2699
|
111
|
20
|
2026-05-07T11:36:08.248061+00:00
|
/Users/lukas/.screenpipe/data/data/2026-05-07/1778 /Users/lukas/.screenpipe/data/data/2026-05-07/1778153768248_m1.jpg...
|
PhpStorm
|
faVsco.js – laravel.log
|
True
|
NULL
|
monitor_1
|
NULL
|
NULL
|
NULL
|
NULL
|
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
[2026-04-27 10:26:25] local.INFO: Jiminny\Console\Commands\Command::run Memory usage before starting command {"command":"mailbox:sync","memoryBeforeCommandInMb":60.0,"memoryPeakBeforeCommandInMb":99.727} {"correlation_id":"7b1ea2fb-8f36-4897-9970-91b4abba2cc7","trace_id":"265be7c0-bf8f-4037-a0d2-57df3ef1d7e1"}
[2026-04-27 10:26:25] local.INFO: [EmailSchedule] STARTING Inbox Sync {"host":"docker_lamp_1"} {"correlation_id":"7b1ea2fb-8f36-4897-9970-91b4abba2cc7","trace_id":"265be7c0-bf8f-4037-a0d2-57df3ef1d7e1"}
[2026-04-27 10:26:25] local.INFO: [EmailSchedule] FINISHED Inbox Sync {"host":"docker_lamp_1","events":2} {"correlation_id":"7b1ea2fb-8f36-4897-9970-91b4abba2cc7","trace_id":"265be7c0-bf8f-4037-a0d2-57df3ef1d7e1"}
[2026-04-27 10:26:25] local.INFO: Jiminny\Console\Commands\Command::run Memory usage for command {"command":"mailbox:sync","memoryBeforeCommandInMb":60.0,"memoryAfterCommandInMB":62.0,"memoryPeakBeforeCommandInMb":99.727,"memoryPeakAfterCommandInMB":99.727} {"correlation_id":"7b1ea2fb-8f36-4897-9970-91b4abba2cc7","trace_id":"265be7c0-bf8f-4037-a0d2-57df3ef1d7e1"}
[2026-04-27 10:26:26] local.INFO: [automated-reports] Started {"correlation_id":"d3f79034-397b-4936-9305-bdd99b23f2dd","trace_id":"aa0f8702-73eb-4947-a765-2e89244599c3"}
[2026-04-27 10:26:26] local.INFO: [automated-reports] Checking conditions {"isMonday":true,"isWeekend":false,"isFirstDayOfMonth":false,"currentMonth":4,"isQuarterlyMonth":true} {"correlation_id":"d3f79034-397b-4936-9305-bdd99b23f2dd","trace_id":"aa0f8702-73eb-4947-a765-2e89244599c3"}
[2026-04-27 10:26:26] local.INFO: [automated-reports] Processing daily reports {"correlation_id":"d3f79034-397b-4936-9305-bdd99b23f2dd","trace_id":"aa0f8702-73eb-4947-a765-2e89244599c3"}
[2026-04-27 10:26:26] local.INFO: [automated-reports] Found 1 daily reports to process {"correlation_id":"d3f79034-397b-4936-9305-bdd99b23f2dd","trace_id":"aa0f8702-73eb-4947-a765-2e89244599c3"}
[2026-04-27 10:26:26] local.INFO: [automated-reports] Dispatching Generate Report job for report {"reportUuid":"4f6ca2b5-1993-48aa-99ad-b66f19f15d43","teamId":1,"frequency":"weekly","type":"ask_jiminny"} {"correlation_id":"d3f79034-397b-4936-9305-bdd99b23f2dd","trace_id":"aa0f8702-73eb-4947-a765-2e89244599c3"}
[2026-04-27 10:26:26] local.INFO: [automated-reports] Processing weekly reports {"correlation_id":"d3f79034-397b-4936-9305-bdd99b23f2dd","trace_id":"aa0f8702-73eb-4947-a765-2e89244599c3"}
[2026-04-27 10:26:26] local.INFO: [automated-reports] Found 1 weekly reports to process {"correlation_id":"d3f79034-397b-4936-9305-bdd99b23f2dd","trace_id":"aa0f8702-73eb-4947-a765-2e89244599c3"}
[2026-04-27 10:26:26] local.INFO: [automated-reports] Dispatching Generate Report job for report {"reportUuid":"4f6ca2b5-1993-48aa-99ad-b66f19f15d43","teamId":1,"frequency":"weekly","type":"ask_jiminny"} {"correlation_id":"d3f79034-397b-4936-9305-bdd99b23f2dd","trace_id":"aa0f8702-73eb-4947-a765-2e89244599c3"}
[2026-04-27 10:26:26] local.INFO: [automated-reports] Completed {"correlation_id":"d3f79034-397b-4936-9305-bdd99b23f2dd","trace_id":"aa0f8702-73eb-4947-a765-2e89244599c3"}
[2026-04-27 10:26:26] local.INFO: [HubSpot Journal Polling] Getting offset from database {"offset":"","jiminny_team_id":1} {"correlation_id":"b453f0f4-0d2c-491c-a935-14c60265ec18","trace_id":"a50202d6-5601-46b4-a02e-cf5ac8d061fa"}
[2026-04-27 10:26:26] local.INFO: [HubSpot Journal API] Fetching latest journal entry {"url":"[URL_WITH_CREDENTIALS] {"correlation_id":"d3e3eb3d-5c83-4d6e-9592-aaa38c1843a4","trace_id":"265be7c0-bf8f-4037-a0d2-57df3ef1d7e1"}
[2026-04-27 10:26:28] local.INFO: [Gmail] imported 7 emails via full sync workflow for inbox 212 {"correlation_id":"d3e3eb3d-5c83-4d6e-9592-aaa38c1843a4","trace_id":"265be7c0-bf8f-4037-a0d2-57df3ef1d7e1"}
[2026-04-27 10:26:28] local.INFO: [Gmail] seeding inbox 212 with last message time : 2026-04-27 10:24:57 {"correlation_id":"d3e3eb3d-5c83-4d6e-9592-aaa38c1843a4","trace_id":"265be7c0-bf8f-4037-a0d2-57df3ef1d7e1"}
[2026-04-27 10:26:28] local.INFO: [Sync Mailbox] Sync complete {"inbox_id":212} {"correlation_id":"d3e3eb3d-5c83-4d6e-9592-aaa38c1843a4","trace_id":"265be7c0-bf8f-4037-a0d2-57df3ef1d7e1"}
[2026-04-27 10:27:04] local.INFO: Jiminny\Console\Commands\Command::run Memory usage before starting command {"command":"meeting-bot:schedule-bot","memoryBeforeCommandInMb":60.0,"memoryPeakBeforeCommandInMb":99.727} {"correlation_id":"f55fbf77-7e5f-4c87-accd-011697ca0fa3","trace_id":"9bd1c431-26de-4e31-83d8-3129ed9b3b79"}
[2026-04-27 10:27:04] local.INFO: [ScheduleBotCommand] Number of activities to be captured: 0 {"correlation_id":"f55fbf77-7e5f-4c87-accd-011697ca0fa3","trace_id":"9bd1c431-26de-4e31-83d8-3129ed9b3b79"}
[2026-04-27 10:27:04] local.INFO: Jiminny\Console\Commands\Command::run Memory usage for command {"command":"meeting-bot:schedule-bot","memoryBeforeCommandInMb":60.0,"memoryAfterCommandInMB":62.0,"memoryPeakBeforeCommandInMb":99.727,"memoryPeakAfterCommandInMB":99.727} {"correlation_id":"f55fbf77-7e5f-4c87-accd-011697ca0fa3","trace_id":"9bd1c431-26de-4e31-83d8-3129ed9b3b79"}
[2026-04-27 10:27:05] local.INFO: Jiminny\Console\Commands\Command::run Memory usage before starting command {"command":"dialers:monitor-activities","memoryBeforeCommandInMb":60.0,"memoryPeakBeforeCommandInMb":99.727} {"correlation_id":"da241053-5f3b-4d02-9504-4ef2987cdbd7","trace_id":"364d3fc7-0f11-40c6-8697-d18eff2c4cb6"}
[2026-04-27 10:27:05] local.INFO: Jiminny\Console\Commands\Command::run Memory usage for command {"command":"dialers:monitor-activities","memoryBeforeCommandInMb":60.0,"memoryAfterCommandInMB":62.0,"memoryPeakBeforeCommandInMb":99.727,"memoryPeakAfterCommandInMB":99.727} {"correlation_id":"da241053-5f3b-4d02-9504-4ef2987cdbd7","trace_id":"364d3fc7-0f11-40c6-8697-d18eff2c4cb6"}
[2026-04-27 10:27:06] local.NOTICE: Monitoring start {"correlation_id":"ad0d871a-a1fd-4725-b2f7-ac8f0fc1857e","trace_id":"cb504e84-d924-43a0-82d5-faf1840134ba"}
[2026-04-27 10:27:06] local.NOTICE: Monitoring end {"correlation_id":"ad0d871a-a1fd-4725-b2f7-ac8f0fc1857e","trace_id":"cb504e84-d924-43a0-82d5-faf1840134ba"}
[2026-04-27 10:27:08] local.INFO: Jiminny\Console\Commands\Command::run Memory usage before starting command {"command":"mailbox:skip-lists:refresh","memoryBeforeCommandInMb":60.0,"memoryPeakBeforeCommandInMb":99.727} {"correlation_id":"b8652f0d-1cee-44ae-a0ea-3d3137a882c1","trace_id":"7fe19940-0813-4199-a2a8-958de09bd0ee"}
[2026-04-27 10:27:08] local.INFO: Jiminny\Console\Commands\Command::run Memory usage for command {"command":"mailbox:skip-lists:refresh","memoryBeforeCommandInMb":60.0,"memoryAfterCommandInMB":62.0,"memoryPeakBeforeCommandInMb":99.727,"memoryPeakAfterCommandInMB":99.727} {"correlation_id":"b8652f0d-1cee-44ae-a0ea-3d3137a882c1","trace_id":"7fe19940-0813-4199-a2a8-958de09bd0ee"}
[2026-04-27 10:27:09] local.INFO: Jiminny\Console\Commands\Command::run Memory usage before starting command {"command":"mailbox:batch:process","memoryBeforeCommandInMb":60.0,"memoryPeakBeforeCommandInMb":99.727} {"correlation_id":"06190462-0f47-403f-8e95-ad5d05bff1fe","trace_id":"4aa6bed6-05a1-4661-a9c9-edadc61d84ac"}
[2026-04-27 10:27:09] local.INFO: [EmailSchedule] STARTING batch process {"host":"docker_lamp_1"} {"correlation_id":"06190462-0f47-403f-8e95-ad5d05bff1fe","trace_id":"4aa6bed6-05a1-4661-a9c9-edadc61d84ac"}
[2026-04-27 10:27:09] local.INFO: [EmailSchedule] FINISHED batch process {"host":"docker_lamp_1","processed":0} {"correlation_id":"06190462-0f47-403f-8e95-ad5d05bff1fe","trace_id":"4aa6bed6-05a1-4661-a9c9-edadc61d84ac"}
[2026-04-27 10:27:09] local.INFO: Jiminny\Console\Commands\Command::run Memory usage for command {"command":"mailbox:batch:process","memoryBeforeCommandInMb":60.0,"memoryAfterCommandInMB":62.0,"memoryPeakBeforeCommandInMb":99.727,"memoryPeakAfterCommandInMB":99.727} {"correlation_id":"06190462-0f47-403f-8e95-ad5d05bff1fe","trace_id":"4aa6bed6-05a1-4661-a9c9-edadc61d84ac"}
[2026-04-27 10:27:11] local.INFO: Jiminny\Console\Commands\Command::run Memory usage before starting command {"command":"mailbox:batch:create","memoryBeforeCommandInMb":60.0,"memoryPeakBeforeCommandInMb":99.727} {"correlation_id":"b561c5d5-e6e3-4091-9517-b0458190a360","trace_id":"bf9679fb-b785-40cb-b814-04a1dc385a1c"}
[2026-04-27 10:27:11] local.INFO: [EmailSchedule] STARTING batch create {"host":"docker_lamp_1"} {"correlation_id":"b561c5d5-e6e3-4091-9517-b0458190a360","trace_id":"bf9679fb-b785-40cb-b814-04a1dc385a1c"}
[2026-04-27 10:27:11] local.INFO: [EmailSchedule] FINISHED batch create {"host":"docker_lamp_1"} {"correlation_id":"b561c5d5-e6e3-4091-9517-b0458190a360","trace_id":"bf9679fb-b785-40cb-b814-04a1dc385a1c"}
[2026-04-27 10:27:11] local.INFO: Jiminny\Console\Commands\Command::run Memory usage for command {"command":"mailbox:batch:create","memoryBeforeCommandInMb":60.0,"memoryAfterCommandInMB":62.0,"memoryPeakBeforeCommandInMb":99.727,"memoryPeakAfterCommandInMB":99.727} {"correlation_id":"b561c5d5-e6e3-4091-9517-b0458190a360","trace_id":"bf9679fb-b785-40cb-b814-04a1dc385a1c"}
[2026-04-27 10:27:14] local.INFO: [Jiminny\Jobs\Mailbox\CreateBatches] processed 2 inboxes and created 1 batches {"userId":null,"batchSize":30,"maxBatches":1000} {"correlation_id":"98416296-43c2-4834-927e-4a0e86dc99cf","trace_id":"bf9679fb-b785-40cb-b814-04a1dc385a1c"}
[2026-04-27 10:28:04] local.INFO: Jiminny\Console\Commands\Command::run Memory usage before starting command {"command":"meeting-bot:schedule-bot","memoryBeforeCommandInMb":60.0,"memoryPeakBeforeCommandInMb":99.727} {"correlation_id":"5852bcf1-af92-4d24-8a2d-08a9d4a5cfac","trace_id":"25af5e8c-a148-493e-ac9f-4d53f1960177"}
[2026-04-27 10:28:04] local.INFO: [ScheduleBotCommand] Number of activities to be captured: 0 {"correlation_id":"5852bcf1-af92-4d24-8a2d-08a9d4a5cfac","trace_id":"25af5e8c-a148-493e-ac9f-4d53f1960177"}
[2026-04-27 10:28:04] local.INFO: Jiminny\Console\Commands\Command::run Memory usage for command {"command":"meeting-bot:schedule-bot","memoryBeforeCommandInMb":60.0,"memoryAfterCommandInMB":62.0,"memoryPeakBeforeCommandInMb":99.727,"memoryPeakAfterCommandInMB":99.727} {"correlation_id":"5852bcf1-af92-4d24-8a2d-08a9d4a5cfac","trace_id":"25af5e8c-a148-493e-ac9f-4d53f1960177"}
[2026-04-27 10:28:06] local.INFO: Jiminny\Console\Commands\Command::run Memory usage before starting command {"command":"dialers:monitor-activities","memoryBeforeCommandInMb":60.0,"memoryPeakBeforeCommandInMb":99.727} {"correlation_id":"cc1cff1f-71b4-4bb1-800f-66e27c0f2b3a","trace_id":"c03103a3-9ab8-480b-9753-7c7c6488f246"}
[2026-04-27 10:28:06] local.INFO: Jiminny\Console\Commands\Command::run Memory usage for command {"command":"dialers:monitor-activities","memoryBeforeCommandInMb":60.0,"memoryAfterCommandInMB":62.0,"memoryPeakBeforeCommandInMb":99.727,"memoryPeakAfterCommandInMB":99.727} {"correlation_id":"cc1cff1f-71b4-4bb1-800f-66e27c0f2b3a","trace_id":"c03103a3-9ab8-480b-9753-7c7c6488f246"}
[2026-04-27 10:28:09] local.ERROR: Target class [Jiminny\Repositories\AjReportsRepository] does not exist. {"exception":"[object] (Illuminate\\Contracts\\Container\\BindingResolutionException(code: 0): Target class [Jiminny\\Repositories\\AjReportsRepository] does not exist. at /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php:1124)
[stacktrace]
#0 /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php(933): Illuminate\\Container\\Container->build('Jiminny\\\\Reposit...')
#1 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Application.php(1078): Illuminate\\Container\\Container->resolve('Jiminny\\\\Reposit...', Array, true)
#2 /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php(864): Illuminate\\Foundation\\Application->resolve('Jiminny\\\\Reposit...', Array)
#3 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Application.php(1058): Illuminate\\Container\\Container->make('Jiminny\\\\Reposit...', Array)
#4 /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php(1336): Illuminate\\Foundation\\Application->make('Jiminny\\\\Reposit...')
#5 /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php(1237): Illuminate\\Container\\Container->resolveClass(Object(ReflectionParameter))
#6 /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php(1162): Illuminate\\Container\\Container->resolveDependencies(Array)
#7 /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php(933): Illuminate\\Container\\Container->build('Jiminny\\\\Http\\\\Co...')
#8 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Application.php(1078): Illuminate\\Container\\Container->resolve('Jiminny\\\\Http\\\\Co...', Array, true)
#9 /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php(864): Illuminate\\Foundation\\Application->resolve('Jiminny\\\\Http\\\\Co...', Array)
#10 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Application.php(1058): Illuminate\\Container\\Container->make('Jiminny\\\\Http\\\\Co...', Array)
#11 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Route.php(286): Illuminate\\Foundation\\Application->make('Jiminny\\\\Http\\\\Co...')
#12 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Route.php(1133): Illuminate\\Routing\\Route->getController()
#13 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Route.php(1062): Illuminate\\Routing\\Route->controllerMiddleware()
#14 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Router.php(834): Illuminate\\Routing\\Route->gatherMiddleware()
#15 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Router.php(816): Illuminate\\Routing\\Router->gatherRouteMiddleware(Object(Illuminate\\Routing\\Route))
#16 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Router.php(800): Illuminate\\Routing\\Router->runRouteWithinStack(Object(Illuminate\\Routing\\Route), Object(Illuminate\\Http\\Request))
#17 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Router.php(764): Illuminate\\Routing\\Router->runRoute(Object(Illuminate\\Http\\Request), Object(Illuminate\\Routing\\Route))
#18 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Router.php(753): Illuminate\\Routing\\Router->dispatchToRoute(Object(Illuminate\\Http\\Request))
#19 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Http/Kernel.php(200): Illuminate\\Routing\\Router->dispatch(Object(Illuminate\\Http\\Request))
#20 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(180): Illuminate\\Foundation\\Http\\Kernel->Illuminate\\Foundation\\Http\\{closure}(Object(Illuminate\\Http\\Request))
#21 /home/jiminny/vendor/barryvdh/laravel-debugbar/src/Middleware/InjectDebugbar.php(59): Illuminate\\Pipeline\\Pipeline->Illuminate\\Pipeline\\{closure}(Object(Illuminate\\Http\\Request))
#22 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(219): Barryvdh\\Debugbar\\Middleware\\InjectDebugbar->handle(Object(Illuminate\\Http\\Request), Object(Closure))
#23 /home/jiminny/vendor/laravel/framework/src/Illuminate/Http/Middleware/HandleCors.php(74): Illuminate\\Pipeline\\Pipeline->Illuminate\\Pipeline\\{closure}(Object(Illuminate\\Http\\Request))
#24 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(219): Illuminate\\Http\\Middleware\\HandleCors->handle(Object(Illuminate\\Http\\Request), Object(Closure))
#25 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Http/Middleware/TransformsRequest.php(21): Illuminate\\Pipeline\\Pipeline->Illuminate\\Pipeline\\{closure}(Object(Illuminate\\Http\\Request))
#26 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Http/Middleware/TrimStrings.php(51): Illuminate\\Foundation\\Http\\Middleware\\TransformsRequest->handle(Object(Illuminate\\Http\\Request), Object(Closure))
#27 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(219): Illuminate\\Foundation\\Http\\Middleware\\TrimStrings->handle(Object(Illuminate\\Http\\Request), Object(Closure))
#28 /home/jiminny/vendor/bepsvpt/secure-headers/src/SecureHeadersMiddleware.php(18): Illuminate\\Pipeline\\Pipeline->Illuminate\\Pipeline\\{closure}(Object(Illuminate\\Http\\Request))
#29 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(219): Bepsvpt\\SecureHeaders\\SecureHeadersMiddleware->handle(Object(Illuminate\\Http\\Request), Object(Closure))
#30 /home/jiminny/app/Http/Middleware/SentryContext.php(60): Illuminate\\Pipeline\\Pipeline->Illuminate\\Pipeline\\{closure}(Object(Illuminate\\Http\\Request))
#31 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(219): Jiminny\\Http\\Middleware\\SentryContext->handle(Object(Illuminate\\Http\\Request), Object(Closure))
#32 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Http/Middleware/PreventRequestsDuringMaintenance.php(109): Illuminate\\Pipeline\\Pipeline->Illuminate\\Pipeline\\{closure}(Object(Illuminate\\Http\\Request))
#33 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(219): Illuminate\\Foundation\\Http\\Middleware\\PreventRequestsDuringMaintenance->handle(Object(Illuminate\\Http\\Request), Object(Closure))
#34 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Http/Middleware/InvokeDeferredCallbacks.php(22): Illuminate\\Pipeline\\Pipeline->Illuminate\\Pipeline\\{closure}(Object(Illuminate\\Http\\Request))
#35 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(219): Illuminate\\Foundation\\Http\\Middleware\\InvokeDeferredCallbacks->handle(Object(Illuminate\\Http\\Request), Object(Closure))
#36 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(137): Illuminate\\Pipeline\\Pipeline->Illuminate\\Pipeline\\{closure}(Object(Illuminate\\Http\\Request))
#37 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Http/Kernel.php(175): Illuminate\\Pipeline\\Pipeline->then(Object(Closure))
#38 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Http/Kernel.php(144): Illuminate\\Foundation\\Http\\Kernel->sendRequestThroughRouter(Object(Illuminate\\Http\\Request))
#39 /home/jiminny/public/index.php(51): Illuminate\\Foundation\\Http\\Kernel->handle(Object(Illuminate\\Http\\Request))
#40 {main}
[previous exception] [object] (ReflectionException(code: -1): Class \"Jiminny\\Repositories\\AjReportsRepository\" does not exist at /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php:1122)
[stacktrace]
#0 /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php(1122): ReflectionClass->__construct('Jiminny\\\\Reposit...')
#1 /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php(933): Illuminate\\Container\\Container->build('Jiminny\\\\Reposit...')
#2 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Application.php(1078): Illuminate\\Container\\Container->resolve('Jiminny\\\\Reposit...', Array, true)
#3 /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php(864): Illuminate\\Foundation\\Application->resolve('Jiminny\\\\Reposit...', Array)
#4 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Application.php(1058): Illuminate\\Container\\Container->make('Jiminny\\\\Reposit...', Array)
#5 /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php(1336): Illuminate\\Foundation\\Application->make('Jiminny\\\\Reposit...')
#6 /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php(1237): Illuminate\\Container\\Container->resolveClass(Object(ReflectionParameter))
#7 /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php(1162): Illuminate\\Container\\Container->resolveDependencies(Array)
#8 /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php(933): Illuminate\\Container\\Container->build('Jiminny\\\\Http\\\\Co...')
#9 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Application.php(1078): Illuminate\\Container\\Container->resolve('Jiminny\\\\Http\\\\Co...', Array, true)
#10 /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php(864): Illuminate\\Foundation\\Application->resolve('Jiminny\\\\Http\\\\Co...', Array)
#11 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Application.php(1058): Illuminate\\Container\\Container->make('Jiminny\\\\Http\\\\Co...', Array)
#12 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Route.php(286): Illuminate\\Foundation\\Application->make('Jiminny\\\\Http\\\\Co...')
#13 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Route.php(1133): Illuminate\\Routing\\Route->getController()
#14 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Route.php(1062): Illuminate\\Routing\\Route->controllerMiddleware()
#15 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Router.php(834): Illuminate\\Routing\\Route->gatherMiddleware()
#16 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Router.php(816): Illuminate\\Routing\\Router->gatherRouteMiddleware(Object(Illuminate\\Routing\\Route))
#17 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Router.php(800): Illuminate\\Routing\\Router->runRouteWithinStack(Object(Illuminate\\Routing\\Route), Object(Illuminate\\Http\\Request))
#18 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Router.php(764): Illuminate\\Routing\\Router->runRoute(Object(Illuminate\\Http\\Request), Object(Illuminate\\Routing\\Route))
#19 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Router.php(753): Illuminate\\Routing\\Router->dispatchToRoute(Object(Illuminate\\Http\\Request))
#20 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Http/Kernel.php(200): Illuminate\\Routing\\Router->dispatch(Object(Illuminate\\Http\\Request))
#21 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(180): Illuminate\\Foundation\\Http\\Kernel->Illuminate\\Foundation\\Http\\{closure}(Object(Illuminate\\Http\\Request))
#22 /home/jiminny/vendor/barryvdh/laravel-debugbar/src/Middleware/InjectDebugbar.php(59): Illuminate\\Pipeline\\Pipeline->Illuminate\\Pipeline\\{closure}(Object(Illuminate\\Http\\Request))
#23 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(219): Barryvdh\\Debugbar\\Middleware\\InjectDebugbar->handle(Object(Illuminate\\Http\\Request), Object(Closure))
#24 /home/jiminny/vendor/laravel/framework/src/Illuminate/Http/Middleware/HandleCors.php(74): Illuminate\\Pipeline\\Pipeline->Illuminate\\Pipeline\\{closure}(Object(Illuminate\\Http\\Request))
#25 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(219): Illuminate\\Http\\Middleware\\HandleCors->handle(Object(Illuminate\\Http\\Request), Object(Closure))
#26 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Http/Middleware/TransformsRequest.php(21): Illuminate\\Pipeline\\Pipeline->Illuminate\\Pipeline\\{closure}(Object(Illuminate\\Http\\Request))
#27 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Http/Middleware/TrimStrings.php(51): Illuminate\\Foundation\\Http\\Middleware\\TransformsRequest->handle(Object(Illuminate\\Http\\Request), Object(Closure))
#28 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(219): Illuminate\\Foundation\\Http\\Middleware\\TrimStrings->handle(Object(Illuminate\\Http\\Request), Object(Closure))
#29 /home/jiminny/vendor/bepsvpt/secure-headers/src/SecureHeadersMiddleware.php(18): Illuminate\\Pipeline\\Pipeline->Illuminate\\Pipeline\\{closure}(Object(Illuminate\\Http\\Request))
#30 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(219): Bepsvpt\\SecureHeaders\\SecureHeadersMiddleware->handle(Object(Illuminate\\Http\\Request), Object(Closure))
#31 /home/jiminny/app/Http/Middleware/SentryContext.php(60): Illuminate\\Pipeline\\Pipeline->Illuminate\\Pipeline\\{closure}(Object(Illuminate\\Http\\Request))
#32 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(219): Jiminny\\Http\\Middleware\\SentryContext->handle(Object(Illuminate\\Http\\Request), Object(Closure))
#33 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Http/Middleware/PreventRequestsDuringMaintenance.php(109): Illuminate\\Pipeline\\Pipeline->Illuminate\\Pipeline\\{closure}(Object(Illuminate\\Http\\Request))
#34 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(219): Illuminate\\Foundation\\Http\\Middleware\\PreventRequestsDuringMaintenance->handle(Object(Illuminate\\Http\\Request), Object(Closure))
#35 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Http/Middleware/InvokeDeferredCallbacks.php(22): Illuminate\\Pipeline\\Pipeline->Illuminate\\Pipeline\\{closure}(Object(Illuminate\\Http\\Request))
#36 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(219): Illuminate\\Foundation\\Http\\Middleware\\InvokeDeferredCallbacks->handle(Object(Illuminate\\Http\\Request), Object(Closure))
#37 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(137): Illuminate\\Pipeline\\Pipeline->Illuminate\\Pipeline\\{closure}(Object(Illuminate\\Http\\Request))
#38 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Http/Kernel.php(175): Illuminate\\Pipeline\\Pipeline->then(Object(Closure))
#39 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Http/Kernel.php(144): Illuminate\\Foundation\\Http\\Kernel->sendRequestThroughRouter(Object(Illuminate\\Http\\Request))
#40 /home/jiminny/public/index.php(51): Illuminate\\Foundation\\Http\\Kernel->handle(Object(Illuminate\\Http\\Request))
#41 {main}
"} {"correlation_id":"dfad8817-1dde-4622-8320-f5fc1616a8fe","trace_id":"d21232b2-60fb-4ca0-838c-ba42e61593f9"}
[2026-04-27 10:28:09] local.NOTICE: Monitoring start {"correlation_id":"93744c67-4444-41ff-aaad-6aa9cafe294e","trace_id":"80e396b0-b1ac-4ec8-838c-00021f98072c"}
[2026-04-27 10:28:09] local.NOTICE: Monitoring end {"correlation_id":"93744c67-4444-41ff-aaad-6aa9cafe294e","trace_id":"80e396b0-b1ac-4ec8-838c-00021f98072c"}
[2026-04-27 10:28:10] local.ERROR: Target class [Jiminny\Repositories\AjReportsRepository] does not exist. {"exception":"[object] (Illuminate\\Contracts\\Container\\BindingResolutionException(code: 0): Target class [Jiminny\\Repositories\\AjReportsRepository] does not exist. at /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php:1124)
[stacktrace]
#0 /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php(933): Illuminate\\Container\\Container->build('Jiminny\\\\Reposit...')
#1 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Application.php(1078): Illuminate\\Container\\Container->resolve('Jiminny\\\\Reposit...', Array, true)
#2 /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php(864): Illuminate\\Foundation\\Application->resolve('Jiminny\\\\Reposit...', Array)
#3 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Application.php(1058): Illuminate\\Container\\Container->make('Jiminny\\\\Reposit...', Array)
#4 /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php(1336): Illuminate\\Foundation\\Application->make('Jiminny\\\\Reposit...')
#5 /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php(1237): Illuminate\\Container\\Container->resolveClass(Object(ReflectionParameter))
#6 /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php(1162): Illuminate\\Container\\Container->resolveDependencies(Array)
#7 /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php(933): Illuminate\\Container\\Container->build('Jiminny\\\\Http\\\\Co...')
#8 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Application.php(1078): Illuminate\\Container\\Container->resolve('Jiminny\\\\Http\\\\Co...', Array, true)
#9 /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php(864): Illuminate\\Foundation\\Application->resolve('Jiminny\\\\Http\\\\Co...', Array)
#10 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Application.php(1058): Illuminate\\Container\\Container->make('Jiminny\\\\Http\\\\Co...', Array)
#11 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Route.php(286): Illuminate\\Foundation\\Application->make('Jiminny\\\\Http\\\\Co...')
#12 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Route.php(1133): Illuminate\\Routing\\Route->getController()
#13 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Route.php(1062): Illuminate\\Routing\\Route->controllerMiddleware()
#14 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Router.php(834): Illuminate\\Routing\\Route->gatherMiddleware()
#15 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Router.php(816): Illuminate\\Routing\\Router->gatherRouteMiddleware(Object(Illuminate\\Routing\\Route))
#16 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Router.php(800): Illuminate\\Routing\\Router->runRouteWithinStack(Object(Illuminate\\Routing\\Route), Object(Illuminate\\Http\\Request))
#17 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Router.php(764): Illuminate\\Routing\\Router->runRoute(Object(Illuminate\\Http\\Request), Object(Illuminate\\Routing\\Route))
#18 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Router.php(753): Illuminate\\Routing\\Router->dispatchToRoute(Object(Illuminate\\Http\\Request))
#19 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Http/Kernel.php(200): Illuminate\\Routing\\Router->dispatch(Object(Illuminate\\Http\\Request))
#20 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(180): Illuminate\\Foundation\\Http\\Kernel->Illuminate\\Foundation\\Http\\{closure}(Object(Illuminate\\Http\\Request))
#21 /home/jiminny/vendor/barryvdh/laravel-debugbar/src/Middleware/InjectDebugbar.php(59): Illuminate\\Pipeline\\Pipeline->Illuminate\\Pipeline\\{closure}(Object(Illuminate\\Http\\Request))
#22 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(219): Barryvdh\\Debugbar\\Middleware\\InjectDebugbar->handle(Object(Illuminate\\Http\\Request), Object(Closure))
#23 /home/jiminny/vendor/laravel/framework/src/Illuminate/Http/Middleware/HandleCors.php(74): Illuminate\\Pipeline\\Pipeline->Illuminate\\Pipeline\\{closure}(Object(Illuminate\\Http\\Request))
#24 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(219): Illuminate\\Http\\Middleware\\HandleCors->handle(Object(Illuminate\\Http\\Request), Object(Closure))
#25 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Http/Middleware/TransformsRequest.php(21): Illuminate\\Pipeline\\Pipeline->Illuminate\\Pipeline\\{closure}(Object(Illuminate\\Http\\Request))
#26 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Http/Middleware/TrimStrings.php(51): Illuminate\\Foundation\\Http\\Middleware\\TransformsRequest->handle(Object(Illuminate\\Http\\Request), Object(Closure))
#27 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(219): Illuminate\\Foundation\\Http\\Middleware\\TrimStrings->handle(Object(Illuminate\\Http\\Request), Object(Closure))
#28 /home/jiminny/vendor/bepsvpt/secure-headers/src/SecureHeadersMiddleware.php(18): Illuminate\\Pipeline\\Pipeline->Illuminate\\Pipeline\\{closure}(Object(Illuminate\\Http\\Request))
#29 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(219): Bepsvpt\\SecureHeaders\\SecureHeadersMiddleware->handle(Object(Illuminate\\Http\\Request), Object(Closure))
#30 /home/jiminny/app/Http/Middleware/SentryContext.php(60): Illuminate\\Pipeline\\Pipeline->Illuminate\\Pipeline\\{closure}(Object(Illuminate\\Http\\Request))
#31 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(219): Jiminny\\Http\\Middleware\\SentryContext->handle(Object(Illuminate\\Http\\Request), Object(Closure))
#32 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Http/Middleware/PreventRequestsDuringMaintenance.php(109): Illuminate\\Pipeline\\Pipeline->Illuminate\\Pipeline\\{closure}(Object(Illuminate\\Http\\Request))
#33 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(219): Illuminate\\Foundation\\Http\\Middleware\\PreventRequestsDuringMaintenance->handle(Object(Illuminate\\Http\\Request), Object(Closure))
#34 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Http/Middleware/InvokeDeferredCallbacks.php(22): Illuminate\\Pipeline\\Pipeline->Illuminate\\Pipeline\\{closure}(Object(Illuminate\\Http\\Request))
#35 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(219): Illuminate\\Foundation\\Http\\Middleware\\InvokeDeferredCallbacks->handle(Object(Illuminate\\Http\\Request), Object(Closure))
#36 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(137): Illuminate\\Pipeline\\Pipeline->Illuminate\\Pipeline\\{closure}(Object(Illuminate\\Http\\Request))
#37 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Http/Kernel.php(175): Illuminate\\Pipeline\\Pipeline->then(Object(Closure))
#38 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Http/Kernel.php(144): Illuminate\\Foundation\\Http\\Kernel->sendRequestThroughRouter(Object(Illuminate\\Http\\Request))
#39 /home/jiminny/public/index.php(51): Illuminate\\Foundation\\Http\\Kernel->handle(Object(Illuminate\\Http\\Request))
#40 {main}
[previous exception] [object] (ReflectionException(code: -1): Class \"Jiminny\\Repositories\\AjReportsRepository\" does not exist at /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php:1122)
[stacktrace]
#0 /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php(1122): ReflectionClass->__construct('Jiminny\\\\Reposit...')
#1 /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php(933): Illuminate\\Container\\Container->build('Jiminny\\\\Reposit...')
#2 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Application.php(1078): Illuminate\\Container\\Container->resolve('Jiminny\\\\Reposit...', Array, true)
#3 /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php(864): Illuminate\\Foundation\\Application->resolve('Jiminny\\\\Reposit...', Array)
#4 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Application.php(1058): Illuminate\\Container\\Container->make('Jiminny\\\\Reposit...', Array)
#5 /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php(1336): Illuminate\\Foundation\\Application->make('Jiminny\\\\Reposit...')
#6 /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php(1237): Illuminate\\Container\\Container->resolveClass(Object(ReflectionParameter))
#7 /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php(1162): Illuminate\\Container\\Container->resolveDependencies(Array)
#8 /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php(933): Illuminate\\Container\\Container->build('Jiminny\\\\Http\\\\Co...')
#9 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Application.php(1078): Illuminate\\Container\\Container->resolve('Jiminny\\\\Http\\\\Co...', Array, true)
#10 /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php(864): Illuminate\\Foundation\\Application->resolve('Jiminny\\\\Http\\\\Co...', Array)
#11 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Application.php(1058): Illuminate\\Container\\Container->make('Jiminny\\\\Http\\\\Co...', Array)
#12 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Route.php(286): Illuminate\\Foundation\\Application->make('Jiminny\\\\Http\\\\Co...')
#13 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Route.php(1133): Illuminate\\Routing\\Route->getController()
#14 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Route.php(1062): Illuminate\\Routing\\Route->controllerMiddleware()
#15 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Router.php(834): Illuminate\\Routing\\Route->gatherMiddleware()
#16 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Router.php(816): Illuminate\\Routing\\Router->gatherRouteMiddleware(Object(Illuminate\\Routing\\Route))
#17 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Router.php(800): Illuminate\\Routing\\Router->runRouteWithinStack(Object(Illuminate\\Routing\\Route), Object(Illuminate\\Http\\Request))
#18 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Router.php(764): Illuminate\\Routing\\Router->runRoute(Object(Illuminate\\Http\\Request), Object(Illuminate\\Routing\\Route))
#19 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Router.php(753): Illuminate\\Routing\\Router->dispatchToRoute(Object(Illuminate\\Http\\Request))
#20 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Http/Kernel.php(200): Illuminate\\Routing\\Router->dispatch(Object(Illuminate\\Http\\Request))
#21 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(180): Illuminate\\Foundation\\Http\\Kernel->Illuminate\\Foundation\\Http\\{closure}(Object(Illuminate\\Http\\Request))
#22 /home/jiminny/vendor/barryvdh/laravel-debugbar/src/Middleware/InjectDebugbar.php(59): Illuminate\\Pipeline\\Pipeline->Illuminate\\Pipeline\\{closure}(Object(Illuminate\\Http\\Request))
#23 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(219): Barryvdh\\Debugbar\\Middleware\\InjectDebugbar->handle(Object(Illuminate\\Http\\Request), Object(Closure))
#24 /home/jiminny/vendor/laravel/framework/src/Illuminate/Http/Middleware/HandleCors.php(74): Illuminate\\Pipeline\\Pipeline->Illuminate\\Pipeline\\{closure}(Object(Illuminate\\Http\\Request))
#25 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(219): Illuminate\\Http\\Middleware\\HandleCors->handle(Object(Illuminate\\Http\\Request), Object(Closure))
#26 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Http/Middleware/TransformsRequest.php(21): Illuminate\\Pipeline\\Pipeline->Illuminate\\Pipeline\\{closure}(Object(Illuminate\\Http\\Request))
#27 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Http/Middleware/TrimStrings.php(51): Illuminate\\Foundation\\Http\\Middleware\\TransformsRequest->handle(Object(Illuminate\\Http\\Request), Object(Closure))
#28 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(219): Illuminate\\Foundation\\Http\\Middleware\\TrimStrings->handle(Object(Illuminate\\Http\\Request), Object(Closure))
#29 /home/jiminny/vendor/bepsvpt/secure-headers/src/SecureHeadersMiddleware.php(18): Illuminate\\Pipeline\\Pipeline->Illuminate\\Pipeline\\{closure}(Object(Illuminate\\Http\\Request))
#30 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(219): Bepsvpt\\SecureHeaders\\SecureHeadersMiddleware->handle(Object(Illuminate\\Http\\Request), Object(Closure))
#31 /home/jiminny/app/Http/Middleware/SentryContext.php(60): Illuminate\\Pipeline\\Pipeline->Illuminate\\Pipeline\\{closure}(Object(Illuminate\\Http\\Request))
#32 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(219): Jiminny\\Http\\Middleware\\SentryContext->handle(Object(Illuminate\\Http\\Request), Object(Closure))
#33 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Http/Middleware/PreventRequestsDuringMaintenance.php(109): Illuminate\\Pipeline\\Pipeline->Illuminate\\Pipeline\\{closure}(Object(Illuminate\\Http\\Request))
#34 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(219): Illuminate\\Foundation\\Http\\Middleware\\PreventRequestsDuringMaintenance->handle(Object(Illuminate\\Http\\Request), Object(Closure))
#35 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Http/Middleware/InvokeDeferredCallbacks.php(22): Illuminate\\Pipeline\\Pipeline->Illuminate\\Pipeline\\{closure}(Object(Illuminate\\Http\\Request))
#36 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(219): Illuminate\\Foundation\\Http\\Middleware\\InvokeDeferredCallbacks->handle(Object(Illuminate\\Http\\Request), Object(Closure))
#37 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(137): Illuminate\\Pipeline\\Pipeline->Illuminate\\Pipeline\\{closure}(Object(Illuminate\\Http\\Request))
#38 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Http/Kernel.php(175): Illuminate\\Pipeline\\Pipeline->then(Object(Closure))
#39 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Http/Kernel.php(144): Illuminate\\Foundation\\Http\\Kernel->sendRequestThroughRouter(Object(Illuminate\\Http\\Request))
#40 /home/jiminny/public/index.php(51): Illuminate\\Foundation\\Http\\Kernel->handle(Object(Illuminate\\Http\\Request))
#41 {main}
"} {"correlation_id":"de768781-5c05-418c-baf3-79f77a8c8694","trace_id":"fec74062-3f6d-4cfc-968a-1bb285496db3"}
[2026-04-27 10:28:12] local.INFO: Jiminny\Console\Commands\Command::run Memory usage before starting command {"command":"mailbox:skip-lists:refresh","memoryBeforeCommandInMb":60.0,"memoryPeakBeforeCommandInMb":99.727} {"correlation_id":"907d15ed-8c84-41a1-a473-590eebb55dbc","trace_id":"ec7bd353-516e-491d-89dd-de44136c1672"}
[2026-04-27 10:28:12] local.INFO: Jiminny\Console\Commands\Command::run Memory usage for command {"command":"mailbox:skip-lists:refresh","memoryBeforeCommandInMb":60.0,"memoryAfterCommandInMB":62.0,"memoryPeakBeforeCommandInMb":99.727,"memoryPeakAfterCommandInMB":99.727} {"correlation_id":"907d15ed-8c84-41a1-a473-590eebb55dbc","trace_id":"ec7bd353-516e-491d-89dd-de44136c1672"}
[2026-04-27 10:28:13] local.ERROR: Target class [Jiminny\Repositories\AjReportsRepository] does not exist. {"exception":"[object] (Illuminate\\Contracts\\Container\\BindingResolutionException(code: 0): Target class [Jiminny\\Repositories\\AjReportsRepository] does not exist. at /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php:1124)
[stacktrace]
#0 /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php(933): Illuminate\\Container\\Container->build('Jiminny\\\\Reposit...')
#1 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Application.php(1078): Illuminate\\Container\\Container->resolve('Jiminny\\\\Reposit...', Array, true)
#2 /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php(864): Illuminate\\Foundation\\Application->resolve('Jiminny\\\\Reposit...', Array)
#3 /home/jiminny/vendor/laravel/framewo...
|
[{"role":"AXButton","text" [{"role":"AXButton","text":"Project: faVsco.js, menu","depth":5,"on_screen":true,"help_text":"~/jiminny/app","role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"master, menu","depth":5,"on_screen":true,"help_text":"Git Branch: master","role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Start Listening for PHP Debug Connections","depth":5,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"AskJiminnyReportActivityServiceTest","depth":6,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Run 'AskJiminnyReportActivityServiceTest'","depth":6,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Debug 'AskJiminnyReportActivityServiceTest'","depth":6,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"More Actions","depth":6,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"JetBrains AI","depth":5,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Search Everywhere","depth":5,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"IDE and Project Settings","depth":5,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXTextArea","text":"[2026-04-27 10:26:25] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage before starting command {\"command\":\"mailbox:sync\",\"memoryBeforeCommandInMb\":60.0,\"memoryPeakBeforeCommandInMb\":99.727} {\"correlation_id\":\"7b1ea2fb-8f36-4897-9970-91b4abba2cc7\",\"trace_id\":\"265be7c0-bf8f-4037-a0d2-57df3ef1d7e1\"}\n[2026-04-27 10:26:25] local.INFO: [EmailSchedule] STARTING Inbox Sync {\"host\":\"docker_lamp_1\"} {\"correlation_id\":\"7b1ea2fb-8f36-4897-9970-91b4abba2cc7\",\"trace_id\":\"265be7c0-bf8f-4037-a0d2-57df3ef1d7e1\"}\n[2026-04-27 10:26:25] local.INFO: [EmailSchedule] FINISHED Inbox Sync {\"host\":\"docker_lamp_1\",\"events\":2} {\"correlation_id\":\"7b1ea2fb-8f36-4897-9970-91b4abba2cc7\",\"trace_id\":\"265be7c0-bf8f-4037-a0d2-57df3ef1d7e1\"}\n[2026-04-27 10:26:25] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage for command {\"command\":\"mailbox:sync\",\"memoryBeforeCommandInMb\":60.0,\"memoryAfterCommandInMB\":62.0,\"memoryPeakBeforeCommandInMb\":99.727,\"memoryPeakAfterCommandInMB\":99.727} {\"correlation_id\":\"7b1ea2fb-8f36-4897-9970-91b4abba2cc7\",\"trace_id\":\"265be7c0-bf8f-4037-a0d2-57df3ef1d7e1\"}\n[2026-04-27 10:26:26] local.INFO: [automated-reports] Started {\"correlation_id\":\"d3f79034-397b-4936-9305-bdd99b23f2dd\",\"trace_id\":\"aa0f8702-73eb-4947-a765-2e89244599c3\"}\n[2026-04-27 10:26:26] local.INFO: [automated-reports] Checking conditions {\"isMonday\":true,\"isWeekend\":false,\"isFirstDayOfMonth\":false,\"currentMonth\":4,\"isQuarterlyMonth\":true} {\"correlation_id\":\"d3f79034-397b-4936-9305-bdd99b23f2dd\",\"trace_id\":\"aa0f8702-73eb-4947-a765-2e89244599c3\"}\n[2026-04-27 10:26:26] local.INFO: [automated-reports] Processing daily reports {\"correlation_id\":\"d3f79034-397b-4936-9305-bdd99b23f2dd\",\"trace_id\":\"aa0f8702-73eb-4947-a765-2e89244599c3\"}\n[2026-04-27 10:26:26] local.INFO: [automated-reports] Found 1 daily reports to process {\"correlation_id\":\"d3f79034-397b-4936-9305-bdd99b23f2dd\",\"trace_id\":\"aa0f8702-73eb-4947-a765-2e89244599c3\"}\n[2026-04-27 10:26:26] local.INFO: [automated-reports] Dispatching Generate Report job for report {\"reportUuid\":\"4f6ca2b5-1993-48aa-99ad-b66f19f15d43\",\"teamId\":1,\"frequency\":\"weekly\",\"type\":\"ask_jiminny\"} {\"correlation_id\":\"d3f79034-397b-4936-9305-bdd99b23f2dd\",\"trace_id\":\"aa0f8702-73eb-4947-a765-2e89244599c3\"}\n[2026-04-27 10:26:26] local.INFO: [automated-reports] Processing weekly reports {\"correlation_id\":\"d3f79034-397b-4936-9305-bdd99b23f2dd\",\"trace_id\":\"aa0f8702-73eb-4947-a765-2e89244599c3\"}\n[2026-04-27 10:26:26] local.INFO: [automated-reports] Found 1 weekly reports to process {\"correlation_id\":\"d3f79034-397b-4936-9305-bdd99b23f2dd\",\"trace_id\":\"aa0f8702-73eb-4947-a765-2e89244599c3\"}\n[2026-04-27 10:26:26] local.INFO: [automated-reports] Dispatching Generate Report job for report {\"reportUuid\":\"4f6ca2b5-1993-48aa-99ad-b66f19f15d43\",\"teamId\":1,\"frequency\":\"weekly\",\"type\":\"ask_jiminny\"} {\"correlation_id\":\"d3f79034-397b-4936-9305-bdd99b23f2dd\",\"trace_id\":\"aa0f8702-73eb-4947-a765-2e89244599c3\"}\n[2026-04-27 10:26:26] local.INFO: [automated-reports] Completed {\"correlation_id\":\"d3f79034-397b-4936-9305-bdd99b23f2dd\",\"trace_id\":\"aa0f8702-73eb-4947-a765-2e89244599c3\"}\n[2026-04-27 10:26:26] local.INFO: [HubSpot Journal Polling] Getting offset from database {\"offset\":\"\",\"jiminny_team_id\":1} {\"correlation_id\":\"b453f0f4-0d2c-491c-a935-14c60265ec18\",\"trace_id\":\"a50202d6-5601-46b4-a02e-cf5ac8d061fa\"}\n[2026-04-27 10:26:26] local.INFO: [HubSpot Journal API] Fetching latest journal entry {\"url\":\"https://api.hubapi.com/webhooks/v4/journal/latest\"} {\"correlation_id\":\"b453f0f4-0d2c-491c-a935-14c60265ec18\",\"trace_id\":\"a50202d6-5601-46b4-a02e-cf5ac8d061fa\"}\n[2026-04-27 10:26:26] local.INFO: [AskJiminnyReport:Generate] Started {\"automatedReportUuid\":\"4f6ca2b5-1993-48aa-99ad-b66f19f15d43\"} {\"correlation_id\":\"c6277ddd-09a6-4b04-8d42-bd395fc07d0a\",\"trace_id\":\"aa0f8702-73eb-4947-a765-2e89244599c3\"}\n[2026-04-27 10:26:26] local.INFO: [HubSpot Journal Polling] No data {\"correlation_id\":\"b453f0f4-0d2c-491c-a935-14c60265ec18\",\"trace_id\":\"a50202d6-5601-46b4-a02e-cf5ac8d061fa\"}\n[2026-04-27 10:26:26] local.WARNING: [HubSpot Journal Polling] Maximum empty results reached, stopping {\"empty_results\":5,\"max_empty_results\":5} {\"correlation_id\":\"b453f0f4-0d2c-491c-a935-14c60265ec18\",\"trace_id\":\"a50202d6-5601-46b4-a02e-cf5ac8d061fa\"}\n[2026-04-27 10:26:26] local.WARNING: [HubSpot Journal Polling] Maximum empty results reached, stopping {\"empty_results\":5,\"max_empty_results\":5} {\"correlation_id\":\"b453f0f4-0d2c-491c-a935-14c60265ec18\",\"trace_id\":\"a50202d6-5601-46b4-a02e-cf5ac8d061fa\"}\n[2026-04-27 10:26:26] local.INFO: [HubSpot Journal Polling] Service ending {\"runtime_seconds\":56,\"total_cycles\":5,\"files_downloaded\":0,\"empty_files\":0,\"other_portal_skipped\":0,\"total_events\":0,\"events_per_file\":0,\"avg_api_ms\":273.7,\"avg_download_ms\":0.0,\"avg_transform_ms\":0.0,\"avg_process_ms\":0.0,\"peak_memory_mb\":99.73} {\"correlation_id\":\"b453f0f4-0d2c-491c-a935-14c60265ec18\",\"trace_id\":\"a50202d6-5601-46b4-a02e-cf5ac8d061fa\"}\n[2026-04-27 10:26:26] local.INFO: [HubSpot Journal Polling] Released polling lock {\"correlation_id\":\"b453f0f4-0d2c-491c-a935-14c60265ec18\",\"trace_id\":\"a50202d6-5601-46b4-a02e-cf5ac8d061fa\"}\n[2026-04-27 10:26:27] local.INFO: [AskJiminnyReport] Fetched activity IDs for saved search {\"saved_search_id\":1977,\"user_id\":143,\"activity_count\":0} {\"correlation_id\":\"c6277ddd-09a6-4b04-8d42-bd395fc07d0a\",\"trace_id\":\"aa0f8702-73eb-4947-a765-2e89244599c3\"}\n[2026-04-27 10:26:27] local.INFO: [AskJiminnyReport:Generate] Fetched activity IDs {\"automatedReportUuid\":\"4f6ca2b5-1993-48aa-99ad-b66f19f15d43\",\"activityCount\":0} {\"correlation_id\":\"c6277ddd-09a6-4b04-8d42-bd395fc07d0a\",\"trace_id\":\"aa0f8702-73eb-4947-a765-2e89244599c3\"}\n[2026-04-27 10:26:27] local.INFO: [AskJiminnyReport:Generate] Not enough activities, skipped {\"automatedReportUuid\":\"4f6ca2b5-1993-48aa-99ad-b66f19f15d43\",\"activityCount\":0} {\"correlation_id\":\"c6277ddd-09a6-4b04-8d42-bd395fc07d0a\",\"trace_id\":\"aa0f8702-73eb-4947-a765-2e89244599c3\"}\n[2026-04-27 10:26:27] local.INFO: [AskJiminnyReport:Generate] Dispatched not-generated notifications {\"automatedReportUuid\":\"4f6ca2b5-1993-48aa-99ad-b66f19f15d43\",\"recipientsCount\":1} {\"correlation_id\":\"c6277ddd-09a6-4b04-8d42-bd395fc07d0a\",\"trace_id\":\"aa0f8702-73eb-4947-a765-2e89244599c3\"}\n[2026-04-27 10:26:27] local.INFO: [AskJiminnyReport:Generate] Started {\"automatedReportUuid\":\"4f6ca2b5-1993-48aa-99ad-b66f19f15d43\"} {\"correlation_id\":\"f1d3e4df-9591-487f-ac2a-a43ddb89a466\",\"trace_id\":\"aa0f8702-73eb-4947-a765-2e89244599c3\"}\n[2026-04-27 10:26:27] local.INFO: [AskJiminnyReport] Fetched activity IDs for saved search {\"saved_search_id\":1977,\"user_id\":143,\"activity_count\":0} {\"correlation_id\":\"f1d3e4df-9591-487f-ac2a-a43ddb89a466\",\"trace_id\":\"aa0f8702-73eb-4947-a765-2e89244599c3\"}\n[2026-04-27 10:26:27] local.INFO: [AskJiminnyReport:Generate] Fetched activity IDs {\"automatedReportUuid\":\"4f6ca2b5-1993-48aa-99ad-b66f19f15d43\",\"activityCount\":0} {\"correlation_id\":\"f1d3e4df-9591-487f-ac2a-a43ddb89a466\",\"trace_id\":\"aa0f8702-73eb-4947-a765-2e89244599c3\"}\n[2026-04-27 10:26:27] local.INFO: [AskJiminnyReport:Generate] Not enough activities, skipped {\"automatedReportUuid\":\"4f6ca2b5-1993-48aa-99ad-b66f19f15d43\",\"activityCount\":0} {\"correlation_id\":\"f1d3e4df-9591-487f-ac2a-a43ddb89a466\",\"trace_id\":\"aa0f8702-73eb-4947-a765-2e89244599c3\"}\n[2026-04-27 10:26:27] local.INFO: [AskJiminnyReport:Generate] Dispatched not-generated notifications {\"automatedReportUuid\":\"4f6ca2b5-1993-48aa-99ad-b66f19f15d43\",\"recipientsCount\":1} {\"correlation_id\":\"f1d3e4df-9591-487f-ac2a-a43ddb89a466\",\"trace_id\":\"aa0f8702-73eb-4947-a765-2e89244599c3\"}\n[2026-04-27 10:26:27] local.INFO: [Send Report Not Generated Mail] Email sent {\"uuid\":\"cd5b5081-22f8-4d1a-9d6f-f82aca1121f6\",\"email\":\"lukas.kovalik@jiminny.com\"} {\"correlation_id\":\"4255983f-07a4-4872-9ea6-5491fa32bdd5\",\"trace_id\":\"aa0f8702-73eb-4947-a765-2e89244599c3\"}\n[2026-04-27 10:26:28] local.INFO: [Sync Mailbox] Sync start {\"inbox_id\":59} {\"correlation_id\":\"280d5bf6-c34c-45ea-bea7-05d9c028e9d3\",\"trace_id\":\"265be7c0-bf8f-4037-a0d2-57df3ef1d7e1\"}\n[2026-04-27 10:26:28] local.INFO: [Inbox service] Skipping METADATA SYNC for inbox 59 due to unauthorized access to the mailbox {\"correlation_id\":\"280d5bf6-c34c-45ea-bea7-05d9c028e9d3\",\"trace_id\":\"265be7c0-bf8f-4037-a0d2-57df3ef1d7e1\"}\n[2026-04-27 10:26:28] local.INFO: [Sync Mailbox] Sync complete {\"inbox_id\":59} {\"correlation_id\":\"280d5bf6-c34c-45ea-bea7-05d9c028e9d3\",\"trace_id\":\"265be7c0-bf8f-4037-a0d2-57df3ef1d7e1\"}\n[2026-04-27 10:26:28] local.INFO: [Sync Mailbox] Sync start {\"inbox_id\":212} {\"correlation_id\":\"d3e3eb3d-5c83-4d6e-9592-aaa38c1843a4\",\"trace_id\":\"265be7c0-bf8f-4037-a0d2-57df3ef1d7e1\"}\n[2026-04-27 10:26:28] local.INFO: [SocialAccountService] Fetching token {\"socialAccountId\":1500,\"provider\":\"salesforce\"} {\"correlation_id\":\"d3e3eb3d-5c83-4d6e-9592-aaa38c1843a4\",\"trace_id\":\"265be7c0-bf8f-4037-a0d2-57df3ef1d7e1\"}\n[2026-04-27 10:26:28] local.INFO: [SocialAccountService] Token retrieved {\"socialAccountId\":1500,\"provider\":\"salesforce\"} {\"correlation_id\":\"d3e3eb3d-5c83-4d6e-9592-aaa38c1843a4\",\"trace_id\":\"265be7c0-bf8f-4037-a0d2-57df3ef1d7e1\"}\n[2026-04-27 10:26:28] local.INFO: [EncryptedTokenManager] Generating access token. {\"mode\":\"legacy\"} {\"correlation_id\":\"d3e3eb3d-5c83-4d6e-9592-aaa38c1843a4\",\"trace_id\":\"265be7c0-bf8f-4037-a0d2-57df3ef1d7e1\"}\n[2026-04-27 10:26:28] local.INFO: [CrmOwnerResolver] Integration owner matched as CRM Owner {\"crm_provider\":\"salesforce\",\"crm_owner\":143,\"team_id\":1} {\"correlation_id\":\"d3e3eb3d-5c83-4d6e-9592-aaa38c1843a4\",\"trace_id\":\"265be7c0-bf8f-4037-a0d2-57df3ef1d7e1\"}\n[2026-04-27 10:26:28] local.INFO: [SocialAccountService] Fetching token {\"socialAccountId\":1354,\"provider\":\"google\"} {\"correlation_id\":\"d3e3eb3d-5c83-4d6e-9592-aaa38c1843a4\",\"trace_id\":\"265be7c0-bf8f-4037-a0d2-57df3ef1d7e1\"}\n[2026-04-27 10:26:28] local.INFO: [SocialAccountService] Token retrieved {\"socialAccountId\":1354,\"provider\":\"google\"} {\"correlation_id\":\"d3e3eb3d-5c83-4d6e-9592-aaa38c1843a4\",\"trace_id\":\"265be7c0-bf8f-4037-a0d2-57df3ef1d7e1\"}\n[2026-04-27 10:26:28] local.INFO: [EncryptedTokenManager] Generating access token. {\"mode\":\"legacy\"} {\"correlation_id\":\"d3e3eb3d-5c83-4d6e-9592-aaa38c1843a4\",\"trace_id\":\"265be7c0-bf8f-4037-a0d2-57df3ef1d7e1\"}\n[2026-04-27 10:26:28] local.INFO: [Gmail] Performing incremental sync for inbox 212 using history ID: @1777284288 {\"correlation_id\":\"d3e3eb3d-5c83-4d6e-9592-aaa38c1843a4\",\"trace_id\":\"265be7c0-bf8f-4037-a0d2-57df3ef1d7e1\"}\n[2026-04-27 10:26:28] local.INFO: [Gmail] imported 7 emails via full sync workflow for inbox 212 {\"correlation_id\":\"d3e3eb3d-5c83-4d6e-9592-aaa38c1843a4\",\"trace_id\":\"265be7c0-bf8f-4037-a0d2-57df3ef1d7e1\"}\n[2026-04-27 10:26:28] local.INFO: [Gmail] seeding inbox 212 with last message time : 2026-04-27 10:24:57 {\"correlation_id\":\"d3e3eb3d-5c83-4d6e-9592-aaa38c1843a4\",\"trace_id\":\"265be7c0-bf8f-4037-a0d2-57df3ef1d7e1\"}\n[2026-04-27 10:26:28] local.INFO: [Sync Mailbox] Sync complete {\"inbox_id\":212} {\"correlation_id\":\"d3e3eb3d-5c83-4d6e-9592-aaa38c1843a4\",\"trace_id\":\"265be7c0-bf8f-4037-a0d2-57df3ef1d7e1\"}\n[2026-04-27 10:27:04] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage before starting command {\"command\":\"meeting-bot:schedule-bot\",\"memoryBeforeCommandInMb\":60.0,\"memoryPeakBeforeCommandInMb\":99.727} {\"correlation_id\":\"f55fbf77-7e5f-4c87-accd-011697ca0fa3\",\"trace_id\":\"9bd1c431-26de-4e31-83d8-3129ed9b3b79\"}\n[2026-04-27 10:27:04] local.INFO: [ScheduleBotCommand] Number of activities to be captured: 0 {\"correlation_id\":\"f55fbf77-7e5f-4c87-accd-011697ca0fa3\",\"trace_id\":\"9bd1c431-26de-4e31-83d8-3129ed9b3b79\"}\n[2026-04-27 10:27:04] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage for command {\"command\":\"meeting-bot:schedule-bot\",\"memoryBeforeCommandInMb\":60.0,\"memoryAfterCommandInMB\":62.0,\"memoryPeakBeforeCommandInMb\":99.727,\"memoryPeakAfterCommandInMB\":99.727} {\"correlation_id\":\"f55fbf77-7e5f-4c87-accd-011697ca0fa3\",\"trace_id\":\"9bd1c431-26de-4e31-83d8-3129ed9b3b79\"}\n[2026-04-27 10:27:05] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage before starting command {\"command\":\"dialers:monitor-activities\",\"memoryBeforeCommandInMb\":60.0,\"memoryPeakBeforeCommandInMb\":99.727} {\"correlation_id\":\"da241053-5f3b-4d02-9504-4ef2987cdbd7\",\"trace_id\":\"364d3fc7-0f11-40c6-8697-d18eff2c4cb6\"}\n[2026-04-27 10:27:05] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage for command {\"command\":\"dialers:monitor-activities\",\"memoryBeforeCommandInMb\":60.0,\"memoryAfterCommandInMB\":62.0,\"memoryPeakBeforeCommandInMb\":99.727,\"memoryPeakAfterCommandInMB\":99.727} {\"correlation_id\":\"da241053-5f3b-4d02-9504-4ef2987cdbd7\",\"trace_id\":\"364d3fc7-0f11-40c6-8697-d18eff2c4cb6\"}\n[2026-04-27 10:27:06] local.NOTICE: Monitoring start {\"correlation_id\":\"ad0d871a-a1fd-4725-b2f7-ac8f0fc1857e\",\"trace_id\":\"cb504e84-d924-43a0-82d5-faf1840134ba\"}\n[2026-04-27 10:27:06] local.NOTICE: Monitoring end {\"correlation_id\":\"ad0d871a-a1fd-4725-b2f7-ac8f0fc1857e\",\"trace_id\":\"cb504e84-d924-43a0-82d5-faf1840134ba\"}\n[2026-04-27 10:27:08] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage before starting command {\"command\":\"mailbox:skip-lists:refresh\",\"memoryBeforeCommandInMb\":60.0,\"memoryPeakBeforeCommandInMb\":99.727} {\"correlation_id\":\"b8652f0d-1cee-44ae-a0ea-3d3137a882c1\",\"trace_id\":\"7fe19940-0813-4199-a2a8-958de09bd0ee\"}\n[2026-04-27 10:27:08] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage for command {\"command\":\"mailbox:skip-lists:refresh\",\"memoryBeforeCommandInMb\":60.0,\"memoryAfterCommandInMB\":62.0,\"memoryPeakBeforeCommandInMb\":99.727,\"memoryPeakAfterCommandInMB\":99.727} {\"correlation_id\":\"b8652f0d-1cee-44ae-a0ea-3d3137a882c1\",\"trace_id\":\"7fe19940-0813-4199-a2a8-958de09bd0ee\"}\n[2026-04-27 10:27:09] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage before starting command {\"command\":\"mailbox:batch:process\",\"memoryBeforeCommandInMb\":60.0,\"memoryPeakBeforeCommandInMb\":99.727} {\"correlation_id\":\"06190462-0f47-403f-8e95-ad5d05bff1fe\",\"trace_id\":\"4aa6bed6-05a1-4661-a9c9-edadc61d84ac\"}\n[2026-04-27 10:27:09] local.INFO: [EmailSchedule] STARTING batch process {\"host\":\"docker_lamp_1\"} {\"correlation_id\":\"06190462-0f47-403f-8e95-ad5d05bff1fe\",\"trace_id\":\"4aa6bed6-05a1-4661-a9c9-edadc61d84ac\"}\n[2026-04-27 10:27:09] local.INFO: [EmailSchedule] FINISHED batch process {\"host\":\"docker_lamp_1\",\"processed\":0} {\"correlation_id\":\"06190462-0f47-403f-8e95-ad5d05bff1fe\",\"trace_id\":\"4aa6bed6-05a1-4661-a9c9-edadc61d84ac\"}\n[2026-04-27 10:27:09] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage for command {\"command\":\"mailbox:batch:process\",\"memoryBeforeCommandInMb\":60.0,\"memoryAfterCommandInMB\":62.0,\"memoryPeakBeforeCommandInMb\":99.727,\"memoryPeakAfterCommandInMB\":99.727} {\"correlation_id\":\"06190462-0f47-403f-8e95-ad5d05bff1fe\",\"trace_id\":\"4aa6bed6-05a1-4661-a9c9-edadc61d84ac\"}\n[2026-04-27 10:27:11] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage before starting command {\"command\":\"mailbox:batch:create\",\"memoryBeforeCommandInMb\":60.0,\"memoryPeakBeforeCommandInMb\":99.727} {\"correlation_id\":\"b561c5d5-e6e3-4091-9517-b0458190a360\",\"trace_id\":\"bf9679fb-b785-40cb-b814-04a1dc385a1c\"}\n[2026-04-27 10:27:11] local.INFO: [EmailSchedule] STARTING batch create {\"host\":\"docker_lamp_1\"} {\"correlation_id\":\"b561c5d5-e6e3-4091-9517-b0458190a360\",\"trace_id\":\"bf9679fb-b785-40cb-b814-04a1dc385a1c\"}\n[2026-04-27 10:27:11] local.INFO: [EmailSchedule] FINISHED batch create {\"host\":\"docker_lamp_1\"} {\"correlation_id\":\"b561c5d5-e6e3-4091-9517-b0458190a360\",\"trace_id\":\"bf9679fb-b785-40cb-b814-04a1dc385a1c\"}\n[2026-04-27 10:27:11] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage for command {\"command\":\"mailbox:batch:create\",\"memoryBeforeCommandInMb\":60.0,\"memoryAfterCommandInMB\":62.0,\"memoryPeakBeforeCommandInMb\":99.727,\"memoryPeakAfterCommandInMB\":99.727} {\"correlation_id\":\"b561c5d5-e6e3-4091-9517-b0458190a360\",\"trace_id\":\"bf9679fb-b785-40cb-b814-04a1dc385a1c\"}\n[2026-04-27 10:27:14] local.INFO: [Jiminny\\Jobs\\Mailbox\\CreateBatches] processed 2 inboxes and created 1 batches {\"userId\":null,\"batchSize\":30,\"maxBatches\":1000} {\"correlation_id\":\"98416296-43c2-4834-927e-4a0e86dc99cf\",\"trace_id\":\"bf9679fb-b785-40cb-b814-04a1dc385a1c\"}\n[2026-04-27 10:28:04] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage before starting command {\"command\":\"meeting-bot:schedule-bot\",\"memoryBeforeCommandInMb\":60.0,\"memoryPeakBeforeCommandInMb\":99.727} {\"correlation_id\":\"5852bcf1-af92-4d24-8a2d-08a9d4a5cfac\",\"trace_id\":\"25af5e8c-a148-493e-ac9f-4d53f1960177\"}\n[2026-04-27 10:28:04] local.INFO: [ScheduleBotCommand] Number of activities to be captured: 0 {\"correlation_id\":\"5852bcf1-af92-4d24-8a2d-08a9d4a5cfac\",\"trace_id\":\"25af5e8c-a148-493e-ac9f-4d53f1960177\"}\n[2026-04-27 10:28:04] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage for command {\"command\":\"meeting-bot:schedule-bot\",\"memoryBeforeCommandInMb\":60.0,\"memoryAfterCommandInMB\":62.0,\"memoryPeakBeforeCommandInMb\":99.727,\"memoryPeakAfterCommandInMB\":99.727} {\"correlation_id\":\"5852bcf1-af92-4d24-8a2d-08a9d4a5cfac\",\"trace_id\":\"25af5e8c-a148-493e-ac9f-4d53f1960177\"}\n[2026-04-27 10:28:06] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage before starting command {\"command\":\"dialers:monitor-activities\",\"memoryBeforeCommandInMb\":60.0,\"memoryPeakBeforeCommandInMb\":99.727} {\"correlation_id\":\"cc1cff1f-71b4-4bb1-800f-66e27c0f2b3a\",\"trace_id\":\"c03103a3-9ab8-480b-9753-7c7c6488f246\"}\n[2026-04-27 10:28:06] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage for command {\"command\":\"dialers:monitor-activities\",\"memoryBeforeCommandInMb\":60.0,\"memoryAfterCommandInMB\":62.0,\"memoryPeakBeforeCommandInMb\":99.727,\"memoryPeakAfterCommandInMB\":99.727} {\"correlation_id\":\"cc1cff1f-71b4-4bb1-800f-66e27c0f2b3a\",\"trace_id\":\"c03103a3-9ab8-480b-9753-7c7c6488f246\"}\n[2026-04-27 10:28:09] local.ERROR: Target class [Jiminny\\Repositories\\AjReportsRepository] does not exist. {\"exception\":\"[object] (Illuminate\\\\Contracts\\\\Container\\\\BindingResolutionException(code: 0): Target class [Jiminny\\\\Repositories\\\\AjReportsRepository] does not exist. at /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php:1124)\n[stacktrace]\n#0 /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php(933): Illuminate\\\\Container\\\\Container->build('Jiminny\\\\\\\\Reposit...')\n#1 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Application.php(1078): Illuminate\\\\Container\\\\Container->resolve('Jiminny\\\\\\\\Reposit...', Array, true)\n#2 /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php(864): Illuminate\\\\Foundation\\\\Application->resolve('Jiminny\\\\\\\\Reposit...', Array)\n#3 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Application.php(1058): Illuminate\\\\Container\\\\Container->make('Jiminny\\\\\\\\Reposit...', Array)\n#4 /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php(1336): Illuminate\\\\Foundation\\\\Application->make('Jiminny\\\\\\\\Reposit...')\n#5 /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php(1237): Illuminate\\\\Container\\\\Container->resolveClass(Object(ReflectionParameter))\n#6 /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php(1162): Illuminate\\\\Container\\\\Container->resolveDependencies(Array)\n#7 /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php(933): Illuminate\\\\Container\\\\Container->build('Jiminny\\\\\\\\Http\\\\\\\\Co...')\n#8 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Application.php(1078): Illuminate\\\\Container\\\\Container->resolve('Jiminny\\\\\\\\Http\\\\\\\\Co...', Array, true)\n#9 /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php(864): Illuminate\\\\Foundation\\\\Application->resolve('Jiminny\\\\\\\\Http\\\\\\\\Co...', Array)\n#10 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Application.php(1058): Illuminate\\\\Container\\\\Container->make('Jiminny\\\\\\\\Http\\\\\\\\Co...', Array)\n#11 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Route.php(286): Illuminate\\\\Foundation\\\\Application->make('Jiminny\\\\\\\\Http\\\\\\\\Co...')\n#12 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Route.php(1133): Illuminate\\\\Routing\\\\Route->getController()\n#13 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Route.php(1062): Illuminate\\\\Routing\\\\Route->controllerMiddleware()\n#14 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Router.php(834): Illuminate\\\\Routing\\\\Route->gatherMiddleware()\n#15 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Router.php(816): Illuminate\\\\Routing\\\\Router->gatherRouteMiddleware(Object(Illuminate\\\\Routing\\\\Route))\n#16 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Router.php(800): Illuminate\\\\Routing\\\\Router->runRouteWithinStack(Object(Illuminate\\\\Routing\\\\Route), Object(Illuminate\\\\Http\\\\Request))\n#17 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Router.php(764): Illuminate\\\\Routing\\\\Router->runRoute(Object(Illuminate\\\\Http\\\\Request), Object(Illuminate\\\\Routing\\\\Route))\n#18 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Router.php(753): Illuminate\\\\Routing\\\\Router->dispatchToRoute(Object(Illuminate\\\\Http\\\\Request))\n#19 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Http/Kernel.php(200): Illuminate\\\\Routing\\\\Router->dispatch(Object(Illuminate\\\\Http\\\\Request))\n#20 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(180): Illuminate\\\\Foundation\\\\Http\\\\Kernel->Illuminate\\\\Foundation\\\\Http\\\\{closure}(Object(Illuminate\\\\Http\\\\Request))\n#21 /home/jiminny/vendor/barryvdh/laravel-debugbar/src/Middleware/InjectDebugbar.php(59): Illuminate\\\\Pipeline\\\\Pipeline->Illuminate\\\\Pipeline\\\\{closure}(Object(Illuminate\\\\Http\\\\Request))\n#22 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(219): Barryvdh\\\\Debugbar\\\\Middleware\\\\InjectDebugbar->handle(Object(Illuminate\\\\Http\\\\Request), Object(Closure))\n#23 /home/jiminny/vendor/laravel/framework/src/Illuminate/Http/Middleware/HandleCors.php(74): Illuminate\\\\Pipeline\\\\Pipeline->Illuminate\\\\Pipeline\\\\{closure}(Object(Illuminate\\\\Http\\\\Request))\n#24 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(219): Illuminate\\\\Http\\\\Middleware\\\\HandleCors->handle(Object(Illuminate\\\\Http\\\\Request), Object(Closure))\n#25 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Http/Middleware/TransformsRequest.php(21): Illuminate\\\\Pipeline\\\\Pipeline->Illuminate\\\\Pipeline\\\\{closure}(Object(Illuminate\\\\Http\\\\Request))\n#26 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Http/Middleware/TrimStrings.php(51): Illuminate\\\\Foundation\\\\Http\\\\Middleware\\\\TransformsRequest->handle(Object(Illuminate\\\\Http\\\\Request), Object(Closure))\n#27 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(219): Illuminate\\\\Foundation\\\\Http\\\\Middleware\\\\TrimStrings->handle(Object(Illuminate\\\\Http\\\\Request), Object(Closure))\n#28 /home/jiminny/vendor/bepsvpt/secure-headers/src/SecureHeadersMiddleware.php(18): Illuminate\\\\Pipeline\\\\Pipeline->Illuminate\\\\Pipeline\\\\{closure}(Object(Illuminate\\\\Http\\\\Request))\n#29 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(219): Bepsvpt\\\\SecureHeaders\\\\SecureHeadersMiddleware->handle(Object(Illuminate\\\\Http\\\\Request), Object(Closure))\n#30 /home/jiminny/app/Http/Middleware/SentryContext.php(60): Illuminate\\\\Pipeline\\\\Pipeline->Illuminate\\\\Pipeline\\\\{closure}(Object(Illuminate\\\\Http\\\\Request))\n#31 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(219): Jiminny\\\\Http\\\\Middleware\\\\SentryContext->handle(Object(Illuminate\\\\Http\\\\Request), Object(Closure))\n#32 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Http/Middleware/PreventRequestsDuringMaintenance.php(109): Illuminate\\\\Pipeline\\\\Pipeline->Illuminate\\\\Pipeline\\\\{closure}(Object(Illuminate\\\\Http\\\\Request))\n#33 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(219): Illuminate\\\\Foundation\\\\Http\\\\Middleware\\\\PreventRequestsDuringMaintenance->handle(Object(Illuminate\\\\Http\\\\Request), Object(Closure))\n#34 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Http/Middleware/InvokeDeferredCallbacks.php(22): Illuminate\\\\Pipeline\\\\Pipeline->Illuminate\\\\Pipeline\\\\{closure}(Object(Illuminate\\\\Http\\\\Request))\n#35 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(219): Illuminate\\\\Foundation\\\\Http\\\\Middleware\\\\InvokeDeferredCallbacks->handle(Object(Illuminate\\\\Http\\\\Request), Object(Closure))\n#36 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(137): Illuminate\\\\Pipeline\\\\Pipeline->Illuminate\\\\Pipeline\\\\{closure}(Object(Illuminate\\\\Http\\\\Request))\n#37 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Http/Kernel.php(175): Illuminate\\\\Pipeline\\\\Pipeline->then(Object(Closure))\n#38 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Http/Kernel.php(144): Illuminate\\\\Foundation\\\\Http\\\\Kernel->sendRequestThroughRouter(Object(Illuminate\\\\Http\\\\Request))\n#39 /home/jiminny/public/index.php(51): Illuminate\\\\Foundation\\\\Http\\\\Kernel->handle(Object(Illuminate\\\\Http\\\\Request))\n#40 {main}\n\n[previous exception] [object] (ReflectionException(code: -1): Class \\\"Jiminny\\\\Repositories\\\\AjReportsRepository\\\" does not exist at /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php:1122)\n[stacktrace]\n#0 /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php(1122): ReflectionClass->__construct('Jiminny\\\\\\\\Reposit...')\n#1 /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php(933): Illuminate\\\\Container\\\\Container->build('Jiminny\\\\\\\\Reposit...')\n#2 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Application.php(1078): Illuminate\\\\Container\\\\Container->resolve('Jiminny\\\\\\\\Reposit...', Array, true)\n#3 /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php(864): Illuminate\\\\Foundation\\\\Application->resolve('Jiminny\\\\\\\\Reposit...', Array)\n#4 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Application.php(1058): Illuminate\\\\Container\\\\Container->make('Jiminny\\\\\\\\Reposit...', Array)\n#5 /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php(1336): Illuminate\\\\Foundation\\\\Application->make('Jiminny\\\\\\\\Reposit...')\n#6 /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php(1237): Illuminate\\\\Container\\\\Container->resolveClass(Object(ReflectionParameter))\n#7 /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php(1162): Illuminate\\\\Container\\\\Container->resolveDependencies(Array)\n#8 /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php(933): Illuminate\\\\Container\\\\Container->build('Jiminny\\\\\\\\Http\\\\\\\\Co...')\n#9 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Application.php(1078): Illuminate\\\\Container\\\\Container->resolve('Jiminny\\\\\\\\Http\\\\\\\\Co...', Array, true)\n#10 /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php(864): Illuminate\\\\Foundation\\\\Application->resolve('Jiminny\\\\\\\\Http\\\\\\\\Co...', Array)\n#11 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Application.php(1058): Illuminate\\\\Container\\\\Container->make('Jiminny\\\\\\\\Http\\\\\\\\Co...', Array)\n#12 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Route.php(286): Illuminate\\\\Foundation\\\\Application->make('Jiminny\\\\\\\\Http\\\\\\\\Co...')\n#13 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Route.php(1133): Illuminate\\\\Routing\\\\Route->getController()\n#14 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Route.php(1062): Illuminate\\\\Routing\\\\Route->controllerMiddleware()\n#15 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Router.php(834): Illuminate\\\\Routing\\\\Route->gatherMiddleware()\n#16 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Router.php(816): Illuminate\\\\Routing\\\\Router->gatherRouteMiddleware(Object(Illuminate\\\\Routing\\\\Route))\n#17 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Router.php(800): Illuminate\\\\Routing\\\\Router->runRouteWithinStack(Object(Illuminate\\\\Routing\\\\Route), Object(Illuminate\\\\Http\\\\Request))\n#18 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Router.php(764): Illuminate\\\\Routing\\\\Router->runRoute(Object(Illuminate\\\\Http\\\\Request), Object(Illuminate\\\\Routing\\\\Route))\n#19 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Router.php(753): Illuminate\\\\Routing\\\\Router->dispatchToRoute(Object(Illuminate\\\\Http\\\\Request))\n#20 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Http/Kernel.php(200): Illuminate\\\\Routing\\\\Router->dispatch(Object(Illuminate\\\\Http\\\\Request))\n#21 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(180): Illuminate\\\\Foundation\\\\Http\\\\Kernel->Illuminate\\\\Foundation\\\\Http\\\\{closure}(Object(Illuminate\\\\Http\\\\Request))\n#22 /home/jiminny/vendor/barryvdh/laravel-debugbar/src/Middleware/InjectDebugbar.php(59): Illuminate\\\\Pipeline\\\\Pipeline->Illuminate\\\\Pipeline\\\\{closure}(Object(Illuminate\\\\Http\\\\Request))\n#23 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(219): Barryvdh\\\\Debugbar\\\\Middleware\\\\InjectDebugbar->handle(Object(Illuminate\\\\Http\\\\Request), Object(Closure))\n#24 /home/jiminny/vendor/laravel/framework/src/Illuminate/Http/Middleware/HandleCors.php(74): Illuminate\\\\Pipeline\\\\Pipeline->Illuminate\\\\Pipeline\\\\{closure}(Object(Illuminate\\\\Http\\\\Request))\n#25 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(219): Illuminate\\\\Http\\\\Middleware\\\\HandleCors->handle(Object(Illuminate\\\\Http\\\\Request), Object(Closure))\n#26 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Http/Middleware/TransformsRequest.php(21): Illuminate\\\\Pipeline\\\\Pipeline->Illuminate\\\\Pipeline\\\\{closure}(Object(Illuminate\\\\Http\\\\Request))\n#27 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Http/Middleware/TrimStrings.php(51): Illuminate\\\\Foundation\\\\Http\\\\Middleware\\\\TransformsRequest->handle(Object(Illuminate\\\\Http\\\\Request), Object(Closure))\n#28 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(219): Illuminate\\\\Foundation\\\\Http\\\\Middleware\\\\TrimStrings->handle(Object(Illuminate\\\\Http\\\\Request), Object(Closure))\n#29 /home/jiminny/vendor/bepsvpt/secure-headers/src/SecureHeadersMiddleware.php(18): Illuminate\\\\Pipeline\\\\Pipeline->Illuminate\\\\Pipeline\\\\{closure}(Object(Illuminate\\\\Http\\\\Request))\n#30 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(219): Bepsvpt\\\\SecureHeaders\\\\SecureHeadersMiddleware->handle(Object(Illuminate\\\\Http\\\\Request), Object(Closure))\n#31 /home/jiminny/app/Http/Middleware/SentryContext.php(60): Illuminate\\\\Pipeline\\\\Pipeline->Illuminate\\\\Pipeline\\\\{closure}(Object(Illuminate\\\\Http\\\\Request))\n#32 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(219): Jiminny\\\\Http\\\\Middleware\\\\SentryContext->handle(Object(Illuminate\\\\Http\\\\Request), Object(Closure))\n#33 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Http/Middleware/PreventRequestsDuringMaintenance.php(109): Illuminate\\\\Pipeline\\\\Pipeline->Illuminate\\\\Pipeline\\\\{closure}(Object(Illuminate\\\\Http\\\\Request))\n#34 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(219): Illuminate\\\\Foundation\\\\Http\\\\Middleware\\\\PreventRequestsDuringMaintenance->handle(Object(Illuminate\\\\Http\\\\Request), Object(Closure))\n#35 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Http/Middleware/InvokeDeferredCallbacks.php(22): Illuminate\\\\Pipeline\\\\Pipeline->Illuminate\\\\Pipeline\\\\{closure}(Object(Illuminate\\\\Http\\\\Request))\n#36 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(219): Illuminate\\\\Foundation\\\\Http\\\\Middleware\\\\InvokeDeferredCallbacks->handle(Object(Illuminate\\\\Http\\\\Request), Object(Closure))\n#37 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(137): Illuminate\\\\Pipeline\\\\Pipeline->Illuminate\\\\Pipeline\\\\{closure}(Object(Illuminate\\\\Http\\\\Request))\n#38 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Http/Kernel.php(175): Illuminate\\\\Pipeline\\\\Pipeline->then(Object(Closure))\n#39 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Http/Kernel.php(144): Illuminate\\\\Foundation\\\\Http\\\\Kernel->sendRequestThroughRouter(Object(Illuminate\\\\Http\\\\Request))\n#40 /home/jiminny/public/index.php(51): Illuminate\\\\Foundation\\\\Http\\\\Kernel->handle(Object(Illuminate\\\\Http\\\\Request))\n#41 {main}\n\"} {\"correlation_id\":\"dfad8817-1dde-4622-8320-f5fc1616a8fe\",\"trace_id\":\"d21232b2-60fb-4ca0-838c-ba42e61593f9\"}\n[2026-04-27 10:28:09] local.NOTICE: Monitoring start {\"correlation_id\":\"93744c67-4444-41ff-aaad-6aa9cafe294e\",\"trace_id\":\"80e396b0-b1ac-4ec8-838c-00021f98072c\"}\n[2026-04-27 10:28:09] local.NOTICE: Monitoring end {\"correlation_id\":\"93744c67-4444-41ff-aaad-6aa9cafe294e\",\"trace_id\":\"80e396b0-b1ac-4ec8-838c-00021f98072c\"}\n[2026-04-27 10:28:10] local.ERROR: Target class [Jiminny\\Repositories\\AjReportsRepository] does not exist. {\"exception\":\"[object] (Illuminate\\\\Contracts\\\\Container\\\\BindingResolutionException(code: 0): Target class [Jiminny\\\\Repositories\\\\AjReportsRepository] does not exist. at /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php:1124)\n[stacktrace]\n#0 /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php(933): Illuminate\\\\Container\\\\Container->build('Jiminny\\\\\\\\Reposit...')\n#1 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Application.php(1078): Illuminate\\\\Container\\\\Container->resolve('Jiminny\\\\\\\\Reposit...', Array, true)\n#2 /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php(864): Illuminate\\\\Foundation\\\\Application->resolve('Jiminny\\\\\\\\Reposit...', Array)\n#3 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Application.php(1058): Illuminate\\\\Container\\\\Container->make('Jiminny\\\\\\\\Reposit...', Array)\n#4 /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php(1336): Illuminate\\\\Foundation\\\\Application->make('Jiminny\\\\\\\\Reposit...')\n#5 /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php(1237): Illuminate\\\\Container\\\\Container->resolveClass(Object(ReflectionParameter))\n#6 /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php(1162): Illuminate\\\\Container\\\\Container->resolveDependencies(Array)\n#7 /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php(933): Illuminate\\\\Container\\\\Container->build('Jiminny\\\\\\\\Http\\\\\\\\Co...')\n#8 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Application.php(1078): Illuminate\\\\Container\\\\Container->resolve('Jiminny\\\\\\\\Http\\\\\\\\Co...', Array, true)\n#9 /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php(864): Illuminate\\\\Foundation\\\\Application->resolve('Jiminny\\\\\\\\Http\\\\\\\\Co...', Array)\n#10 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Application.php(1058): Illuminate\\\\Container\\\\Container->make('Jiminny\\\\\\\\Http\\\\\\\\Co...', Array)\n#11 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Route.php(286): Illuminate\\\\Foundation\\\\Application->make('Jiminny\\\\\\\\Http\\\\\\\\Co...')\n#12 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Route.php(1133): Illuminate\\\\Routing\\\\Route->getController()\n#13 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Route.php(1062): Illuminate\\\\Routing\\\\Route->controllerMiddleware()\n#14 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Router.php(834): Illuminate\\\\Routing\\\\Route->gatherMiddleware()\n#15 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Router.php(816): Illuminate\\\\Routing\\\\Router->gatherRouteMiddleware(Object(Illuminate\\\\Routing\\\\Route))\n#16 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Router.php(800): Illuminate\\\\Routing\\\\Router->runRouteWithinStack(Object(Illuminate\\\\Routing\\\\Route), Object(Illuminate\\\\Http\\\\Request))\n#17 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Router.php(764): Illuminate\\\\Routing\\\\Router->runRoute(Object(Illuminate\\\\Http\\\\Request), Object(Illuminate\\\\Routing\\\\Route))\n#18 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Router.php(753): Illuminate\\\\Routing\\\\Router->dispatchToRoute(Object(Illuminate\\\\Http\\\\Request))\n#19 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Http/Kernel.php(200): Illuminate\\\\Routing\\\\Router->dispatch(Object(Illuminate\\\\Http\\\\Request))\n#20 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(180): Illuminate\\\\Foundation\\\\Http\\\\Kernel->Illuminate\\\\Foundation\\\\Http\\\\{closure}(Object(Illuminate\\\\Http\\\\Request))\n#21 /home/jiminny/vendor/barryvdh/laravel-debugbar/src/Middleware/InjectDebugbar.php(59): Illuminate\\\\Pipeline\\\\Pipeline->Illuminate\\\\Pipeline\\\\{closure}(Object(Illuminate\\\\Http\\\\Request))\n#22 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(219): Barryvdh\\\\Debugbar\\\\Middleware\\\\InjectDebugbar->handle(Object(Illuminate\\\\Http\\\\Request), Object(Closure))\n#23 /home/jiminny/vendor/laravel/framework/src/Illuminate/Http/Middleware/HandleCors.php(74): Illuminate\\\\Pipeline\\\\Pipeline->Illuminate\\\\Pipeline\\\\{closure}(Object(Illuminate\\\\Http\\\\Request))\n#24 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(219): Illuminate\\\\Http\\\\Middleware\\\\HandleCors->handle(Object(Illuminate\\\\Http\\\\Request), Object(Closure))\n#25 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Http/Middleware/TransformsRequest.php(21): Illuminate\\\\Pipeline\\\\Pipeline->Illuminate\\\\Pipeline\\\\{closure}(Object(Illuminate\\\\Http\\\\Request))\n#26 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Http/Middleware/TrimStrings.php(51): Illuminate\\\\Foundation\\\\Http\\\\Middleware\\\\TransformsRequest->handle(Object(Illuminate\\\\Http\\\\Request), Object(Closure))\n#27 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(219): Illuminate\\\\Foundation\\\\Http\\\\Middleware\\\\TrimStrings->handle(Object(Illuminate\\\\Http\\\\Request), Object(Closure))\n#28 /home/jiminny/vendor/bepsvpt/secure-headers/src/SecureHeadersMiddleware.php(18): Illuminate\\\\Pipeline\\\\Pipeline->Illuminate\\\\Pipeline\\\\{closure}(Object(Illuminate\\\\Http\\\\Request))\n#29 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(219): Bepsvpt\\\\SecureHeaders\\\\SecureHeadersMiddleware->handle(Object(Illuminate\\\\Http\\\\Request), Object(Closure))\n#30 /home/jiminny/app/Http/Middleware/SentryContext.php(60): Illuminate\\\\Pipeline\\\\Pipeline->Illuminate\\\\Pipeline\\\\{closure}(Object(Illuminate\\\\Http\\\\Request))\n#31 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(219): Jiminny\\\\Http\\\\Middleware\\\\SentryContext->handle(Object(Illuminate\\\\Http\\\\Request), Object(Closure))\n#32 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Http/Middleware/PreventRequestsDuringMaintenance.php(109): Illuminate\\\\Pipeline\\\\Pipeline->Illuminate\\\\Pipeline\\\\{closure}(Object(Illuminate\\\\Http\\\\Request))\n#33 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(219): Illuminate\\\\Foundation\\\\Http\\\\Middleware\\\\PreventRequestsDuringMaintenance->handle(Object(Illuminate\\\\Http\\\\Request), Object(Closure))\n#34 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Http/Middleware/InvokeDeferredCallbacks.php(22): Illuminate\\\\Pipeline\\\\Pipeline->Illuminate\\\\Pipeline\\\\{closure}(Object(Illuminate\\\\Http\\\\Request))\n#35 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(219): Illuminate\\\\Foundation\\\\Http\\\\Middleware\\\\InvokeDeferredCallbacks->handle(Object(Illuminate\\\\Http\\\\Request), Object(Closure))\n#36 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(137): Illuminate\\\\Pipeline\\\\Pipeline->Illuminate\\\\Pipeline\\\\{closure}(Object(Illuminate\\\\Http\\\\Request))\n#37 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Http/Kernel.php(175): Illuminate\\\\Pipeline\\\\Pipeline->then(Object(Closure))\n#38 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Http/Kernel.php(144): Illuminate\\\\Foundation\\\\Http\\\\Kernel->sendRequestThroughRouter(Object(Illuminate\\\\Http\\\\Request))\n#39 /home/jiminny/public/index.php(51): Illuminate\\\\Foundation\\\\Http\\\\Kernel->handle(Object(Illuminate\\\\Http\\\\Request))\n#40 {main}\n\n[previous exception] [object] (ReflectionException(code: -1): Class \\\"Jiminny\\\\Repositories\\\\AjReportsRepository\\\" does not exist at /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php:1122)\n[stacktrace]\n#0 /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php(1122): ReflectionClass->__construct('Jiminny\\\\\\\\Reposit...')\n#1 /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php(933): Illuminate\\\\Container\\\\Container->build('Jiminny\\\\\\\\Reposit...')\n#2 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Application.php(1078): Illuminate\\\\Container\\\\Container->resolve('Jiminny\\\\\\\\Reposit...', Array, true)\n#3 /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php(864): Illuminate\\\\Foundation\\\\Application->resolve('Jiminny\\\\\\\\Reposit...', Array)\n#4 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Application.php(1058): Illuminate\\\\Container\\\\Container->make('Jiminny\\\\\\\\Reposit...', Array)\n#5 /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php(1336): Illuminate\\\\Foundation\\\\Application->make('Jiminny\\\\\\\\Reposit...')\n#6 /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php(1237): Illuminate\\\\Container\\\\Container->resolveClass(Object(ReflectionParameter))\n#7 /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php(1162): Illuminate\\\\Container\\\\Container->resolveDependencies(Array)\n#8 /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php(933): Illuminate\\\\Container\\\\Container->build('Jiminny\\\\\\\\Http\\\\\\\\Co...')\n#9 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Application.php(1078): Illuminate\\\\Container\\\\Container->resolve('Jiminny\\\\\\\\Http\\\\\\\\Co...', Array, true)\n#10 /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php(864): Illuminate\\\\Foundation\\\\Application->resolve('Jiminny\\\\\\\\Http\\\\\\\\Co...', Array)\n#11 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Application.php(1058): Illuminate\\\\Container\\\\Container->make('Jiminny\\\\\\\\Http\\\\\\\\Co...', Array)\n#12 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Route.php(286): Illuminate\\\\Foundation\\\\Application->make('Jiminny\\\\\\\\Http\\\\\\\\Co...')\n#13 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Route.php(1133): Illuminate\\\\Routing\\\\Route->getController()\n#14 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Route.php(1062): Illuminate\\\\Routing\\\\Route->controllerMiddleware()\n#15 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Router.php(834): Illuminate\\\\Routing\\\\Route->gatherMiddleware()\n#16 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Router.php(816): Illuminate\\\\Routing\\\\Router->gatherRouteMiddleware(Object(Illuminate\\\\Routing\\\\Route))\n#17 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Router.php(800): Illuminate\\\\Routing\\\\Router->runRouteWithinStack(Object(Illuminate\\\\Routing\\\\Route), Object(Illuminate\\\\Http\\\\Request))\n#18 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Router.php(764): Illuminate\\\\Routing\\\\Router->runRoute(Object(Illuminate\\\\Http\\\\Request), Object(Illuminate\\\\Routing\\\\Route))\n#19 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Router.php(753): Illuminate\\\\Routing\\\\Router->dispatchToRoute(Object(Illuminate\\\\Http\\\\Request))\n#20 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Http/Kernel.php(200): Illuminate\\\\Routing\\\\Router->dispatch(Object(Illuminate\\\\Http\\\\Request))\n#21 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(180): Illuminate\\\\Foundation\\\\Http\\\\Kernel->Illuminate\\\\Foundation\\\\Http\\\\{closure}(Object(Illuminate\\\\Http\\\\Request))\n#22 /home/jiminny/vendor/barryvdh/laravel-debugbar/src/Middleware/InjectDebugbar.php(59): Illuminate\\\\Pipeline\\\\Pipeline->Illuminate\\\\Pipeline\\\\{closure}(Object(Illuminate\\\\Http\\\\Request))\n#23 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(219): Barryvdh\\\\Debugbar\\\\Middleware\\\\InjectDebugbar->handle(Object(Illuminate\\\\Http\\\\Request), Object(Closure))\n#24 /home/jiminny/vendor/laravel/framework/src/Illuminate/Http/Middleware/HandleCors.php(74): Illuminate\\\\Pipeline\\\\Pipeline->Illuminate\\\\Pipeline\\\\{closure}(Object(Illuminate\\\\Http\\\\Request))\n#25 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(219): Illuminate\\\\Http\\\\Middleware\\\\HandleCors->handle(Object(Illuminate\\\\Http\\\\Request), Object(Closure))\n#26 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Http/Middleware/TransformsRequest.php(21): Illuminate\\\\Pipeline\\\\Pipeline->Illuminate\\\\Pipeline\\\\{closure}(Object(Illuminate\\\\Http\\\\Request))\n#27 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Http/Middleware/TrimStrings.php(51): Illuminate\\\\Foundation\\\\Http\\\\Middleware\\\\TransformsRequest->handle(Object(Illuminate\\\\Http\\\\Request), Object(Closure))\n#28 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(219): Illuminate\\\\Foundation\\\\Http\\\\Middleware\\\\TrimStrings->handle(Object(Illuminate\\\\Http\\\\Request), Object(Closure))\n#29 /home/jiminny/vendor/bepsvpt/secure-headers/src/SecureHeadersMiddleware.php(18): Illuminate\\\\Pipeline\\\\Pipeline->Illuminate\\\\Pipeline\\\\{closure}(Object(Illuminate\\\\Http\\\\Request))\n#30 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(219): Bepsvpt\\\\SecureHeaders\\\\SecureHeadersMiddleware->handle(Object(Illuminate\\\\Http\\\\Request), Object(Closure))\n#31 /home/jiminny/app/Http/Middleware/SentryContext.php(60): Illuminate\\\\Pipeline\\\\Pipeline->Illuminate\\\\Pipeline\\\\{closure}(Object(Illuminate\\\\Http\\\\Request))\n#32 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(219): Jiminny\\\\Http\\\\Middleware\\\\SentryContext->handle(Object(Illuminate\\\\Http\\\\Request), Object(Closure))\n#33 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Http/Middleware/PreventRequestsDuringMaintenance.php(109): Illuminate\\\\Pipeline\\\\Pipeline->Illuminate\\\\Pipeline\\\\{closure}(Object(Illuminate\\\\Http\\\\Request))\n#34 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(219): Illuminate\\\\Foundation\\\\Http\\\\Middleware\\\\PreventRequestsDuringMaintenance->handle(Object(Illuminate\\\\Http\\\\Request), Object(Closure))\n#35 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Http/Middleware/InvokeDeferredCallbacks.php(22): Illuminate\\\\Pipeline\\\\Pipeline->Illuminate\\\\Pipeline\\\\{closure}(Object(Illuminate\\\\Http\\\\Request))\n#36 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(219): Illuminate\\\\Foundation\\\\Http\\\\Middleware\\\\InvokeDeferredCallbacks->handle(Object(Illuminate\\\\Http\\\\Request), Object(Closure))\n#37 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(137): Illuminate\\\\Pipeline\\\\Pipeline->Illuminate\\\\Pipeline\\\\{closure}(Object(Illuminate\\\\Http\\\\Request))\n#38 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Http/Kernel.php(175): Illuminate\\\\Pipeline\\\\Pipeline->then(Object(Closure))\n#39 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Http/Kernel.php(144): Illuminate\\\\Foundation\\\\Http\\\\Kernel->sendRequestThroughRouter(Object(Illuminate\\\\Http\\\\Request))\n#40 /home/jiminny/public/index.php(51): Illuminate\\\\Foundation\\\\Http\\\\Kernel->handle(Object(Illuminate\\\\Http\\\\Request))\n#41 {main}\n\"} {\"correlation_id\":\"de768781-5c05-418c-baf3-79f77a8c8694\",\"trace_id\":\"fec74062-3f6d-4cfc-968a-1bb285496db3\"}\n[2026-04-27 10:28:12] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage before starting command {\"command\":\"mailbox:skip-lists:refresh\",\"memoryBeforeCommandInMb\":60.0,\"memoryPeakBeforeCommandInMb\":99.727} {\"correlation_id\":\"907d15ed-8c84-41a1-a473-590eebb55dbc\",\"trace_id\":\"ec7bd353-516e-491d-89dd-de44136c1672\"}\n[2026-04-27 10:28:12] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage for command {\"command\":\"mailbox:skip-lists:refresh\",\"memoryBeforeCommandInMb\":60.0,\"memoryAfterCommandInMB\":62.0,\"memoryPeakBeforeCommandInMb\":99.727,\"memoryPeakAfterCommandInMB\":99.727} {\"correlation_id\":\"907d15ed-8c84-41a1-a473-590eebb55dbc\",\"trace_id\":\"ec7bd353-516e-491d-89dd-de44136c1672\"}\n[2026-04-27 10:28:13] local.ERROR: Target class [Jiminny\\Repositories\\AjReportsRepository] does not exist. {\"exception\":\"[object] (Illuminate\\\\Contracts\\\\Container\\\\BindingResolutionException(code: 0): Target class [Jiminny\\\\Repositories\\\\AjReportsRepository] does not exist. at /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php:1124)\n[stacktrace]\n#0 /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php(933): Illuminate\\\\Container\\\\Container->build('Jiminny\\\\\\\\Reposit...')\n#1 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Application.php(1078): Illuminate\\\\Container\\\\Container->resolve('Jiminny\\\\\\\\Reposit...', Array, true)\n#2 /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php(864): Illuminate\\\\Foundation\\\\Application->resolve('Jiminny\\\\\\\\Reposit...', Array)\n#3 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Application.php(1058): Illuminate\\\\Container\\\\Container->make('Jiminny\\\\\\\\Reposit...', Array)\n#4 /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php(1336): Illuminate\\\\Foundation\\\\Application->make('Jiminny\\\\\\\\Reposit...')\n#5 /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php(1237): Illuminate\\\\Container\\\\Container->resolveClass(Object(ReflectionParameter))\n#6 /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php(1162): Illuminate\\\\Container\\\\Container->resolveDependencies(Array)\n#7 /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php(933): Illuminate\\\\Container\\\\Container->build('Jiminny\\\\\\\\Http\\\\\\\\Co...')\n#8 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Application.php(1078): Illuminate\\\\Container\\\\Container->resolve('Jiminny\\\\\\\\Http\\\\\\\\Co...', Array, true)\n#9 /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php(864): Illuminate\\\\Foundation\\\\Application->resolve('Jiminny\\\\\\\\Http\\\\\\\\Co...', Array)\n#10 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Application.php(1058): Illuminate\\\\Container\\\\Container->make('Jiminny\\\\\\\\Http\\\\\\\\Co...', Array)\n#11 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Route.php(286): Illuminate\\\\Foundation\\\\Application->make('Jiminny\\\\\\\\Http\\\\\\\\Co...')\n#12 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Route.php(1133): Illuminate\\\\Routing\\\\Route->getController()\n#13 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Route.php(1062): Illuminate\\\\Routing\\\\Route->controllerMiddleware()\n#14 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Router.php(834): Illuminate\\\\Routing\\\\Route->gatherMiddleware()\n#15 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Router.php(816): Illuminate\\\\Routing\\\\Router->gatherRouteMiddleware(Object(Illuminate\\\\Routing\\\\Route))\n#16 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Router.php(800): Illuminate\\\\Routing\\\\Router->runRouteWithinStack(Object(Illuminate\\\\Routing\\\\Route), Object(Illuminate\\\\Http\\\\Request))\n#17 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Router.php(764): Illuminate\\\\Routing\\\\Router->runRoute(Object(Illuminate\\\\Http\\\\Request), Object(Illuminate\\\\Routing\\\\Route))\n#18 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Router.php(753): Illuminate\\\\Routing\\\\Router->dispatchToRoute(Object(Illuminate\\\\Http\\\\Request))\n#19 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Http/Kernel.php(200): Illuminate\\\\Routing\\\\Router->dispatch(Object(Illuminate\\\\Http\\\\Request))\n#20 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(180): Illuminate\\\\Foundation\\\\Http\\\\Kernel->Illuminate\\\\Foundation\\\\Http\\\\{closure}(Object(Illuminate\\\\Http\\\\Request))\n#21 /home/jiminny/vendor/barryvdh/laravel-debugbar/src/Middleware/InjectDebugbar.php(59): Illuminate\\\\Pipeline\\\\Pipeline->Illuminate\\\\Pipeline\\\\{closure}(Object(Illuminate\\\\Http\\\\Request))\n#22 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(219): Barryvdh\\\\Debugbar\\\\Middleware\\\\InjectDebugbar->handle(Object(Illuminate\\\\Http\\\\Request), Object(Closure))\n#23 /home/jiminny/vendor/laravel/framework/src/Illuminate/Http/Middleware/HandleCors.php(74): Illuminate\\\\Pipeline\\\\Pipeline->Illuminate\\\\Pipeline\\\\{closure}(Object(Illuminate\\\\Http\\\\Request))\n#24 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(219): Illuminate\\\\Http\\\\Middleware\\\\HandleCors->handle(Object(Illuminate\\\\Http\\\\Request), Object(Closure))\n#25 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Http/Middleware/TransformsRequest.php(21): Illuminate\\\\Pipeline\\\\Pipeline->Illuminate\\\\Pipeline\\\\{closure}(Object(Illuminate\\\\Http\\\\Request))\n#26 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Http/Middleware/TrimStrings.php(51): Illuminate\\\\Foundation\\\\Http\\\\Middleware\\\\TransformsRequest->handle(Object(Illuminate\\\\Http\\\\Request), Object(Closure))\n#27 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(219): Illuminate\\\\Foundation\\\\Http\\\\Middleware\\\\TrimStrings->handle(Object(Illuminate\\\\Http\\\\Request), Object(Closure))\n#28 /home/jiminny/vendor/bepsvpt/secure-headers/src/SecureHeadersMiddleware.php(18): Illuminate\\\\Pipeline\\\\Pipeline->Illuminate\\\\Pipeline\\\\{closure}(Object(Illuminate\\\\Http\\\\Request))\n#29 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(219): Bepsvpt\\\\SecureHeaders\\\\SecureHeadersMiddleware->handle(Object(Illuminate\\\\Http\\\\Request), Object(Closure))\n#30 /home/jiminny/app/Http/Middleware/SentryContext.php(60): Illuminate\\\\Pipeline\\\\Pipeline->Illuminate\\\\Pipeline\\\\{closure}(Object(Illuminate\\\\Http\\\\Request))\n#31 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(219): Jiminny\\\\Http\\\\Middleware\\\\SentryContext->handle(Object(Illuminate\\\\Http\\\\Request), Object(Closure))\n#32 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Http/Middleware/PreventRequestsDuringMaintenance.php(109): Illuminate\\\\Pipeline\\\\Pipeline->Illuminate\\\\Pipeline\\\\{closure}(Object(Illuminate\\\\Http\\\\Request))\n#33 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(219): Illuminate\\\\Foundation\\\\Http\\\\Middleware\\\\PreventRequestsDuringMaintenance->handle(Object(Illuminate\\\\Http\\\\Request), Object(Closure))\n#34 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Http/Middleware/InvokeDeferredCallbacks.php(22): Illuminate\\\\Pipeline\\\\Pipeline->Illuminate\\\\Pipeline\\\\{closure}(Object(Illuminate\\\\Http\\\\Request))\n#35 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(219): Illuminate\\\\Foundation\\\\Http\\\\Middleware\\\\InvokeDeferredCallbacks->handle(Object(Illuminate\\\\Http\\\\Request), Object(Closure))\n#36 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(137): Illuminate\\\\Pipeline\\\\Pipeline->Illuminate\\\\Pipeline\\\\{closure}(Object(Illuminate\\\\Http\\\\Request))\n#37 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Http/Kernel.php(175): Illuminate\\\\Pipeline\\\\Pipeline->then(Object(Closure))\n#38 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Http/Kernel.php(144): Illuminate\\\\Foundation\\\\Http\\\\Kernel->sendRequestThroughRouter(Object(Illuminate\\\\Http\\\\Request))\n#39 /home/jiminny/public/index.php(51): Illuminate\\\\Foundation\\\\Http\\\\Kernel->handle(Object(Illuminate\\\\Http\\\\Request))\n#40 {main}\n\n[previous exception] [object] (ReflectionException(code: -1): Class \\\"Jiminny\\\\Repositories\\\\AjReportsRepository\\\" does not exist at /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php:1122)\n[stacktrace]\n#0 /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php(1122): ReflectionClass->__construct('Jiminny\\\\\\\\Reposit...')\n#1 /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php(933): Illuminate\\\\Container\\\\Container->build('Jiminny\\\\\\\\Reposit...')\n#2 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Application.php(1078): Illuminate\\\\Container\\\\Container->resolve('Jiminny\\\\\\\\Reposit...', Array, true)\n#3 /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php(864): Illuminate\\\\Foundation\\\\Application->resolve('Jiminny\\\\\\\\Reposit...', Array)\n#4 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Application.php(1058): Illuminate\\\\Container\\\\Container->make('Jiminny\\\\\\\\Reposit...', Array)\n#5 /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php(1336): Illuminate\\\\Foundation\\\\Application->make('Jiminny\\\\\\\\Reposit...')\n#6 /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php(1237): Illuminate\\\\Container\\\\Container->resolveClass(Object(ReflectionParameter))\n#7 /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php(1162): Illuminate\\\\Container\\\\Container->resolveDependencies(Array)\n#8 /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php(933): Illuminate\\\\Container\\\\Container->build('Jiminny\\\\\\\\Http\\\\\\\\Co...')\n#9 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Application.php(1078): Illuminate\\\\Container\\\\Container->resolve('Jiminny\\\\\\\\Http\\\\\\\\Co...', Array, true)\n#10 /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php(864): Illuminate\\\\Foundation\\\\Application->resolve('Jiminny\\\\\\\\Http\\\\\\\\Co...', Array)\n#11 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Application.php(1058): Illuminate\\\\Container\\\\Container->make('Jiminny\\\\\\\\Http\\\\\\\\Co...', Array)\n#12 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Route.php(286): Illuminate\\\\Foundation\\\\Application->make('Jiminny\\\\\\\\Http\\\\\\\\Co...')\n#13 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Route.php(1133): Illuminate\\\\Routing\\\\Route->getController()\n#14 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Route.php(1062): Illuminate\\\\Routing\\\\Route->controllerMiddleware()\n#15 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Router.php(834): Illuminate\\\\Routing\\\\Route->gatherMiddleware()\n#16 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Router.php(816): Illuminate\\\\Routing\\\\Router->gatherRouteMiddleware(Object(Illuminate\\\\Routing\\\\Route))\n#17 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Router.php(800): Illuminate\\\\Routing\\\\Router->runRouteWithinStack(Object(Illuminate\\\\Routing\\\\Route), Object(Illuminate\\\\Http\\\\Request))\n#18 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Router.php(764): Illuminate\\\\Routing\\\\Router->runRoute(Object(Illuminate\\\\Http\\\\Request), Object(Illuminate\\\\Routing\\\\Route))\n#19 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Router.php(753): Illuminate\\\\Routing\\\\Router->dispatchToRoute(Object(Illuminate\\\\Http\\\\Request))\n#20 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Http/Kernel.php(200): Illuminate\\\\Routing\\\\Router->dispatch(Object(Illuminate\\\\Http\\\\Request))\n#21 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(180): Illuminate\\\\Foundation\\\\Http\\\\Kernel->Illuminate\\\\Foundation\\\\Http\\\\{closure}(Object(Illuminate\\\\Http\\\\Request))\n#22 /home/jiminny/vendor/barryvdh/laravel-debugbar/src/Middleware/InjectDebugbar.php(59): Illuminate\\\\Pipeline\\\\Pipeline->Illuminate\\\\Pipeline\\\\{closure}(Object(Illuminate\\\\Http\\\\Request))\n#23 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(219): Barryvdh\\\\Debugbar\\\\Middleware\\\\InjectDebugbar->handle(Object(Illuminate\\\\Http\\\\Request), Object(Closure))\n#24 /home/jiminny/vendor/laravel/framework/src/Illuminate/Http/Middleware/HandleCors.php(74): Illuminate\\\\Pipeline\\\\Pipeline->Illuminate\\\\Pipeline\\\\{closure}(Object(Illuminate\\\\Http\\\\Request))\n#25 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(219): Illuminate\\\\Http\\\\Middleware\\\\HandleCors->handle(Object(Illuminate\\\\Http\\\\Request), Object(Closure))\n#26 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Http/Middleware/TransformsRequest.php(21): Illuminate\\\\Pipeline\\\\Pipeline->Illuminate\\\\Pipeline\\\\{closure}(Object(Illuminate\\\\Http\\\\Request))\n#27 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Http/Middleware/TrimStrings.php(51): Illuminate\\\\Foundation\\\\Http\\\\Middleware\\\\TransformsRequest->handle(Object(Illuminate\\\\Http\\\\Request), Object(Closure))\n#28 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(219): Illuminate\\\\Foundation\\\\Http\\\\Middleware\\\\TrimStrings->handle(Object(Illuminate\\\\Http\\\\Request), Object(Closure))\n#29 /home/jiminny/vendor/bepsvpt/secure-headers/src/SecureHeadersMiddleware.php(18): Illuminate\\\\Pipeline\\\\Pipeline->Illuminate\\\\Pipeline\\\\{closure}(Object(Illuminate\\\\Http\\\\Request))\n#30 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(219): Bepsvpt\\\\SecureHeaders\\\\SecureHeadersMiddleware->handle(Object(Illuminate\\\\Http\\\\Request), Object(Closure))\n#31 /home/jiminny/app/Http/Middleware/SentryContext.php(60): Illuminate\\\\Pipeline\\\\Pipeline->Illuminate\\\\Pipeline\\\\{closure}(Object(Illuminate\\\\Http\\\\Request))\n#32 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(219): Jiminny\\\\Http\\\\Middleware\\\\SentryContext->handle(Object(Illuminate\\\\Http\\\\Request), Object(Closure))\n#33 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Http/Middleware/PreventRequestsDuringMaintenance.php(109): Illuminate\\\\Pipeline\\\\Pipeline->Illuminate\\\\Pipeline\\\\{closure}(Object(Illuminate\\\\Http\\\\Request))\n#34 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(219): Illuminate\\\\Foundation\\\\Http\\\\Middleware\\\\PreventRequestsDuringMaintenance->handle(Object(Illuminate\\\\Http\\\\Request), Object(Closure))\n#35 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Http/Middleware/InvokeDeferredCallbacks.php(22): Illuminate\\\\Pipeline\\\\Pipeline->Illuminate\\\\Pipeline\\\\{closure}(Object(Illuminate\\\\Http\\\\Request))\n#36 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(219): Illuminate\\\\Foundation\\\\Http\\\\Middleware\\\\InvokeDeferredCallbacks->handle(Object(Illuminate\\\\Http\\\\Request), Object(Closure))\n#37 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(137): Illuminate\\\\Pipeline\\\\Pipeline->Illuminate\\\\Pipeline\\\\{closure}(Object(Illuminate\\\\Http\\\\Request))\n#38 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Http/Kernel.php(175): Illuminate\\\\Pipeline\\\\Pipeline->then(Object(Closure))\n#39 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Http/Kernel.php(144): Illuminate\\\\Foundation\\\\Http\\\\Kernel->sendRequestThroughRouter(Object(Illuminate\\\\Http\\\\Request))\n#40 /home/jiminny/public/index.php(51): Illuminate\\\\Foundation\\\\Http\\\\Kernel->handle(Object(Illuminate\\\\Http\\\\Request))\n#41 {main}\n\"} {\"correlation_id\":\"d8709bb9-0f8c-4798-a8c7-e68e24ab6265\",\"trace_id\":\"c5bea1cc-53fe-4bda-a8e2-bec002030bc2\"}\n[2026-04-27 10:28:14] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage before starting command {\"command\":\"mailbox:batch:process\",\"memoryBeforeCommandInMb\":60.0,\"memoryPeakBeforeCommandInMb\":99.727} {\"correlation_id\":\"5fb3c8c8-3160-4964-ac56-02d6e2cb998c\",\"trace_id\":\"3a6d5e64-ecf2-4700-98cd-3edd56a1dc34\"}\n[2026-04-27 10:28:14] local.INFO: [EmailSchedule] STARTING batch process {\"host\":\"docker_lamp_1\"} {\"correlation_id\":\"5fb3c8c8-3160-4964-ac56-02d6e2cb998c\",\"trace_id\":\"3a6d5e64-ecf2-4700-98cd-3edd56a1dc34\"}\n[2026-04-27 10:28:14] local.INFO: Processing email batch 98446 for inbox 212 {\"correlation_id\":\"5fb3c8c8-3160-4964-ac56-02d6e2cb998c\",\"trace_id\":\"3a6d5e64-ecf2-4700-98cd-3edd56a1dc34\"}\n[2026-04-27 10:28:14] local.INFO: [SocialAccountService] Fetching token {\"socialAccountId\":1500,\"provider\":\"salesforce\"} {\"correlation_id\":\"5fb3c8c8-3160-4964-ac56-02d6e2cb998c\",\"trace_id\":\"3a6d5e64-ecf2-4700-98cd-3edd56a1dc34\"}\n[2026-04-27 10:28:14] local.INFO: [SocialAccountService] Token retrieved {\"socialAccountId\":1500,\"provider\":\"salesforce\"} {\"correlation_id\":\"5fb3c8c8-3160-4964-ac56-02d6e2cb998c\",\"trace_id\":\"3a6d5e64-ecf2-4700-98cd-3edd56a1dc34\"}\n[2026-04-27 10:28:14] local.INFO: [EncryptedTokenManager] Generating access token. {\"mode\":\"legacy\"} {\"correlation_id\":\"5fb3c8c8-3160-4964-ac56-02d6e2cb998c\",\"trace_id\":\"3a6d5e64-ecf2-4700-98cd-3edd56a1dc34\"}\n[2026-04-27 10:28:14] local.INFO: [CrmOwnerResolver] Integration owner matched as CRM Owner {\"crm_provider\":\"salesforce\",\"crm_owner\":143,\"team_id\":1} {\"correlation_id\":\"5fb3c8c8-3160-4964-ac56-02d6e2cb998c\",\"trace_id\":\"3a6d5e64-ecf2-4700-98cd-3edd56a1dc34\"}\n[2026-04-27 10:28:14] local.INFO: [SocialAccountService] Fetching token {\"socialAccountId\":1354,\"provider\":\"google\"} {\"correlation_id\":\"5fb3c8c8-3160-4964-ac56-02d6e2cb998c\",\"trace_id\":\"3a6d5e64-ecf2-4700-98cd-3edd56a1dc34\"}\n[2026-04-27 10:28:14] local.INFO: [SocialAccountService] Token retrieved {\"socialAccountId\":1354,\"provider\":\"google\"} {\"correlation_id\":\"5fb3c8c8-3160-4964-ac56-02d6e2cb998c\",\"trace_id\":\"3a6d5e64-ecf2-4700-98cd-3edd56a1dc34\"}\n[2026-04-27 10:28:14] local.INFO: [EncryptedTokenManager] Generating access token. {\"mode\":\"legacy\"} {\"correlation_id\":\"5fb3c8c8-3160-4964-ac56-02d6e2cb998c\",\"trace_id\":\"3a6d5e64-ecf2-4700-98cd-3edd56a1dc34\"}\n[2026-04-27 10:28:15] local.INFO: Processing an email from inbox batch {\"batch\":98446,\"inbox_id\":212,\"email\":\"lukas.kovalik@jiminny.com\",\"email_id\":\"19dce788ac964bbf\",\"from\":\"Nikolay Yankov <notifications@github.com>\",\"to\":\"\\\"jiminny/app\\\" <app@noreply.github.com>\",\"cc\":\"Push <push@noreply.github.com>\"} {\"correlation_id\":\"5fb3c8c8-3160-4964-ac56-02d6e2cb998c\",\"trace_id\":\"3a6d5e64-ecf2-4700-98cd-3edd56a1dc34\"}\n[2026-04-27 10:28:15] local.INFO: [SocialAccountService] Fetching token {\"socialAccountId\":1500,\"provider\":\"salesforce\"} {\"correlation_id\":\"5fb3c8c8-3160-4964-ac56-02d6e2cb998c\",\"trace_id\":\"3a6d5e64-ecf2-4700-98cd-3edd56a1dc34\"}\n[2026-04-27 10:28:15] local.INFO: [SocialAccountService] Token retrieved {\"socialAccountId\":1500,\"provider\":\"salesforce\"} {\"correlation_id\":\"5fb3c8c8-3160-4964-ac56-02d6e2cb998c\",\"trace_id\":\"3a6d5e64-ecf2-4700-98cd-3edd56a1dc34\"}\n[2026-04-27 10:28:15] local.INFO: [EncryptedTokenManager] Generating access token. {\"mode\":\"legacy\"} {\"correlation_id\":\"5fb3c8c8-3160-4964-ac56-02d6e2cb998c\",\"trace_id\":\"3a6d5e64-ecf2-4700-98cd-3edd56a1dc34\"}\n[2026-04-27 10:28:15] local.INFO: [CrmOwnerResolver] Integration owner matched as CRM Owner {\"crm_provider\":\"salesforce\",\"crm_owner\":143,\"team_id\":1} {\"correlation_id\":\"5fb3c8c8-3160-4964-ac56-02d6e2cb998c\",\"trace_id\":\"3a6d5e64-ecf2-4700-98cd-3edd56a1dc34\"}\n[2026-04-27 10:28:15] local.INFO: [EmailImport\\ParticipantsResolver] The sender email is blacklisted, skipping {\"email\":\"notifications@github.com\",\"inbox_id\":212,\"message_provider_id\":\"19dce788ac964bbf\",\"team_id\":1} {\"correlation_id\":\"5fb3c8c8-3160-4964-ac56-02d6e2cb998c\",\"trace_id\":\"3a6d5e64-ecf2-4700-98cd-3edd56a1dc34\"}\n[2026-04-27 10:28:15] local.INFO: [EmailImport\\ParticipantsValidator] Email participants are less than 2 {\"inbox_id\":212,\"message_provider_id\":\"19dce788ac964bbf\",\"message_id\":\"<jiminny/app/pull/11998/before/2b8a97d6be8b053441d5a845b72dd04803cbb53c/after/8a68a94d39cc76ae546e19f354e8b0dabd577314@github.com>\"} {\"correlation_id\":\"5fb3c8c8-3160-4964-ac56-02d6e2cb998c\",\"trace_id\":\"3a6d5e64-ecf2-4700-98cd-3edd56a1dc34\"}\n[2026-04-27 10:28:15] local.INFO: Processing an email from inbox batch {\"batch\":98446,\"inbox_id\":212,\"email\":\"lukas.kovalik@jiminny.com\",\"email_id\":\"19dce7883f2fea19\",\"from\":\"The Jiminny Team <no-reply@dev.jiminny.com>\",\"to\":\"lukas.kovalik@jiminny.com\",\"cc\":null} {\"correlation_id\":\"5fb3c8c8-3160-4964-ac56-02d6e2cb998c\",\"trace_id\":\"3a6d5e64-ecf2-4700-98cd-3edd56a1dc34\"}\n[2026-04-27 10:28:15] local.INFO: [SocialAccountService] Fetching token {\"socialAccountId\":1500,\"provider\":\"salesforce\"} {\"correlation_id\":\"5fb3c8c8-3160-4964-ac56-02d6e2cb998c\",\"trace_id\":\"3a6d5e64-ecf2-4700-98cd-3edd56a1dc34\"}\n[2026-04-27 10:28:15] local.INFO: [SocialAccountService] Token retrieved {\"socialAccountId\":1500,\"provider\":\"salesforce\"} {\"correlation_id\":\"5fb3c8c8-3160-4964-ac56-02d6e2cb998c\",\"trace_id\":\"3a6d5e64-ecf2-4700-98cd-3edd56a1dc34\"}\n[2026-04-27 10:28:15] local.INFO: [EncryptedTokenManager] Generating access token. {\"mode\":\"legacy\"} {\"correlation_id\":\"5fb3c8c8-3160-4964-ac56-02d6e2cb998c\",\"trace_id\":\"3a6d5e64-ecf2-4700-98cd-3edd56a1dc34\"}\n[2026-04-27 10:28:15] local.INFO: [CrmOwnerResolver] Integration owner matched as CRM Owner {\"crm_provider\":\"salesforce\",\"crm_owner\":143,\"team_id\":1} {\"correlation_id\":\"5fb3c8c8-3160-4964-ac56-02d6e2cb998c\",\"trace_id\":\"3a6d5e64-ecf2-4700-98cd-3edd56a1dc34\"}\n[2026-04-27 10:28:15] local.INFO: [EmailImport\\ParticipantsResolver] The sender email is blacklisted, skipping {\"email\":\"no-reply@dev.jiminny.com\",\"inbox_id\":212,\"message_provider_id\":\"19dce7883f2fea19\",\"team_id\":1} {\"correlation_id\":\"5fb3c8c8-3160-4964-ac56-02d6e2cb998c\",\"trace_id\":\"3a6d5e64-ecf2-4700-98cd-3edd56a1dc34\"}\n[2026-04-27 10:28:15] local.INFO: [EmailImport\\ParticipantsValidator] Email participants are less than 2 {\"inbox_id\":212,\"message_provider_id\":\"19dce7883f2fea19\",\"message_id\":\"<d70689e8-7b14-42ac-a3bf-5f1e81da943e@mtasv.net>\"} {\"correlation_id\":\"5fb3c8c8-3160-4964-ac56-02d6e2cb998c\",\"trace_id\":\"3a6d5e64-ecf2-4700-98cd-3edd56a1dc34\"}\n[2026-04-27 10:28:15] local.INFO: Processing an email from inbox batch {\"batch\":98446,\"inbox_id\":212,\"email\":\"lukas.kovalik@jiminny.com\",\"email_id\":\"19dce761cb049fba\",\"from\":\"The Jiminny Team <no-reply@dev.jiminny.com>\",\"to\":\"lukas.kovalik@jiminny.com\",\"cc\":null} {\"correlation_id\":\"5fb3c8c8-3160-4964-ac56-02d6e2cb998c\",\"trace_id\":\"3a6d5e64-ecf2-4700-98cd-3edd56a1dc34\"}\n[2026-04-27 10:28:15] local.INFO: [SocialAccountService] Fetching token {\"socialAccountId\":1500,\"provider\":\"salesforce\"} {\"correlation_id\":\"5fb3c8c8-3160-4964-ac56-02d6e2cb998c\",\"trace_id\":\"3a6d5e64-ecf2-4700-98cd-3edd56a1dc34\"}\n[2026-04-27 10:28:15] local.INFO: [SocialAccountService] Token retrieved {\"socialAccountId\":1500,\"provider\":\"salesforce\"} {\"correlation_id\":\"5fb3c8c8-3160-4964-ac56-02d6e2cb998c\",\"trace_id\":\"3a6d5e64-ecf2-4700-98cd-3edd56a1dc34\"}\n[2026-04-27 10:28:15] local.INFO: [EncryptedTokenManager] Generating access token. {\"mode\":\"legacy\"} {\"correlation_id\":\"5fb3c8c8-3160-4964-ac56-02d6e2cb998c\",\"trace_id\":\"3a6d5e64-ecf2-4700-98cd-3edd56a1dc34\"}\n[2026-04-27 10:28:15] local.INFO: [CrmOwnerResolver] Integration owner matched as CRM Owner {\"crm_provider\":\"salesforce\",\"crm_owner\":143,\"team_id\":1} {\"correlation_id\":\"5fb3c8c8-3160-4964-ac56-02d6e2cb998c\",\"trace_id\":\"3a6d5e64-ecf2-4700-98cd-3edd56a1dc34\"}\n[2026-04-27 10:28:15] local.INFO: [EmailImport\\ParticipantsResolver] The sender email is blacklisted, skipping {\"email\":\"no-reply@dev.jiminny.com\",\"inbox_id\":212,\"message_provider_id\":\"19dce761cb049fba\",\"team_id\":1} {\"correlation_id\":\"5fb3c8c8-3160-4964-ac56-02d6e2cb998c\",\"trace_id\":\"3a6d5e64-ecf2-4700-98cd-3edd56a1dc34\"}\n[2026-04-27 10:28:15] local.INFO: [EmailImport\\ParticipantsValidator] Email participants are less than 2 {\"inbox_id\":212,\"message_provider_id\":\"19dce761cb049fba\",\"message_id\":\"<017f0e66-9989-446e-9788-7ae83a99137e@mtasv.net>\"} {\"correlation_id\":\"5fb3c8c8-3160-4964-ac56-02d6e2cb998c\",\"trace_id\":\"3a6d5e64-ecf2-4700-98cd-3edd56a1dc34\"}\n[2026-04-27 10:28:15] local.INFO: Processing an email from inbox batch {\"batch\":98446,\"inbox_id\":212,\"email\":\"lukas.kovalik@jiminny.com\",\"email_id\":\"19dce74ed398d2c1\",\"from\":\"\\\"sonarqubecloud[bot]\\\" <notifications@github.com>\",\"to\":\"\\\"jiminny/app\\\" <app@noreply.github.com>\",\"cc\":\"Lukas Kovalik <kovaliklukas@gmail.com>, Author <author@noreply.github.com>\"} {\"correlation_id\":\"5fb3c8c8-3160-4964-ac56-02d6e2cb998c\",\"trace_id\":\"3a6d5e64-ecf2-4700-98cd-3edd56a1dc34\"}\n[2026-04-27 10:28:15] local.INFO: [SocialAccountService] Fetching token {\"socialAccountId\":1500,\"provider\":\"salesforce\"} {\"correlation_id\":\"5fb3c8c8-3160-4964-ac56-02d6e2cb998c\",\"trace_id\":\"3a6d5e64-ecf2-4700-98cd-3edd56a1dc34\"}\n[2026-04-27 10:28:15] local.INFO: [SocialAccountService] Token retrieved {\"socialAccountId\":1500,\"provider\":\"salesforce\"} {\"correlation_id\":\"5fb3c8c8-3160-4964-ac56-02d6e2cb998c\",\"trace_id\":\"3a6d5e64-ecf2-4700-98cd-3edd56a1dc34\"}\n[2026-04-27 10:28:15] local.INFO: [EncryptedTokenManager] Generating access token. {\"mode\":\"legacy\"} {\"correlation_id\":\"5fb3c8c8-3160-4964-ac56-02d6e2cb998c\",\"trace_id\":\"3a6d5e64-ecf2-4700-98cd-3edd56a1dc34\"}\n[2026-04-27 10:28:15] local.INFO: [CrmOwnerResolver] Integration owner matched as CRM Owner {\"crm_provider\":\"salesforce\",\"crm_owner\":143,\"team_id\":1} {\"correlation_id\":\"5fb3c8c8-3160-4964-ac56-02d6e2cb998c\",\"trace_id\":\"3a6d5e64-ecf2-4700-98cd-3edd56a1dc34\"}\n[2026-04-27 10:28:15] local.INFO: [EmailImport\\ParticipantsResolver] The sender email is blacklisted, skipping {\"email\":\"notifications@github.com\",\"inbox_id\":212,\"message_provider_id\":\"19dce74ed398d2c1\",\"team_id\":1} {\"correlation_id\":\"5fb3c8c8-3160-4964-ac56-02d6e2cb998c\",\"trace_id\":\"3a6d5e64-ecf2-4700-98cd-3edd56a1dc34\"}\n[2026-04-27 10:28:15] local.INFO: [EmailImport\\ParticipantsValidator] Email participants are less than 2 {\"inbox_id\":212,\"message_provider_id\":\"19dce74ed398d2c1\",\"message_id\":\"<jiminny/app/pull/12016/c4326142661@github.com>\"} {\"correlation_id\":\"5fb3c8c8-3160-4964-ac56-02d6e2cb998c\",\"trace_id\":\"3a6d5e64-ecf2-4700-98cd-3edd56a1dc34\"}\n[2026-04-27 10:28:15] local.INFO: Processing an email from inbox batch {\"batch\":98446,\"inbox_id\":212,\"email\":\"lukas.kovalik@jiminny.com\",\"email_id\":\"19dce713dbecf41f\",\"from\":\"\\\"sonarqubecloud[bot]\\\" <notifications@github.com>\",\"to\":\"\\\"jiminny/infrastructure\\\" <infrastructure@noreply.github.com>\",\"cc\":\"Subscribed <subscribed@noreply.github.com>\"} {\"correlation_id\":\"5fb3c8c8-3160-4964-ac56-02d6e2cb998c\",\"trace_id\":\"3a6d5e64-ecf2-4700-98cd-3edd56a1dc34\"}\n[2026-04-27 10:28:15] local.INFO: [SocialAccountService] Fetching token {\"socialAccountId\":1500,\"provider\":\"salesforce\"} {\"correlation_id\":\"5fb3c8c8-3160-4964-ac56-02d6e2cb998c\",\"trace_id\":\"3a6d5e64-ecf2-4700-98cd-3edd56a1dc34\"}\n[2026-04-27 10:28:15] local.INFO: [SocialAccountService] Token retrieved {\"socialAccountId\":1500,\"provider\":\"salesforce\"} {\"correlation_id\":\"5fb3c8c8-3160-4964-ac56-02d6e2cb998c\",\"trace_id\":\"3a6d5e64-ecf2-4700-98cd-3edd56a1dc34\"}\n[2026-04-27 10:28:15] local.INFO: [EncryptedTokenManager] Generating access token. {\"mode\":\"legacy\"} {\"correlation_id\":\"5fb3c8c8-3160-4964-ac56-02d6e2cb998c\",\"trace_id\":\"3a6d5e64-ecf2-4700-98cd-3edd56a1dc34\"}\n[2026-04-27 10:28:15] local.INFO: [CrmOwnerResolver] Integration owner matched as CRM Owner {\"crm_provider\":\"salesforce\",\"crm_owner\":143,\"team_id\":1} {\"correlation_id\":\"5fb3c8c8-3160-4964-ac56-02d6e2cb998c\",\"trace_id\":\"3a6d5e64-ecf2-4700-98cd-3edd56a1dc34\"}\n[2026-04-27 10:28:15] local.INFO: [EmailImport\\ParticipantsResolver] The sender email is blacklisted, skipping {\"email\":\"notifications@github.com\",\"inbox_id\":212,\"message_provider_id\":\"19dce713dbecf41f\",\"team_id\":1} {\"correlation_id\":\"5fb3c8c8-3160-4964-ac56-02d6e2cb998c\",\"trace_id\":\"3a6d5e64-ecf2-4700-98cd-3edd56a1dc34\"}\n[2026-04-27 10:28:15] local.INFO: [EmailImport\\ParticipantsValidator] Email participants are less than 2 {\"inbox_id\":212,\"message_provider_id\":\"19dce713dbecf41f\",\"message_id\":\"<jiminny/infrastructure/pull/725/c4326108250@github.com>\"} {\"correlation_id\":\"5fb3c8c8-3160-4964-ac56-02d6e2cb998c\",\"trace_id\":\"3a6d5e64-ecf2-4700-98cd-3edd56a1dc34\"}\n[2026-04-27 10:28:15] local.INFO: Processing an email from inbox batch {\"batch\":98446,\"inbox_id\":212,\"email\":\"lukas.kovalik@jiminny.com\",\"email_id\":\"19dce708ed8b5acb\",\"from\":\"Veselin Kulov <notifications@github.com>\",\"to\":\"\\\"jiminny/infrastructure\\\" <infrastructure@noreply.github.com>\",\"cc\":\"Push <push@noreply.github.com>\"} {\"correlation_id\":\"5fb3c8c8-3160-4964-ac56-02d6e2cb998c\",\"trace_id\":\"3a6d5e64-ecf2-4700-98cd-3edd56a1dc34\"}\n[2026-04-27 10:28:15] local.INFO: [SocialAccountService] Fetching token {\"socialAccountId\":1500,\"provider\":\"salesforce\"} {\"correlation_id\":\"5fb3c8c8-3160-4964-ac56-02d6e2cb998c\",\"trace_id\":\"3a6d5e64-ecf2-4700-98cd-3edd56a1dc34\"}\n[2026-04-27 10:28:15] local.INFO: [SocialAccountService] Token retrieved {\"socialAccountId\":1500,\"provider\":\"salesforce\"} {\"correlation_id\":\"5fb3c8c8-3160-4964-ac56-02d6e2cb998c\",\"trace_id\":\"3a6d5e64-ecf2-4700-98cd-3edd56a1dc34\"}\n[2026-04-27 10:28:15] local.INFO: [EncryptedTokenManager] Generating access token. {\"mode\":\"legacy\"} {\"correlation_id\":\"5fb3c8c8-3160-4964-ac56-02d6e2cb998c\",\"trace_id\":\"3a6d5e64-ecf2-4700-98cd-3edd56a1dc34\"}\n[2026-04-27 10:28:15] local.INFO: [CrmOwnerResolver] Integration owner matched as CRM Owner {\"crm_provider\":\"salesforce\",\"crm_owner\":143,\"team_id\":1} {\"correlation_id\":\"5fb3c8c8-3160-4964-ac56-02d6e2cb998c\",\"trace_id\":\"3a6d5e64-ecf2-4700-98cd-3edd56a1dc34\"}\n[2026-04-27 10:28:15] local.INFO: [EmailImport\\ParticipantsResolver] The sender email is blacklisted, skipping {\"email\":\"notifications@github.com\",\"inbox_id\":212,\"message_provider_id\":\"19dce708ed8b5acb\",\"team_id\":1} {\"correlation_id\":\"5fb3c8c8-3160-4964-ac56-02d6e2cb998c\",\"trace_id\":\"3a6d5e64-ecf2-4700-98cd-3edd56a1dc34\"}\n[2026-04-27 10:28:15] local.INFO: [EmailImport\\ParticipantsValidator] Email participants are less than 2 {\"inbox_id\":212,\"message_provider_id\":\"19dce708ed8b5acb\",\"message_id\":\"<jiminny/infrastructure/pull/725/before/186952cc584428fcca17aa364f6cc535d65e804a/after/078246a06b25fb93ad61c7f14472ecb755483f4d@github.com>\"} {\"correlation_id\":\"5fb3c8c8-3160-4964-ac56-02d6e2cb998c\",\"trace_id\":\"3a6d5e64-ecf2-4700-98cd-3edd56a1dc34\"}\n[2026-04-27 10:28:15] local.INFO: Processing an email from inbox batch {\"batch\":98446,\"inbox_id\":212,\"email\":\"lukas.kovalik@jiminny.com\",\"email_id\":\"19dce6eea657f2fa\",\"from\":\"\\\"sonarqubecloud[bot]\\\" <notifications@github.com>\",\"to\":\"\\\"jiminny/app\\\" <app@noreply.github.com>\",\"cc\":\"Subscribed <subscribed@noreply.github.com>\"} {\"correlation_id\":\"5fb3c8c8-3160-4964-ac56-02d6e2cb998c\",\"trace_id\":\"3a6d5e64-ecf2-4700-98cd-3edd56a1dc34\"}\n[2026-04-27 10:28:15] local.INFO: [SocialAccountService] Fetching token {\"socialAccountId\":1500,\"provider\":\"salesforce\"} {\"correlation_id\":\"5fb3c8c8-3160-4964-ac56-02d6e2cb998c\",\"trace_id\":\"3a6d5e64-ecf2-4700-98cd-3edd56a1dc34\"}\n[2026-04-27 10:28:15] local.INFO: [SocialAccountService] Token retrieved {\"socialAccountId\":1500,\"provider\":\"salesforce\"} {\"correlation_id\":\"5fb3c8c8-3160-4964-ac56-02d6e2cb998c\",\"trace_id\":\"3a6d5e64-ecf2-4700-98cd-3edd56a1dc34\"}\n[2026-04-27 10:28:15] local.INFO: [EncryptedTokenManager] Generating access token. {\"mode\":\"legacy\"} {\"correlation_id\":\"5fb3c8c8-3160-4964-ac56-02d6e2cb998c\",\"trace_id\":\"3a6d5e64-ecf2-4700-98cd-3edd56a1dc34\"}\n[2026-04-27 10:28:15] local.INFO: [CrmOwnerResolver] Integration owner matched as CRM Owner {\"crm_provider\":\"salesforce\",\"crm_owner\":143,\"team_id\":1} {\"correlation_id\":\"5fb3c8c8-3160-4964-ac56-02d6e2cb998c\",\"trace_id\":\"3a6d5e64-ecf2-4700-98cd-3edd56a1dc34\"}\n[2026-04-27 10:28:15] local.INFO: [EmailImport\\ParticipantsResolver] The sender email is blacklisted, skipping {\"email\":\"notifications@github.com\",\"inbox_id\":212,\"message_provider_id\":\"19dce6eea657f2fa\",\"team_id\":1} {\"correlation_id\":\"5fb3c8c8-3160-4964-ac56-02d6e2cb998c\",\"trace_id\":\"3a6d5e64-ecf2-4700-98cd-3edd56a1dc34\"}\n[2026-04-27 10:28:15] local.INFO: [EmailImport\\ParticipantsValidator] Email participants are less than 2 {\"inbox_id\":212,\"message_provider_id\":\"19dce6eea657f2fa\",\"message_id\":\"<jiminny/app/pull/11998/c4326088534@github.com>\"} {\"correlation_id\":\"5fb3c8c8-3160-4964-ac56-02d6e2cb998c\",\"trace_id\":\"3a6d5e64-ecf2-4700-98cd-3edd56a1dc34\"}\n[2026-04-27 10:28:15] local.INFO: Deleting successfully processed batch 98446 for inbox 212 {\"correlation_id\":\"5fb3c8c8-3160-4964-ac56-02d6e2cb998c\",\"trace_id\":\"3a6d5e64-ecf2-4700-98cd-3edd56a1dc34\"}\n[2026-04-27 10:28:15] local.INFO: [EmailSchedule] FINISHED batch process {\"host\":\"docker_lamp_1\",\"processed\":1} {\"correlation_id\":\"5fb3c8c8-3160-4964-ac56-02d6e2cb998c\",\"trace_id\":\"3a6d5e64-ecf2-4700-98cd-3edd56a1dc34\"}\n[2026-04-27 10:28:15] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage for command {\"command\":\"mailbox:batch:process\",\"memoryBeforeCommandInMb\":60.0,\"memoryAfterCommandInMB\":62.0,\"memoryPeakBeforeCommandInMb\":99.727,\"memoryPeakAfterCommandInMB\":99.727} {\"correlation_id\":\"5fb3c8c8-3160-4964-ac56-02d6e2cb998c\",\"trace_id\":\"3a6d5e64-ecf2-4700-98cd-3edd56a1dc34\"}\n[2026-04-27 10:28:16] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage before starting command {\"command\":\"conference:monitor:count\",\"memoryBeforeCommandInMb\":60.0,\"memoryPeakBeforeCommandInMb\":99.727} {\"correlation_id\":\"ef5b3e1a-a06a-4527-9120-b2adfe4e1846\",\"trace_id\":\"d932d64a-3f63-4aca-8abf-bb13e0892cba\"}\n[2026-04-27 10:28:16] local.INFO: Running conference:monitor:count command for activities in (2026-04-27 10:26:00, 2026-04-27 10:28:00] {\"correlation_id\":\"ef5b3e1a-a06a-4527-9120-b2adfe4e1846\",\"trace_id\":\"d932d64a-3f63-4aca-8abf-bb13e0892cba\"}\n[2026-04-27 10:28:16] local.INFO: [conference:monitor:count] No activities found in (2026-04-27 10:26:00, 2026-04-27 10:28:00] {\"correlation_id\":\"ef5b3e1a-a06a-4527-9120-b2adfe4e1846\",\"trace_id\":\"d932d64a-3f63-4aca-8abf-bb13e0892cba\"}\n[2026-04-27 10:28:16] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage for command {\"command\":\"conference:monitor:count\",\"memoryBeforeCommandInMb\":60.0,\"memoryAfterCommandInMB\":62.0,\"memoryPeakBeforeCommandInMb\":99.727,\"memoryPeakAfterCommandInMB\":99.727} {\"correlation_id\":\"ef5b3e1a-a06a-4527-9120-b2adfe4e1846\",\"trace_id\":\"d932d64a-3f63-4aca-8abf-bb13e0892cba\"}\n[2026-04-27 10:28:17] local.ERROR: Target class [Jiminny\\Repositories\\AjReportsRepository] does not exist. {\"exception\":\"[object] (Illuminate\\\\Contracts\\\\Container\\\\BindingResolutionException(code: 0): Target class [Jiminny\\\\Repositories\\\\AjReportsRepository] does not exist. at /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php:1124)\n[stacktrace]\n#0 /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php(933): Illuminate\\\\Container\\\\Container->build('Jiminny\\\\\\\\Reposit...')\n#1 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Application.php(1078): Illuminate\\\\Container\\\\Container->resolve('Jiminny\\\\\\\\Reposit...', Array, true)\n#2 /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php(864): Illuminate\\\\Foundation\\\\Application->resolve('Jiminny\\\\\\\\Reposit...', Array)\n#3 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Application.php(1058): Illuminate\\\\Container\\\\Container->make('Jiminny\\\\\\\\Reposit...', Array)\n#4 /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php(1336): Illuminate\\\\Foundation\\\\Application->make('Jiminny\\\\\\\\Reposit...')\n#5 /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php(1237): Illuminate\\\\Container\\\\Container->resolveClass(Object(ReflectionParameter))\n#6 /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php(1162): Illuminate\\\\Container\\\\Container->resolveDependencies(Array)\n#7 /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php(933): Illuminate\\\\Container\\\\Container->build('Jiminny\\\\\\\\Http\\\\\\\\Co...')\n#8 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Application.php(1078): Illuminate\\\\Container\\\\Container->resolve('Jiminny\\\\\\\\Http\\\\\\\\Co...', Array, true)\n#9 /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php(864): Illuminate\\\\Foundation\\\\Application->resolve('Jiminny\\\\\\\\Http\\\\\\\\Co...', Array)\n#10 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Application.php(1058): Illuminate\\\\Container\\\\Container->make('Jiminny\\\\\\\\Http\\\\\\\\Co...', Array)\n#11 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Route.php(286): Illuminate\\\\Foundation\\\\Application->make('Jiminny\\\\\\\\Http\\\\\\\\Co...')\n#12 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Route.php(1133): Illuminate\\\\Routing\\\\Route->getController()\n#13 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Route.php(1062): Illuminate\\\\Routing\\\\Route->controllerMiddleware()\n#14 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Router.php(834): Illuminate\\\\Routing\\\\Route->gatherMiddleware()\n#15 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Router.php(816): Illuminate\\\\Routing\\\\Router->gatherRouteMiddleware(Object(Illuminate\\\\Routing\\\\Route))\n#16 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Router.php(800): Illuminate\\\\Routing\\\\Router->runRouteWithinStack(Object(Illuminate\\\\Routing\\\\Route), Object(Illuminate\\\\Http\\\\Request))\n#17 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Router.php(764): Illuminate\\\\Routing\\\\Router->runRoute(Object(Illuminate\\\\Http\\\\Request), Object(Illuminate\\\\Routing\\\\Route))\n#18 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Router.php(753): Illuminate\\\\Routing\\\\Router->dispatchToRoute(Object(Illuminate\\\\Http\\\\Request))\n#19 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Http/Kernel.php(200): Illuminate\\\\Routing\\\\Router->dispatch(Object(Illuminate\\\\Http\\\\Request))\n#20 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(180): Illuminate\\\\Foundation\\\\Http\\\\Kernel->Illuminate\\\\Foundation\\\\Http\\\\{closure}(Object(Illuminate\\\\Http\\\\Request))\n#21 /home/jiminny/vendor/barryvdh/laravel-debugbar/src/Middleware/InjectDebugbar.php(59): Illuminate\\\\Pipeline\\\\Pipeline->Illuminate\\\\Pipeline\\\\{closure}(Object(Illuminate\\\\Http\\\\Request))\n#22 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(219): Barryvdh\\\\Debugbar\\\\Middleware\\\\InjectDebugbar->handle(Object(Illuminate\\\\Http\\\\Request), Object(Closure))\n#23 /home/jiminny/vendor/laravel/framework/src/Illuminate/Http/Middleware/HandleCors.php(74): Illuminate\\\\Pipeline\\\\Pipeline->Illuminate\\\\Pipeline\\\\{closure}(Object(Illuminate\\\\Http\\\\Request))\n#24 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(219): Illuminate\\\\Http\\\\Middleware\\\\HandleCors->handle(Object(Illuminate\\\\Http\\\\Request), Object(Closure))\n#25 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Http/Middleware/TransformsRequest.php(21): Illuminate\\\\Pipeline\\\\Pipeline->Illuminate\\\\Pipeline\\\\{closure}(Object(Illuminate\\\\Http\\\\Request))\n#26 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Http/Middleware/TrimStrings.php(51): Illuminate\\\\Foundation\\\\Http\\\\Middleware\\\\TransformsRequest->handle(Object(Illuminate\\\\Http\\\\Request), Object(Closure))\n#27 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(219): Illuminate\\\\Foundation\\\\Http\\\\Middleware\\\\TrimStrings->handle(Object(Illuminate\\\\Http\\\\Request), Object(Closure))\n#28 /home/jiminny/vendor/bepsvpt/secure-headers/src/SecureHeadersMiddleware.php(18): Illuminate\\\\Pipeline\\\\Pipeline->Illuminate\\\\Pipeline\\\\{closure}(Object(Illuminate\\\\Http\\\\Request))\n#29 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(219): Bepsvpt\\\\SecureHeaders\\\\SecureHeadersMiddleware->handle(Object(Illuminate\\\\Http\\\\Request), Object(Closure))\n#30 /home/jiminny/app/Http/Middleware/SentryContext.php(60): Illuminate\\\\Pipeline\\\\Pipeline->Illuminate\\\\Pipeline\\\\{closure}(Object(Illuminate\\\\Http\\\\Request))\n#31 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(219): Jiminny\\\\Http\\\\Middleware\\\\SentryContext->handle(Object(Illuminate\\\\Http\\\\Request), Object(Closure))\n#32 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Http/Middleware/PreventRequestsDuringMaintenance.php(109): Illuminate\\\\Pipeline\\\\Pipeline->Illuminate\\\\Pipeline\\\\{closure}(Object(Illuminate\\\\Http\\\\Request))\n#33 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(219): Illuminate\\\\Foundation\\\\Http\\\\Middleware\\\\PreventRequestsDuringMaintenance->handle(Object(Illuminate\\\\Http\\\\Request), Object(Closure))\n#34 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Http/Middleware/InvokeDeferredCallbacks.php(22): Illuminate\\\\Pipeline\\\\Pipeline->Illuminate\\\\Pipeline\\\\{closure}(Object(Illuminate\\\\Http\\\\Request))\n#35 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(219): Illuminate\\\\Foundation\\\\Http\\\\Middleware\\\\InvokeDeferredCallbacks->handle(Object(Illuminate\\\\Http\\\\Request), Object(Closure))\n#36 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(137): Illuminate\\\\Pipeline\\\\Pipeline->Illuminate\\\\Pipeline\\\\{closure}(Object(Illuminate\\\\Http\\\\Request))\n#37 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Http/Kernel.php(175): Illuminate\\\\Pipeline\\\\Pipeline->then(Object(Closure))\n#38 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Http/Kernel.php(144): Illuminate\\\\Foundation\\\\Http\\\\Kernel->sendRequestThroughRouter(Object(Illuminate\\\\Http\\\\Request))\n#39 /home/jiminny/public/index.php(51): Illuminate\\\\Foundation\\\\Http\\\\Kernel->handle(Object(Illuminate\\\\Http\\\\Request))\n#40 {main}\n\n[previous exception] [object] (ReflectionException(code: -1): Class \\\"Jiminny\\\\Repositories\\\\AjReportsRepository\\\" does not exist at /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php:1122)\n[stacktrace]\n#0 /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php(1122): ReflectionClass->__construct('Jiminny\\\\\\\\Reposit...')\n#1 /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php(933): Illuminate\\\\Container\\\\Container->build('Jiminny\\\\\\\\Reposit...')\n#2 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Application.php(1078): Illuminate\\\\Container\\\\Container->resolve('Jiminny\\\\\\\\Reposit...', Array, true)\n#3 /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php(864): Illuminate\\\\Foundation\\\\Application->resolve('Jiminny\\\\\\\\Reposit...', Array)\n#4 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Application.php(1058): Illuminate\\\\Container\\\\Container->make('Jiminny\\\\\\\\Reposit...', Array)\n#5 /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php(1336): Illuminate\\\\Foundation\\\\Application->make('Jiminny\\\\\\\\Reposit...')\n#6 /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php(1237): Illuminate\\\\Container\\\\Container->resolveClass(Object(ReflectionParameter))\n#7 /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php(1162): Illuminate\\\\Container\\\\Container->resolveDependencies(Array)\n#8 /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php(933): Illuminate\\\\Container\\\\Container->build('Jiminny\\\\\\\\Http\\\\\\\\Co...')\n#9 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Application.php(1078): Illuminate\\\\Container\\\\Container->resolve('Jiminny\\\\\\\\Http\\\\\\\\Co...', Array, true)\n#10 /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php(864): Illuminate\\\\Foundation\\\\Application->resolve('Jiminny\\\\\\\\Http\\\\\\\\Co...', Array)\n#11 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Application.php(1058): Illuminate\\\\Container\\\\Container->make('Jiminny\\\\\\\\Http\\\\\\\\Co...', Array)\n#12 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Route.php(286): Illuminate\\\\Foundation\\\\Application->make('Jiminny\\\\\\\\Http\\\\\\\\Co...')\n#13 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Route.php(1133): Illuminate\\\\Routing\\\\Route->getController()\n#14 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Route.php(1062): Illuminate\\\\Routing\\\\Route->controllerMiddleware()\n#15 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Router.php(834): Illuminate\\\\Routing\\\\Route->gatherMiddleware()\n#16 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Router.php(816): Illuminate\\\\Routing\\\\Router->gatherRouteMiddleware(Object(Illuminate\\\\Routing\\\\Route))\n#17 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Router.php(800): Illuminate\\\\Routing\\\\Router->runRouteWithinStack(Object(Illuminate\\\\Routing\\\\Route), Object(Illuminate\\\\Http\\\\Request))\n#18 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Router.php(764): Illuminate\\\\Routing\\\\Router->runRoute(Object(Illuminate\\\\Http\\\\Request), Object(Illuminate\\\\Routing\\\\Route))\n#19 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Router.php(753): Illuminate\\\\Routing\\\\Router->dispatchToRoute(Object(Illuminate\\\\Http\\\\Request))\n#20 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Http/Kernel.php(200): Illuminate\\\\Routing\\\\Router->dispatch(Object(Illuminate\\\\Http\\\\Request))\n#21 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(180): Illuminate\\\\Foundation\\\\Http\\\\Kernel->Illuminate\\\\Foundation\\\\Http\\\\{closure}(Object(Illuminate\\\\Http\\\\Request))\n#22 /home/jiminny/vendor/barryvdh/laravel-debugbar/src/Middleware/InjectDebugbar.php(59): Illuminate\\\\Pipeline\\\\Pipeline->Illuminate\\\\Pipeline\\\\{closure}(Object(Illuminate\\\\Http\\\\Request))\n#23 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(219): Barryvdh\\\\Debugbar\\\\Middleware\\\\InjectDebugbar->handle(Object(Illuminate\\\\Http\\\\Request), Object(Closure))\n#24 /home/jiminny/vendor/laravel/framework/src/Illuminate/Http/Middleware/HandleCors.php(74): Illuminate\\\\Pipeline\\\\Pipeline->Illuminate\\\\Pipeline\\\\{closure}(Object(Illuminate\\\\Http\\\\Request))\n#25 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(219): Illuminate\\\\Http\\\\Middleware\\\\HandleCors->handle(Object(Illuminate\\\\Http\\\\Request), Object(Closure))\n#26 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Http/Middleware/TransformsRequest.php(21): Illuminate\\\\Pipeline\\\\Pipeline->Illuminate\\\\Pipeline\\\\{closure}(Object(Illuminate\\\\Http\\\\Request))\n#27 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Http/Middleware/TrimStrings.php(51): Illuminate\\\\Foundation\\\\Http\\\\Middleware\\\\TransformsRequest->handle(Object(Illuminate\\\\Http\\\\Request), Object(Closure))\n#28 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(219): Illuminate\\\\Foundation\\\\Http\\\\Middleware\\\\TrimStrings->handle(Object(Illuminate\\\\Http\\\\Request), Object(Closure))\n#29 /home/jiminny/vendor/bepsvpt/secure-headers/src/SecureHeadersMiddleware.php(18): Illuminate\\\\Pipeline\\\\Pipeline->Illuminate\\\\Pipeline\\\\{closure}(Object(Illuminate\\\\Http\\\\Request))\n#30 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(219): Bepsvpt\\\\SecureHeaders\\\\SecureHeadersMiddleware->handle(Object(Illuminate\\\\Http\\\\Request), Object(Closure))\n#31 /home/jiminny/app/Http/Middleware/SentryContext.php(60): Illuminate\\\\Pipeline\\\\Pipeline->Illuminate\\\\Pipeline\\\\{closure}(Object(Illuminate\\\\Http\\\\Request))\n#32 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(219): Jiminny\\\\Http\\\\Middleware\\\\SentryContext->handle(Object(Illuminate\\\\Http\\\\Request), Object(Closure))\n#33 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Http/Middleware/PreventRequestsDuringMaintenance.php(109): Illuminate\\\\Pipeline\\\\Pipeline->Illuminate\\\\Pipeline\\\\{closure}(Object(Illuminate\\\\Http\\\\Request))\n#34 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(219): Illuminate\\\\Foundation\\\\Http\\\\Middleware\\\\PreventRequestsDuringMaintenance->handle(Object(Illuminate\\\\Http\\\\Request), Object(Closure))\n#35 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Http/Middleware/InvokeDeferredCallbacks.php(22): Illuminate\\\\Pipeline\\\\Pipeline->Illuminate\\\\Pipeline\\\\{closure}(Object(Illuminate\\\\Http\\\\Request))\n#36 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(219): Illuminate\\\\Foundation\\\\Http\\\\Middleware\\\\InvokeDeferredCallbacks->handle(Object(Illuminate\\\\Http\\\\Request), Object(Closure))\n#37 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(137): Illuminate\\\\Pipeline\\\\Pipeline->Illuminate\\\\Pipeline\\\\{closure}(Object(Illuminate\\\\Http\\\\Request))\n#38 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Http/Kernel.php(175): Illuminate\\\\Pipeline\\\\Pipeline->then(Object(Closure))\n#39 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Http/Kernel.php(144): Illuminate\\\\Foundation\\\\Http\\\\Kernel->sendRequestThroughRouter(Object(Illuminate\\\\Http\\\\Request))\n#40 /home/jiminny/public/index.php(51): Illuminate\\\\Foundation\\\\Http\\\\Kernel->handle(Object(Illuminate\\\\Http\\\\Request))\n#41 {main}\n\"} {\"correlation_id\":\"46b082fc-5a28-4cc4-8bd1-c670f8662e7f\",\"trace_id\":\"8ae7dc26-61c7-42e4-8617-0b85f53e54c1\"}\n[2026-04-27 10:28:19] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage before starting command {\"command\":\"calendar:sync\",\"memoryBeforeCommandInMb\":60.0,\"memoryPeakBeforeCommandInMb\":99.727} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:19] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage before starting command {\"command\":\"mailbox:batch:retry-failed\",\"memoryBeforeCommandInMb\":60.0,\"memoryPeakBeforeCommandInMb\":99.727} {\"correlation_id\":\"e155562a-84a1-45f5-8897-1779f96886f6\",\"trace_id\":\"12141267-1032-479b-aab5-e17e133007d9\"}\n[2026-04-27 10:28:19] local.NOTICE: Calendar sync start {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:19] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage for command {\"command\":\"mailbox:batch:retry-failed\",\"memoryBeforeCommandInMb\":60.0,\"memoryAfterCommandInMB\":62.0,\"memoryPeakBeforeCommandInMb\":99.727,\"memoryPeakAfterCommandInMB\":99.727} {\"correlation_id\":\"e155562a-84a1-45f5-8897-1779f96886f6\",\"trace_id\":\"12141267-1032-479b-aab5-e17e133007d9\"}\n[2026-04-27 10:28:19] local.INFO: [SocialAccountService] Fetching token {\"socialAccountId\":1393,\"provider\":\"google\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:19] local.INFO: [SocialAccountService] Token needs refreshing {\"socialAccountId\":1393,\"provider\":\"google\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:19] local.INFO: [EncryptedTokenManager] Generating access token. {\"mode\":\"legacy\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:19] local.INFO: [SocialAccountService] Refreshing token from provider {\"socialAccountId\":1393,\"provider\":\"google\",\"refreshToken\":\"5aa7e2d96b53201cd16fca5d2e4ef3ad03320971fc064781d18aee3ae7b99fbf\",\"state\":\"full-refresh\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:20] local.ERROR: [SocialAccountService] Failed to refresh token {\"socialAccountId\":1393,\"provider\":\"google\",\"responseBody\":{\"error\":\"invalid_grant\",\"error_description\":\"Account has been deleted\"}} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:20] local.INFO: [SocialAccountObserver] Saving model {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:20] local.ERROR: [SocialAccountService] Failed to refresh token {\"socialAccountId\":1393,\"provider\":\"google\",\"reason\":\"Flow refresh required.\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:20] local.INFO: [SocialAccountService] Fetching token {\"socialAccountId\":1387,\"provider\":\"google\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:20] local.INFO: [SocialAccountService] Token needs refreshing {\"socialAccountId\":1387,\"provider\":\"google\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:20] local.INFO: [EncryptedTokenManager] Generating access token. {\"mode\":\"legacy\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:20] local.INFO: [SocialAccountService] Refreshing token from provider {\"socialAccountId\":1387,\"provider\":\"google\",\"refreshToken\":\"8157ac6de94842937194009e9c50e459253600f799dacf6a40755ffdbeb5bba6\",\"state\":\"full-refresh\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:21] local.ERROR: [SocialAccountService] Failed to refresh token {\"socialAccountId\":1387,\"provider\":\"google\",\"responseBody\":{\"error\":\"invalid_grant\",\"error_description\":\"Account has been deleted\"}} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:21] local.INFO: [SocialAccountObserver] Saving model {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:21] local.ERROR: [SocialAccountService] Failed to refresh token {\"socialAccountId\":1387,\"provider\":\"google\",\"reason\":\"Flow refresh required.\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:21] local.INFO: [SocialAccountService] Fetching token {\"socialAccountId\":1348,\"provider\":\"google\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:21] local.INFO: [SocialAccountService] Token needs refreshing {\"socialAccountId\":1348,\"provider\":\"google\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:21] local.INFO: [EncryptedTokenManager] Generating access token. {\"mode\":\"legacy\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:21] local.INFO: [SocialAccountService] Refreshing token from provider {\"socialAccountId\":1348,\"provider\":\"google\",\"refreshToken\":\"9e7d13d3032d0cb1b79d8e95aef01383e8e91eb52ff8ee960c8a0b6b95cd8c73\",\"state\":\"full-refresh\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:21] local.ERROR: [SocialAccountService] Failed to refresh token {\"socialAccountId\":1348,\"provider\":\"google\",\"responseBody\":{\"error\":\"invalid_grant\",\"error_description\":\"Bad Request\"}} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:21] local.INFO: [SocialAccountObserver] Saving model {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:21] local.ERROR: [SocialAccountService] Failed to refresh token {\"socialAccountId\":1348,\"provider\":\"google\",\"reason\":\"Flow refresh required.\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:21] local.INFO: [SocialAccountService] Fetching token {\"socialAccountId\":1361,\"provider\":\"google\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:21] local.INFO: [SocialAccountService] Token needs refreshing {\"socialAccountId\":1361,\"provider\":\"google\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:21] local.INFO: [EncryptedTokenManager] Generating access token. {\"mode\":\"legacy\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:21] local.INFO: [SocialAccountService] Refreshing token from provider {\"socialAccountId\":1361,\"provider\":\"google\",\"refreshToken\":\"6c843da199c2b9907445329304fcc4ec5057a4ee748d8299641764395c08e1fd\",\"state\":\"full-refresh\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:22] local.ERROR: [SocialAccountService] Failed to refresh token {\"socialAccountId\":1361,\"provider\":\"google\",\"responseBody\":{\"error\":\"invalid_grant\",\"error_description\":\"Account has been deleted\"}} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:22] local.INFO: [SocialAccountObserver] Saving model {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:22] local.ERROR: [SocialAccountService] Failed to refresh token {\"socialAccountId\":1361,\"provider\":\"google\",\"reason\":\"Flow refresh required.\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:22] local.INFO: [SocialAccountService] Fetching token {\"socialAccountId\":1310,\"provider\":\"google\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:22] local.INFO: [SocialAccountService] Token needs refreshing {\"socialAccountId\":1310,\"provider\":\"google\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:22] local.INFO: [EncryptedTokenManager] Generating access token. {\"mode\":\"legacy\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:22] local.INFO: [SocialAccountService] Refreshing token from provider {\"socialAccountId\":1310,\"provider\":\"google\",\"refreshToken\":\"e34818922c2830a660813a63f6169a4a9a992ae2cccd7dc8dd7796cfdb470ef1\",\"state\":\"full-refresh\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:23] local.ERROR: [SocialAccountService] Failed to refresh token {\"socialAccountId\":1310,\"provider\":\"google\",\"responseBody\":{\"error\":\"invalid_grant\",\"error_description\":\"Bad Request\"}} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:23] local.INFO: [SocialAccountObserver] Saving model {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:23] local.ERROR: [SocialAccountService] Failed to refresh token {\"socialAccountId\":1310,\"provider\":\"google\",\"reason\":\"Flow refresh required.\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:23] local.INFO: [SocialAccountService] Fetching token {\"socialAccountId\":1333,\"provider\":\"google\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:23] local.INFO: [SocialAccountService] Token needs refreshing {\"socialAccountId\":1333,\"provider\":\"google\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:23] local.INFO: [EncryptedTokenManager] Generating access token. {\"mode\":\"legacy\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:23] local.INFO: [SocialAccountService] Refreshing token from provider {\"socialAccountId\":1333,\"provider\":\"google\",\"refreshToken\":\"6c902986546d8e8da1dc539b046cdc1d458f519acc972e5b5f1d6a1a295165e0\",\"state\":\"full-refresh\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:23] local.ERROR: [SocialAccountService] Failed to refresh token {\"socialAccountId\":1333,\"provider\":\"google\",\"responseBody\":{\"error\":\"unauthorized_client\",\"error_description\":\"Unauthorized\"}} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:23] local.INFO: [SocialAccountObserver] Saving model {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:23] local.ERROR: [SocialAccountService] Failed to refresh token {\"socialAccountId\":1333,\"provider\":\"google\",\"reason\":\"Flow refresh required.\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:24] local.INFO: [SocialAccountService] Fetching token {\"socialAccountId\":1368,\"provider\":\"google\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:24] local.INFO: [SocialAccountService] Token needs refreshing {\"socialAccountId\":1368,\"provider\":\"google\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:24] local.INFO: [EncryptedTokenManager] Generating access token. {\"mode\":\"legacy\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:24] local.INFO: [SocialAccountService] Refreshing token from provider {\"socialAccountId\":1368,\"provider\":\"google\",\"refreshToken\":\"d2f128898ff8543bd16b69cfae37896ab85119b0f5ed2b431d739593bb600333\",\"state\":\"full-refresh\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:24] local.ERROR: [SocialAccountService] Failed to refresh token {\"socialAccountId\":1368,\"provider\":\"google\",\"responseBody\":{\"error\":\"invalid_grant\",\"error_description\":\"Bad Request\"}} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:24] local.INFO: [SocialAccountObserver] Saving model {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:24] local.ERROR: [SocialAccountService] Failed to refresh token {\"socialAccountId\":1368,\"provider\":\"google\",\"reason\":\"Flow refresh required.\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:24] local.INFO: [SocialAccountService] Fetching token {\"socialAccountId\":1365,\"provider\":\"google\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:24] local.INFO: [SocialAccountService] Token needs refreshing {\"socialAccountId\":1365,\"provider\":\"google\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:24] local.INFO: [EncryptedTokenManager] Generating access token. {\"mode\":\"legacy\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:24] local.INFO: [SocialAccountService] Refreshing token from provider {\"socialAccountId\":1365,\"provider\":\"google\",\"refreshToken\":\"7676e4a9afcd082b413248ab5ec6e487021fec6a9bdf315860a59cefad9caad8\",\"state\":\"full-refresh\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:25] local.ERROR: [SocialAccountService] Failed to refresh token {\"socialAccountId\":1365,\"provider\":\"google\",\"responseBody\":{\"error\":\"unauthorized_client\",\"error_description\":\"Unauthorized\"}} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:25] local.INFO: [SocialAccountObserver] Saving model {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:25] local.ERROR: [SocialAccountService] Failed to refresh token {\"socialAccountId\":1365,\"provider\":\"google\",\"reason\":\"Flow refresh required.\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:25] local.INFO: [SocialAccountService] Fetching token {\"socialAccountId\":1364,\"provider\":\"google\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:25] local.INFO: [SocialAccountService] Token needs refreshing {\"socialAccountId\":1364,\"provider\":\"google\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:25] local.INFO: [EncryptedTokenManager] Generating access token. {\"mode\":\"legacy\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:25] local.INFO: [SocialAccountService] Refreshing token from provider {\"socialAccountId\":1364,\"provider\":\"google\",\"refreshToken\":\"dd5882ebce76e645292ce33ae74238abbb77c0a4ecc6a2bfe723cad82e72ba8e\",\"state\":\"full-refresh\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:25] local.ERROR: [SocialAccountService] Failed to refresh token {\"socialAccountId\":1364,\"provider\":\"google\",\"responseBody\":{\"error\":\"unauthorized_client\",\"error_description\":\"Unauthorized\"}} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:25] local.INFO: [SocialAccountObserver] Saving model {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:25] local.ERROR: [SocialAccountService] Failed to refresh token {\"socialAccountId\":1364,\"provider\":\"google\",\"reason\":\"Flow refresh required.\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:25] local.INFO: [SocialAccountService] Fetching token {\"socialAccountId\":1370,\"provider\":\"office\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:25] local.INFO: [SocialAccountService] Token needs refreshing {\"socialAccountId\":1370,\"provider\":\"office\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:25] local.INFO: [EncryptedTokenManager] Generating access token. {\"mode\":\"legacy\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:25] local.INFO: [SocialAccountService] Refreshing token from provider {\"socialAccountId\":1370,\"provider\":\"office\",\"refreshToken\":\"b7ee8035306d0043cea6e00e7c4fe14f745e44074a1194db62a31cdf8b70af3e\",\"state\":\"full-refresh\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:26] local.ERROR: [SocialAccountService] Failed to refresh token {\"socialAccountId\":1370,\"provider\":\"office\",\"responseBody\":\"{\\\"error\\\":\\\"invalid_client\\\",\\\"error_description\\\":\\\"AADSTS7000215: Invalid client secret provided. Ensure the secret being sent in the request is the client secret value, not the client secret ID, for a secret added to app 'bbcbb2ef-6200-4fae-82bd-d81f5dd738da'. Trace ID: 2be54cfa-1691-4388-a284-754fdf414400 Correlation ID: 9a934049-899b-45c5-99a5-15c9c470d8a2 Timestamp: 2026-04-27 10:28:26Z\\\",\\\"error_codes\\\":[7000215],\\\"timestamp\\\":\\\"2026-04-27 10:28:26Z\\\",\\\"trace_id\\\":\\\"2be54cfa-1691-4388-a284-754fdf414400\\\",\\\"correlation_id\\\":\\\"9a934049-899b-45c5-99a5-15c9c470d8a2\\\",\\\"error_uri\\\":\\\"https://login.microsoftonline.com/error?code=7000215\\\"}\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:26] local.INFO: [SocialAccountObserver] Saving model {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:26] local.ERROR: [SocialAccountService] Failed to refresh token {\"socialAccountId\":1370,\"provider\":\"office\",\"reason\":\"Flow refresh required.\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:26] local.INFO: [SocialAccountService] Fetching token {\"socialAccountId\":1202,\"provider\":\"office\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:26] local.INFO: [SocialAccountService] Token needs refreshing {\"socialAccountId\":1202,\"provider\":\"office\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:26] local.INFO: [EncryptedTokenManager] Generating access token. {\"mode\":\"legacy\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:26] local.INFO: [SocialAccountService] Refreshing token from provider {\"socialAccountId\":1202,\"provider\":\"office\",\"refreshToken\":\"b458799ccc29b21a6e2eb5260fdb63e49ccba21bf942a3973fb63799bd7f0afe\",\"state\":\"full-refresh\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:26] local.ERROR: [SocialAccountService] Failed to refresh token {\"socialAccountId\":1202,\"provider\":\"office\",\"responseBody\":\"{\\\"error\\\":\\\"invalid_client\\\",\\\"error_description\\\":\\\"AADSTS7000215: Invalid client secret provided. Ensure the secret being sent in the request is the client secret value, not the client secret ID, for a secret added to app 'bbcbb2ef-6200-4fae-82bd-d81f5dd738da'. Trace ID: 27729e9a-0943-41bd-9752-4ec9a8444500 Correlation ID: 824a9761-9387-4c53-ae31-5c242b1d4298 Timestamp: 2026-04-27 10:28:26Z\\\",\\\"error_codes\\\":[7000215],\\\"timestamp\\\":\\\"2026-04-27 10:28:26Z\\\",\\\"trace_id\\\":\\\"27729e9a-0943-41bd-9752-4ec9a8444500\\\",\\\"correlation_id\\\":\\\"824a9761-9387-4c53-ae31-5c242b1d4298\\\",\\\"error_uri\\\":\\\"https://login.microsoftonline.com/error?code=7000215\\\"}\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:26] local.INFO: [SocialAccountObserver] Saving model {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:26] local.ERROR: [SocialAccountService] Failed to refresh token {\"socialAccountId\":1202,\"provider\":\"office\",\"reason\":\"Flow refresh required.\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:26] local.INFO: [SocialAccountService] Fetching token {\"socialAccountId\":1502,\"provider\":\"google\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:26] local.INFO: [SocialAccountService] Token needs refreshing {\"socialAccountId\":1502,\"provider\":\"google\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:26] local.INFO: [EncryptedTokenManager] Generating access token. {\"mode\":\"legacy\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:26] local.INFO: [SocialAccountService] Refreshing token from provider {\"socialAccountId\":1502,\"provider\":\"google\",\"refreshToken\":\"d417c92ebaa137295a04675f715d0511ae8acac9d779b102eac50d6300116d3e\",\"state\":\"connected\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:27] local.INFO: [SocialAccountObserver] Saving model {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:27] local.INFO: [SocialAccountObserver] Access token was modified, encrypting {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:27] local.INFO: [SocialAccountService] Token refreshed {\"socialAccountId\":1502,\"provider\":\"google\",\"state\":\"connected\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:27] local.INFO: Calendar sync job dispatched {\"calendar_id\":501} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:27] local.INFO: [SocialAccountService] Fetching token {\"socialAccountId\":1300,\"provider\":\"google\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:27] local.INFO: [SocialAccountService] Token needs refreshing {\"socialAccountId\":1300,\"provider\":\"google\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:27] local.INFO: [EncryptedTokenManager] Generating access token. {\"mode\":\"legacy\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:27] local.INFO: [SocialAccountService] Refreshing token from provider {\"socialAccountId\":1300,\"provider\":\"google\",\"refreshToken\":\"4b811db0725fd9602a95943519a7da935e2a5065da7d9ebfcb170752e3e1ddb8\",\"state\":\"full-refresh\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:27] local.ERROR: [SocialAccountService] Failed to refresh token {\"socialAccountId\":1300,\"provider\":\"google\",\"responseBody\":{\"error\":\"invalid_grant\",\"error_description\":\"Account has been deleted\"}} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:27] local.INFO: [SocialAccountObserver] Saving model {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:27] local.ERROR: [SocialAccountService] Failed to refresh token {\"socialAccountId\":1300,\"provider\":\"google\",\"reason\":\"Flow refresh required.\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:27] local.INFO: [SocialAccountService] Fetching token {\"socialAccountId\":1409,\"provider\":\"google\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:27] local.INFO: [SocialAccountService] Token needs refreshing {\"socialAccountId\":1409,\"provider\":\"google\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:27] local.INFO: [EncryptedTokenManager] Generating access token. {\"mode\":\"legacy\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:27] local.INFO: [SocialAccountService] Refreshing token from provider {\"socialAccountId\":1409,\"provider\":\"google\",\"refreshToken\":\"e2a3f2d06894894eed1ee87d9db1ace77d4d42ee6e1288a8940ad2c10333b0c4\",\"state\":\"full-refresh\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:28] local.ERROR: [SocialAccountService] Failed to refresh token {\"socialAccountId\":1409,\"provider\":\"google\",\"responseBody\":{\"error\":\"invalid_grant\",\"error_description\":\"Bad Request\"}} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:28] local.INFO: [SocialAccountObserver] Saving model {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:28] local.ERROR: [SocialAccountService] Failed to refresh token {\"socialAccountId\":1409,\"provider\":\"google\",\"reason\":\"Flow refresh required.\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:28] local.INFO: [SocialAccountService] Fetching token {\"socialAccountId\":1352,\"provider\":\"google\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:28] local.INFO: [SocialAccountService] Token needs refreshing {\"socialAccountId\":1352,\"provider\":\"google\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:28] local.INFO: [EncryptedTokenManager] Generating access token. {\"mode\":\"legacy\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:28] local.INFO: [SocialAccountService] Refreshing token from provider {\"socialAccountId\":1352,\"provider\":\"google\",\"refreshToken\":\"dd4b16b00fdc1216da6b717c02338c073636e29162826b2de6db3f064fc029eb\",\"state\":\"full-refresh\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:28] local.ERROR: [SocialAccountService] Failed to refresh token {\"socialAccountId\":1352,\"provider\":\"google\",\"responseBody\":{\"error\":\"unauthorized_client\",\"error_description\":\"Unauthorized\"}} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:28] local.INFO: [SocialAccountObserver] Saving model {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:28] local.ERROR: [SocialAccountService] Failed to refresh token {\"socialAccountId\":1352,\"provider\":\"google\",\"reason\":\"Flow refresh required.\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:28] local.INFO: [SocialAccountService] Fetching token {\"socialAccountId\":1296,\"provider\":\"office\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:28] local.INFO: [SocialAccountService] Token needs refreshing {\"socialAccountId\":1296,\"provider\":\"office\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:28] local.INFO: [EncryptedTokenManager] Generating access token. {\"mode\":\"legacy\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:28] local.INFO: [SocialAccountService] Refreshing token from provider {\"socialAccountId\":1296,\"provider\":\"office\",\"refreshToken\":\"011ae723c9d800c674e0b4be76f49fc046dac7d501b66c59ef0d9549cfa56ae5\",\"state\":\"full-refresh\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:28] local.ERROR: [SocialAccountService] Failed to refresh token {\"socialAccountId\":1296,\"provider\":\"office\",\"responseBody\":\"{\\\"error\\\":\\\"invalid_client\\\",\\\"error_description\\\":\\\"AADSTS7000215: Invalid client secret provided. Ensure the secret being sent in the request is the client secret value, not the client secret ID, for a secret added to app 'bbcbb2ef-6200-4fae-82bd-d81f5dd738da'. Trace ID: 73b326f0-9fcd-4ff5-ad61-edd6a0504500 Correlation ID: 6bd8fef3-b24c-46d8-8bba-a9b2ab41c108 Timestamp: 2026-04-27 10:28:28Z\\\",\\\"error_codes\\\":[7000215],\\\"timestamp\\\":\\\"2026-04-27 10:28:28Z\\\",\\\"trace_id\\\":\\\"73b326f0-9fcd-4ff5-ad61-edd6a0504500\\\",\\\"correlation_id\\\":\\\"6bd8fef3-b24c-46d8-8bba-a9b2ab41c108\\\",\\\"error_uri\\\":\\\"https://login.microsoftonline.com/error?code=7000215\\\"}\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:28] local.INFO: [SocialAccountObserver] Saving model {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:28] local.ERROR: [SocialAccountService] Failed to refresh token {\"socialAccountId\":1296,\"provider\":\"office\",\"reason\":\"Flow refresh required.\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:28] local.INFO: [SocialAccountService] Fetching token {\"socialAccountId\":391,\"provider\":\"office\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:28] local.INFO: [SocialAccountService] Token needs refreshing {\"socialAccountId\":391,\"provider\":\"office\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:28] local.INFO: [EncryptedTokenManager] Generating access token. {\"mode\":\"legacy\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:28] local.INFO: [SocialAccountService] Refreshing token from provider {\"socialAccountId\":391,\"provider\":\"office\",\"refreshToken\":\"00045eebae0f39b34887c6d53f92ae78064f7145e1f4b67754aebd03cfb2d881\",\"state\":\"full-refresh\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:28] local.INFO: [SocialAccountService] Fetching token {\"socialAccountId\":1502,\"provider\":\"google\"} {\"correlation_id\":\"8a9488be-81de-40f5-8554-2503eec42566\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:28] local.INFO: [SocialAccountService] Token retrieved {\"socialAccountId\":1502,\"provider\":\"google\"} {\"correlation_id\":\"8a9488be-81de-40f5-8554-2503eec42566\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:28] local.INFO: [EncryptedTokenManager] Generating access token. {\"mode\":\"legacy\"} {\"correlation_id\":\"8a9488be-81de-40f5-8554-2503eec42566\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:29] local.INFO: [Calendar] Processing sync {\"calendarId\":\"a33076c1-8d97-431a-99f0-85c9524e118b\",\"from\":null,\"to\":null,\"delta\":\"CIiFh8TP44kDEIiFh8TP44kDGAUgkZvkzgIokZvkzgI=\",\"last_sync\":\"2024-12-09 07:12:53\",\"dateMode\":\"daily\"} {\"correlation_id\":\"8a9488be-81de-40f5-8554-2503eec42566\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:29] local.INFO: [CrmOwnerResolver] Integration owner matched as CRM Owner {\"crm_provider\":\"integration-app\",\"crm_owner\":1695,\"team_id\":3143} {\"correlation_id\":\"8a9488be-81de-40f5-8554-2503eec42566\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:29] local.INFO: [SocialAccountService] Fetching token {\"socialAccountId\":1502,\"provider\":\"google\"} {\"correlation_id\":\"8a9488be-81de-40f5-8554-2503eec42566\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:29] local.INFO: [SocialAccountService] Token retrieved {\"socialAccountId\":1502,\"provider\":\"google\"} {\"correlation_id\":\"8a9488be-81de-40f5-8554-2503eec42566\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:29] local.INFO: [EncryptedTokenManager] Generating access token. {\"mode\":\"legacy\"} {\"correlation_id\":\"8a9488be-81de-40f5-8554-2503eec42566\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:29] local.ERROR: [SocialAccountService] Failed to refresh token {\"socialAccountId\":391,\"provider\":\"office\",\"responseBody\":\"{\\\"error\\\":\\\"invalid_client\\\",\\\"error_description\\\":\\\"AADSTS7000215: Invalid client secret provided. Ensure the secret being sent in the request is the client secret value, not the client secret ID, for a secret added to app 'bbcbb2ef-6200-4fae-82bd-d81f5dd738da'. Trace ID: c5f1db6f-22fb-41cc-bd89-c46497634200 Correlation ID: 7b4df0cc-bf50-4017-9b1a-e25ffb6fe9bf Timestamp: 2026-04-27 10:28:29Z\\\",\\\"error_codes\\\":[7000215],\\\"timestamp\\\":\\\"2026-04-27 10:28:29Z\\\",\\\"trace_id\\\":\\\"c5f1db6f-22fb-41cc-bd89-c46497634200\\\",\\\"correlation_id\\\":\\\"7b4df0cc-bf50-4017-9b1a-e25ffb6fe9bf\\\",\\\"error_uri\\\":\\\"https://login.microsoftonline.com/error?code=7000215\\\"}\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:29] local.INFO: [SocialAccountObserver] Saving model {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:29] local.ERROR: [SocialAccountService] Failed to refresh token {\"socialAccountId\":391,\"provider\":\"office\",\"reason\":\"Flow refresh required.\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:29] local.INFO: [SocialAccountService] Fetching token {\"socialAccountId\":1271,\"provider\":\"office\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:29] local.INFO: [SocialAccountService] Token needs refreshing {\"socialAccountId\":1271,\"provider\":\"office\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:29] local.INFO: [EncryptedTokenManager] Generating access token. {\"mode\":\"legacy\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:29] local.INFO: [SocialAccountService] Refreshing token from provider {\"socialAccountId\":1271,\"provider\":\"office\",\"refreshToken\":\"118cde2c06993147b07ccaec4cbcd5026a819dea6c71081166a492933e392afb\",\"state\":\"full-refresh\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:29] local.INFO: [Google Calendar] Failed to watch channel for calendar {\"calendarId\":\"a33076c1-8d97-431a-99f0-85c9524e118b\",\"code\":400,\"reason\":\"{\n \\\"error\\\": {\n \\\"errors\\\": [\n {\n \\\"domain\\\": \\\"global\\\",\n \\\"reason\\\": \\\"push.webhookUrlNotHttps\\\",\n \\\"message\\\": \\\"WebHook callback must be HTTPS: /webhook/calendar/google?resourceType=event\\\"\n }\n ],\n \\\"code\\\": 400,\n \\\"message\\\": \\\"WebHook callback must be HTTPS: /webhook/calendar/google?resourceType=event\\\"\n }\n}\"} {\"correlation_id\":\"8a9488be-81de-40f5-8554-2503eec42566\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:29] local.WARNING: [Calendar] Sync failed {\"calendarId\":\"a33076c1-8d97-431a-99f0-85c9524e118b\",\"code\":400,\"reason\":\"{\n \\\"error\\\": {\n \\\"errors\\\": [\n {\n \\\"domain\\\": \\\"global\\\",\n \\\"reason\\\": \\\"push.webhookUrlNotHttps\\\",\n \\\"message\\\": \\\"WebHook callback must be HTTPS: /webhook/calendar/google?resourceType=event\\\"\n }\n ],\n \\\"code\\\": 400,\n \\\"message\\\": \\\"WebHook callback must be HTTPS: /webhook/calendar/google?resourceType=event\\\"\n }\n}\"} {\"correlation_id\":\"8a9488be-81de-40f5-8554-2503eec42566\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:29] local.ERROR: [SocialAccountService] Failed to refresh token {\"socialAccountId\":1271,\"provider\":\"office\",\"responseBody\":\"{\\\"error\\\":\\\"invalid_client\\\",\\\"error_description\\\":\\\"AADSTS7000215: Invalid client secret provided. Ensure the secret being sent in the request is the client secret value, not the client secret ID, for a secret added to app 'bbcbb2ef-6200-4fae-82bd-d81f5dd738da'. Trace ID: 8d6e82ba-88c3-4ec9-95ee-a4a905803900 Correlation ID: 6ede51d5-704d-4e24-9fb7-88f69f3da5db Timestamp: 2026-04-27 10:28:29Z\\\",\\\"error_codes\\\":[7000215],\\\"timestamp\\\":\\\"2026-04-27 10:28:29Z\\\",\\\"trace_id\\\":\\\"8d6e82ba-88c3-4ec9-95ee-a4a905803900\\\",\\\"correlation_id\\\":\\\"6ede51d5-704d-4e24-9fb7-88f69f3da5db\\\",\\\"error_uri\\\":\\\"https://login.microsoftonline.com/error?code=7000215\\\"}\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:30] local.INFO: [SocialAccountObserver] Saving model {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:30] local.ERROR: [SocialAccountService] Failed to refresh token {\"socialAccountId\":1271,\"provider\":\"office\",\"reason\":\"Flow refresh required.\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:30] local.INFO: [SocialAccountService] Fetching token {\"socialAccountId\":1351,\"provider\":\"google\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:30] local.INFO: [SocialAccountService] Token needs refreshing {\"socialAccountId\":1351,\"provider\":\"google\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:30] local.INFO: [EncryptedTokenManager] Generating access token. {\"mode\":\"legacy\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:30] local.INFO: [SocialAccountService] Refreshing token from provider {\"socialAccountId\":1351,\"provider\":\"google\",\"refreshToken\":\"4271d15b9e60a606439caddc68337f783e472c85b03dacff14d1b6dfded9051c\",\"state\":\"full-refresh\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:30] local.ERROR: [SocialAccountService] Failed to refresh token {\"socialAccountId\":1351,\"provider\":\"google\",\"responseBody\":{\"error\":\"invalid_grant\",\"error_description\":\"Token has been expired or revoked.\"}} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:30] local.INFO: [SocialAccountObserver] Saving model {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:30] local.ERROR: [SocialAccountService] Failed to refresh token {\"socialAccountId\":1351,\"provider\":\"google\",\"reason\":\"Flow refresh required.\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:30] local.INFO: [SocialAccountService] Fetching token {\"socialAccountId\":1366,\"provider\":\"google\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:30] local.INFO: [SocialAccountService] Token needs refreshing {\"socialAccountId\":1366,\"provider\":\"google\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:30] local.INFO: [EncryptedTokenManager] Generating access token. {\"mode\":\"legacy\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:30] local.INFO: [SocialAccountService] Refreshing token from provider {\"socialAccountId\":1366,\"provider\":\"google\",\"refreshToken\":\"ae21385059b2eebfd43f68aecd56eccd702a1aabb6598f1f7ab594ed8af491b4\",\"state\":\"full-refresh\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:30] local.ERROR: [SocialAccountService] Failed to refresh token {\"socialAccountId\":1366,\"provider\":\"google\",\"responseBody\":{\"error\":\"invalid_grant\",\"error_description\":\"Bad Request\"}} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:30] local.INFO: [SocialAccountObserver] Saving model {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:30] local.ERROR: [SocialAccountService] Failed to refresh token {\"socialAccountId\":1366,\"provider\":\"google\",\"reason\":\"Flow refresh required.\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:30] local.INFO: [SocialAccountService] Fetching token {\"socialAccountId\":1115,\"provider\":\"google\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:30] local.INFO: [SocialAccountService] Token needs refreshing {\"socialAccountId\":1115,\"provider\":\"google\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:30] local.INFO: [EncryptedTokenManager] Generating access token. {\"mode\":\"legacy\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:30] local.INFO: [SocialAccountService] Refreshing token from provider {\"socialAccountId\":1115,\"provider\":\"google\",\"refreshToken\":\"356b60f12e262a5e24d3042386ef47d6a6cfe3074c242f4426edcec8646192b1\",\"state\":\"connected\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:30] local.INFO: [SocialAccountObserver] Saving model {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:30] local.INFO: [SocialAccountObserver] Access token was modified, encrypting {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:31] local.INFO: [SocialAccountService] Token refreshed {\"socialAccountId\":1115,\"provider\":\"google\",\"state\":\"connected\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:31] local.INFO: Calendar sync job dispatched {\"calendar_id\":378} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:31] local.INFO: [SocialAccountService] Fetching token {\"socialAccountId\":1421,\"provider\":\"office\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:31] local.INFO: [SocialAccountService] Token needs refreshing {\"socialAccountId\":1421,\"provider\":\"office\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:31] local.INFO: [EncryptedTokenManager] Generating access token. {\"mode\":\"legacy\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:31] local.INFO: [SocialAccountService] Refreshing token from provider {\"socialAccountId\":1421,\"provider\":\"office\",\"refreshToken\":\"9e3e287aa0cc900726e3d4eead2600fb64aa34b437790f6a1fd8048306cb2db9\",\"state\":\"connected\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:31] local.INFO: [SocialAccountService] Fetching token {\"socialAccountId\":1115,\"provider\":\"google\"} {\"correlation_id\":\"8fdf411d-8a4f-46c1-a46a-cc84f3c2f2b1\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:31] local.INFO: [SocialAccountService] Token retrieved {\"socialAccountId\":1115,\"provider\":\"google\"} {\"correlation_id\":\"8fdf411d-8a4f-46c1-a46a-cc84f3c2f2b1\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:31] local.INFO: [EncryptedTokenManager] Generating access token. {\"mode\":\"legacy\"} {\"correlation_id\":\"8fdf411d-8a4f-46c1-a46a-cc84f3c2f2b1\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:31] local.INFO: [Calendar] Processing sync {\"calendarId\":\"2676cb6d-f86c-427e-bf78-591e388e3c1e\",\"from\":null,\"to\":null,\"delta\":\"CJ_x49O3jpIDEJ_x49O3jpIDGAUgw67KlwMow67KlwM=\",\"last_sync\":\"2026-01-19 07:48:40\",\"dateMode\":\"daily\"} {\"correlation_id\":\"8fdf411d-8a4f-46c1-a46a-cc84f3c2f2b1\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:31] local.WARNING: [Pipedrive] Account not connected for user {\"userId\":\"e6538737-e7b4-455f-a37a-3e79b665a220\",\"account\":{\"Jiminny\\\\Models\\\\SocialAccount\":{\"id\":1116,\"sociable_id\":241,\"provider_user_id\":\"19555731\",\"expires\":1775683749,\"refresh_token_expires\":null,\"provider\":\"pipedrive\",\"state\":\"full-refresh\",\"auth_scope\":\"base,deals:full,activities:full,contacts:full,search:read\",\"retry_after\":null,\"created_at\":\"2023-09-08 09:44:29\",\"updated_at\":\"2026-04-08 22:58:34\"}}} {\"correlation_id\":\"8fdf411d-8a4f-46c1-a46a-cc84f3c2f2b1\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:31] local.INFO: [CrmOwnerResolver] Integration owner is not connected, attempting team members {\"crm_provider\":\"pipedrive\",\"crm_owner\":241,\"team_id\":19} {\"correlation_id\":\"8fdf411d-8a4f-46c1-a46a-cc84f3c2f2b1\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:31] local.INFO: [CrmOwnerResolver] No team members found with active crm connection {\"crm_provider\":\"pipedrive\",\"team_id\":19} {\"correlation_id\":\"8fdf411d-8a4f-46c1-a46a-cc84f3c2f2b1\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:31] local.INFO: [CrmOwnerResolver] No team member found with active crm connection {\"crm_provider\":\"pipedrive\",\"team_id\":19} {\"correlation_id\":\"8fdf411d-8a4f-46c1-a46a-cc84f3c2f2b1\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:31] local.WARNING: [Calendar] CRM disconnected for user so events will not be matched {\"provider\":\"pipedrive\",\"user_id\":241,\"message\":\"Your Pipedrive account has become disconnected. Please login to Jiminny to reconnect.\"} {\"correlation_id\":\"8fdf411d-8a4f-46c1-a46a-cc84f3c2f2b1\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:31] local.INFO: [SocialAccountService] Fetching token {\"socialAccountId\":1115,\"provider\":\"google\"} {\"correlation_id\":\"8fdf411d-8a4f-46c1-a46a-cc84f3c2f2b1\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:31] local.INFO: [SocialAccountService] Token retrieved {\"socialAccountId\":1115,\"provider\":\"google\"} {\"correlation_id\":\"8fdf411d-8a4f-46c1-a46a-cc84f3c2f2b1\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:31] local.INFO: [EncryptedTokenManager] Generating access token. {\"mode\":\"legacy\"} {\"correlation_id\":\"8fdf411d-8a4f-46c1-a46a-cc84f3c2f2b1\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:31] local.INFO: [Google Calendar] Failed to watch channel for calendar {\"calendarId\":\"2676cb6d-f86c-427e-bf78-591e388e3c1e\",\"code\":400,\"reason\":\"{\n \\\"error\\\": {\n \\\"errors\\\": [\n {\n \\\"domain\\\": \\\"global\\\",\n \\\"reason\\\": \\\"push.webhookUrlNotHttps\\\",\n \\\"message\\\": \\\"WebHook callback must be HTTPS: /webhook/calendar/google?resourceType=event\\\"\n }\n ],\n \\\"code\\\": 400,\n \\\"message\\\": \\\"WebHook callback must be HTTPS: /webhook/calendar/google?resourceType=event\\\"\n }\n}\"} {\"correlation_id\":\"8fdf411d-8a4f-46c1-a46a-cc84f3c2f2b1\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:31] local.WARNING: [Calendar] Sync failed {\"calendarId\":\"2676cb6d-f86c-427e-bf78-591e388e3c1e\",\"code\":400,\"reason\":\"{\n \\\"error\\\": {\n \\\"errors\\\": [\n {\n \\\"domain\\\": \\\"global\\\",\n \\\"reason\\\": \\\"push.webhookUrlNotHttps\\\",\n \\\"message\\\": \\\"WebHook callback must be HTTPS: /webhook/calendar/google?resourceType=event\\\"\n }\n ],\n \\\"code\\\": 400,\n \\\"message\\\": \\\"WebHook callback must be HTTPS: /webhook/calendar/google?resourceType=event\\\"\n }\n}\"} {\"correlation_id\":\"8fdf411d-8a4f-46c1-a46a-cc84f3c2f2b1\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:32] local.INFO: [SocialAccountObserver] Saving model {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:32] local.INFO: [SocialAccountObserver] Access token was modified, encrypting {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:32] local.INFO: [SocialAccountObserver] Refresh token was modified, encrypting {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:32] local.INFO: [SocialAccountService] Token refreshed {\"socialAccountId\":1421,\"provider\":\"office\",\"state\":\"connected\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:32] local.INFO: Calendar sync job dispatched {\"calendar_id\":504} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:32] local.NOTICE: Calendar sync end {\"retrieved_calendars\":31,\"processed_calendars\":3} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:32] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage for command {\"command\":\"calendar:sync\",\"memoryBeforeCommandInMb\":60.0,\"memoryAfterCommandInMB\":62.0,\"memoryPeakBeforeCommandInMb\":99.727,\"memoryPeakAfterCommandInMB\":99.727} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:33] local.INFO: [SocialAccountService] Fetching token {\"socialAccountId\":1421,\"provider\":\"office\"} {\"correlation_id\":\"52b2ff86-82c7-4ed0-b528-35d7d00d69f0\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:33] local.INFO: [SocialAccountService] Token retrieved {\"socialAccountId\":1421,\"provider\":\"office\"} {\"correlation_id\":\"52b2ff86-82c7-4ed0-b528-35d7d00d69f0\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:33] local.INFO: [EncryptedTokenManager] Generating access token. {\"mode\":\"legacy\"} {\"correlation_id\":\"52b2ff86-82c7-4ed0-b528-35d7d00d69f0\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:34] local.INFO: [Calendar] Processing sync {\"calendarId\":\"9e8b1a2c-1a8f-42bd-b161-810fc0baf540\",\"from\":null,\"to\":null,\"delta\":\"R0usmcdvmMuZCBYV0hguCMx0nGVsTQEodM41jSlu-5eY2JUTYHZXE-bDro2tnepp8q_Vq3ITNS8CkncjGjcBfaHkza05dCTIlaXcSIoPNGOl9vNckYy_ZhVgE1SWvgRcCeVT-SyogdB9hP-oaWbH9r51OQrrzAwmalK5aCksyUVh9jG_A-mGVzRPnUB4SKa77o5P659lpx1egON48YU2tQ.D2oErKma-mQrBdXxAKdH7beUkp6HS2PXUWYLCgvbP2c\",\"last_sync\":\"2026-04-27 06:28:41\",\"dateMode\":\"daily\"} {\"correlation_id\":\"52b2ff86-82c7-4ed0-b528-35d7d00d69f0\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:34] local.INFO: [SocialAccountService] Fetching token {\"socialAccountId\":1499,\"provider\":\"hubspot\"} {\"correlation_id\":\"52b2ff86-82c7-4ed0-b528-35d7d00d69f0\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:34] local.INFO: [SocialAccountService] Token retrieved {\"socialAccountId\":1499,\"provider\":\"hubspot\"} {\"correlation_id\":\"52b2ff86-82c7-4ed0-b528-35d7d00d69f0\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:34] local.INFO: [EncryptedTokenManager] Generating access token. {\"mode\":\"legacy\"} {\"correlation_id\":\"52b2ff86-82c7-4ed0-b528-35d7d00d69f0\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:34] local.INFO: [CrmOwnerResolver] Integration owner matched as CRM Owner {\"crm_provider\":\"hubspot\",\"crm_owner\":89,\"team_id\":2} {\"correlation_id\":\"52b2ff86-82c7-4ed0-b528-35d7d00d69f0\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:34] local.INFO: [MS Office Calendar] Skipping delta sync for daily mode {\"calendarId\":\"9e8b1a2c-1a8f-42bd-b161-810fc0baf540\"} {\"correlation_id\":\"52b2ff86-82c7-4ed0-b528-35d7d00d69f0\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:29:06] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage before starting command {\"command\":\"meeting-bot:schedule-bot\",\"memoryBeforeCommandInMb\":60.0,\"memoryPeakBeforeCommandInMb\":99.727} {\"correlation_id\":\"7e95c05b-50c8-4e0b-a01c-efeed6af544c\",\"trace_id\":\"07db4d4f-d6e7-44d9-8e54-4f038ea69afb\"}\n[2026-04-27 10:29:06] local.INFO: [ScheduleBotCommand] Number of activities to be captured: 0 {\"correlation_id\":\"7e95c05b-50c8-4e0b-a01c-efeed6af544c\",\"trace_id\":\"07db4d4f-d6e7-44d9-8e54-4f038ea69afb\"}\n[2026-04-27 10:29:06] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage for command {\"command\":\"meeting-bot:schedule-bot\",\"memoryBeforeCommandInMb\":60.0,\"memoryAfterCommandInMB\":62.0,\"memoryPeakBeforeCommandInMb\":99.727,\"memoryPeakAfterCommandInMB\":99.727} {\"correlation_id\":\"7e95c05b-50c8-4e0b-a01c-efeed6af544c\",\"trace_id\":\"07db4d4f-d6e7-44d9-8e54-4f038ea69afb\"}\n[2026-04-27 10:29:09] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage before starting command {\"command\":\"dialers:monitor-activities\",\"memoryBeforeCommandInMb\":60.0,\"memoryPeakBeforeCommandInMb\":99.727} {\"correlation_id\":\"e6e7fbf8-6e80-4529-858e-a4e95d0d3805\",\"trace_id\":\"f91a115a-9fdc-46ce-b9f2-13a1e6d0e325\"}\n[2026-04-27 10:29:09] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage for command {\"command\":\"dialers:monitor-activities\",\"memoryBeforeCommandInMb\":60.0,\"memoryAfterCommandInMB\":62.0,\"memoryPeakBeforeCommandInMb\":99.727,\"memoryPeakAfterCommandInMB\":99.727} {\"correlation_id\":\"e6e7fbf8-6e80-4529-858e-a4e95d0d3805\",\"trace_id\":\"f91a115a-9fdc-46ce-b9f2-13a1e6d0e325\"}\n[2026-04-27 10:29:10] local.NOTICE: Monitoring start {\"correlation_id\":\"211e5e36-1c6c-4198-b928-d2f1525ab0ea\",\"trace_id\":\"c26e980d-6e32-426d-9667-f1d902432b8d\"}\n[2026-04-27 10:29:10] local.NOTICE: Monitoring end {\"correlation_id\":\"211e5e36-1c6c-4198-b928-d2f1525ab0ea\",\"trace_id\":\"c26e980d-6e32-426d-9667-f1d902432b8d\"}\n[2026-04-27 10:29:12] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage before starting command {\"command\":\"mailbox:skip-lists:refresh\",\"memoryBeforeCommandInMb\":60.0,\"memoryPeakBeforeCommandInMb\":99.727} {\"correlation_id\":\"684bdf68-76ee-42dd-9d97-62f414a33b1b\",\"trace_id\":\"284c98ef-5b7f-4572-996c-72c5bc2995f9\"}\n[2026-04-27 10:29:13] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage for command {\"command\":\"mailbox:skip-lists:refresh\",\"memoryBeforeCommandInMb\":60.0,\"memoryAfterCommandInMB\":62.0,\"memoryPeakBeforeCommandInMb\":99.727,\"memoryPeakAfterCommandInMB\":99.727} {\"correlation_id\":\"684bdf68-76ee-42dd-9d97-62f414a33b1b\",\"trace_id\":\"284c98ef-5b7f-4572-996c-72c5bc2995f9\"}\n[2026-04-27 10:29:18] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage before starting command {\"command\":\"mailbox:batch:process\",\"memoryBeforeCommandInMb\":60.0,\"memoryPeakBeforeCommandInMb\":99.727} {\"correlation_id\":\"6e74b148-ae54-42ff-ab72-2ded18d10ff4\",\"trace_id\":\"d05bc9a0-450c-45b2-be3b-225a551ed909\"}\n[2026-04-27 10:29:18] local.INFO: [EmailSchedule] STARTING batch process {\"host\":\"docker_lamp_1\"} {\"correlation_id\":\"6e74b148-ae54-42ff-ab72-2ded18d10ff4\",\"trace_id\":\"d05bc9a0-450c-45b2-be3b-225a551ed909\"}\n[2026-04-27 10:29:18] local.INFO: [EmailSchedule] FINISHED batch process {\"host\":\"docker_lamp_1\",\"processed\":0} {\"correlation_id\":\"6e74b148-ae54-42ff-ab72-2ded18d10ff4\",\"trace_id\":\"d05bc9a0-450c-45b2-be3b-225a551ed909\"}\n[2026-04-27 10:29:18] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage for command {\"command\":\"mailbox:batch:process\",\"memoryBeforeCommandInMb\":60.0,\"memoryAfterCommandInMB\":62.0,\"memoryPeakBeforeCommandInMb\":99.727,\"memoryPeakAfterCommandInMB\":99.727} {\"correlation_id\":\"6e74b148-ae54-42ff-ab72-2ded18d10ff4\",\"trace_id\":\"d05bc9a0-450c-45b2-be3b-225a551ed909\"}\n[2026-04-27 10:30:08] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage before starting command {\"command\":\"meeting-bot:schedule-bot\",\"memoryBeforeCommandInMb\":60.0,\"memoryPeakBeforeCommandInMb\":99.727} {\"correlation_id\":\"24b807e0-b413-4ecc-a89b-365280be88ac\",\"trace_id\":\"542ac70c-6756-4409-8224-0844a7576d9c\"}\n[2026-04-27 10:30:08] local.INFO: [ScheduleBotCommand] Number of activities to be captured: 0 {\"correlation_id\":\"24b807e0-b413-4ecc-a89b-365280be88ac\",\"trace_id\":\"542ac70c-6756-4409-8224-0844a7576d9c\"}\n[2026-04-27 10:30:08] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage for command {\"command\":\"meeting-bot:schedule-bot\",\"memoryBeforeCommandInMb\":60.0,\"memoryAfterCommandInMB\":62.0,\"memoryPeakBeforeCommandInMb\":99.727,\"memoryPeakAfterCommandInMB\":99.727} {\"correlation_id\":\"24b807e0-b413-4ecc-a89b-365280be88ac\",\"trace_id\":\"542ac70c-6756-4409-8224-0844a7576d9c\"}\n[2026-04-27 10:30:10] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage before starting command {\"command\":\"dialers:monitor-activities\",\"memoryBeforeCommandInMb\":60.0,\"memoryPeakBeforeCommandInMb\":99.727} {\"correlation_id\":\"2711ca04-b4a7-4f3a-b0b2-5826fea4da3b\",\"trace_id\":\"2613ffca-f181-4d91-ad55-ec1bdd97ee7e\"}\n[2026-04-27 10:30:10] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage for command {\"command\":\"dialers:monitor-activities\",\"memoryBeforeCommandInMb\":60.0,\"memoryAfterCommandInMB\":62.0,\"memoryPeakBeforeCommandInMb\":99.727,\"memoryPeakAfterCommandInMB\":99.727} {\"correlation_id\":\"2711ca04-b4a7-4f3a-b0b2-5826fea4da3b\",\"trace_id\":\"2613ffca-f181-4d91-ad55-ec1bdd97ee7e\"}\n[2026-04-27 10:30:17] local.NOTICE: Monitoring start {\"correlation_id\":\"e1537475-8481-4144-b9f5-596ea8034b8a\",\"trace_id\":\"f5561d0a-0cb7-4c72-8bde-cce617183529\"}\n[2026-04-27 10:30:17] local.NOTICE: Monitoring end {\"correlation_id\":\"e1537475-8481-4144-b9f5-596ea8034b8a\",\"trace_id\":\"f5561d0a-0cb7-4c72-8bde-cce617183529\"}\n[2026-04-27 10:30:21] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage before starting command {\"command\":\"mailbox:skip-lists:refresh\",\"memoryBeforeCommandInMb\":60.0,\"memoryPeakBeforeCommandInMb\":99.727} {\"correlation_id\":\"8a911b60-16a0-416e-934e-3b7e0280e452\",\"trace_id\":\"3c2bbdd6-985d-4624-b42b-fd9cc2b4756f\"}\n[2026-04-27 10:30:21] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage for command {\"command\":\"mailbox:skip-lists:refresh\",\"memoryBeforeCommandInMb\":60.0,\"memoryAfterCommandInMB\":62.0,\"memoryPeakBeforeCommandInMb\":99.727,\"memoryPeakAfterCommandInMB\":99.727} {\"correlation_id\":\"8a911b60-16a0-416e-934e-3b7e0280e452\",\"trace_id\":\"3c2bbdd6-985d-4624-b42b-fd9cc2b4756f\"}\n[2026-04-27 10:30:25] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage before starting command {\"command\":\"mailbox:batch:process\",\"memoryBeforeCommandInMb\":60.0,\"memoryPeakBeforeCommandInMb\":99.727} {\"correlation_id\":\"f62efaa6-201f-43b2-bf45-9619982dffb1\",\"trace_id\":\"f1ad3d60-af4e-4427-9d77-6aa71cfed172\"}\n[2026-04-27 10:30:25] local.INFO: [EmailSchedule] STARTING batch process {\"host\":\"docker_lamp_1\"} {\"correlation_id\":\"f62efaa6-201f-43b2-bf45-9619982dffb1\",\"trace_id\":\"f1ad3d60-af4e-4427-9d77-6aa71cfed172\"}\n[2026-04-27 10:30:25] local.INFO: [EmailSchedule] FINISHED batch process {\"host\":\"docker_lamp_1\",\"processed\":0} {\"correlation_id\":\"f62efaa6-201f-43b2-bf45-9619982dffb1\",\"trace_id\":\"f1ad3d60-af4e-4427-9d77-6aa71cfed172\"}\n[2026-04-27 10:30:25] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage for command {\"command\":\"mailbox:batch:process\",\"memoryBeforeCommandInMb\":60.0,\"memoryAfterCommandInMB\":62.0,\"memoryPeakBeforeCommandInMb\":99.727,\"memoryPeakAfterCommandInMB\":99.727} {\"correlation_id\":\"f62efaa6-201f-43b2-bf45-9619982dffb1\",\"trace_id\":\"f1ad3d60-af4e-4427-9d77-6aa71cfed172\"}\n[2026-04-27 10:30:30] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage before starting command {\"command\":\"conference:monitor:count\",\"memoryBeforeCommandInMb\":60.0,\"memoryPeakBeforeCommandInMb\":99.727} {\"correlation_id\":\"cf6dc132-ecb8-4efc-bead-06bf1c96cae6\",\"trace_id\":\"9e2b073b-940d-498a-babe-115f633dd954\"}\n[2026-04-27 10:30:30] local.INFO: Running conference:monitor:count command for activities in (2026-04-27 10:28:00, 2026-04-27 10:30:00] {\"correlation_id\":\"cf6dc132-ecb8-4efc-bead-06bf1c96cae6\",\"trace_id\":\"9e2b073b-940d-498a-babe-115f633dd954\"}\n[2026-04-27 10:30:30] local.INFO: [conference:monitor:count] No activities found in (2026-04-27 10:28:00, 2026-04-27 10:30:00] {\"correlation_id\":\"cf6dc132-ecb8-4efc-bead-06bf1c96cae6\",\"trace_id\":\"9e2b073b-940d-498a-babe-115f633dd954\"}\n[2026-04-27 10:30:30] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage for command {\"command\":\"conference:monitor:count\",\"memoryBeforeCommandInMb\":60.0,\"memoryAfterCommandInMB\":62.0,\"memoryPeakBeforeCommandInMb\":99.727,\"memoryPeakAfterCommandInMB\":99.727} {\"correlation_id\":\"cf6dc132-ecb8-4efc-bead-06bf1c96cae6\",\"trace_id\":\"9e2b073b-940d-498a-babe-115f633dd954\"}\n[2026-04-27 10:30:32] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage before starting command {\"command\":\"activity:purge-stale\",\"memoryBeforeCommandInMb\":60.0,\"memoryPeakBeforeCommandInMb\":99.727} {\"correlation_id\":\"f455260d-d1c1-456f-8937-d4bb026d9078\",\"trace_id\":\"f8c2e585-0137-4103-9e16-1b4ce05df2ef\"}\n[2026-04-27 10:30:32] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage for command {\"command\":\"activity:purge-stale\",\"memoryBeforeCommandInMb\":60.0,\"memoryAfterCommandInMB\":62.0,\"memoryPeakBeforeCommandInMb\":99.727,\"memoryPeakAfterCommandInMB\":99.727} {\"correlation_id\":\"f455260d-d1c1-456f-8937-d4bb026d9078\",\"trace_id\":\"f8c2e585-0137-4103-9e16-1b4ce05df2ef\"}\n[2026-04-27 10:30:35] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage before starting command {\"command\":\"mailbox:text-relay:sync\",\"memoryBeforeCommandInMb\":60.0,\"memoryPeakBeforeCommandInMb\":99.727} {\"correlation_id\":\"be7d3c6a-664a-42e8-a4b3-cbd021296404\",\"trace_id\":\"0b250827-4c02-4237-8b34-e5ddd004eba1\"}\n[2026-04-27 10:30:35] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage for command {\"command\":\"mailbox:text-relay:sync\",\"memoryBeforeCommandInMb\":60.0,\"memoryAfterCommandInMB\":62.0,\"memoryPeakBeforeCommandInMb\":99.727,\"memoryPeakAfterCommandInMB\":99.727} {\"correlation_id\":\"be7d3c6a-664a-42e8-a4b3-cbd021296404\",\"trace_id\":\"0b250827-4c02-4237-8b34-e5ddd004eba1\"}\n[2026-04-27 10:30:37] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage before starting command {\"command\":\"conference:pre-meeting-notification\",\"memoryBeforeCommandInMb\":60.0,\"memoryPeakBeforeCommandInMb\":99.727} {\"correlation_id\":\"5d8fbdf6-2737-4d73-babe-699c88279e1f\",\"trace_id\":\"0882a963-b02f-4049-b670-12b92f80829a\"}\n[2026-04-27 10:30:37] local.INFO: Running pre-meeting notification command {\"correlation_id\":\"5d8fbdf6-2737-4d73-babe-699c88279e1f\",\"trace_id\":\"0882a963-b02f-4049-b670-12b92f80829a\"}\n[2026-04-27 10:30:37] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage for command {\"command\":\"conference:pre-meeting-notification\",\"memoryBeforeCommandInMb\":60.0,\"memoryAfterCommandInMB\":62.0,\"memoryPeakBeforeCommandInMb\":99.727,\"memoryPeakAfterCommandInMB\":99.727} {\"correlation_id\":\"5d8fbdf6-2737-4d73-babe-699c88279e1f\",\"trace_id\":\"0882a963-b02f-4049-b670-12b92f80829a\"}\n[2026-04-27 10:30:39] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage before starting command {\"command\":\"conference:monitor:start\",\"memoryBeforeCommandInMb\":60.0,\"memoryPeakBeforeCommandInMb\":99.727} {\"correlation_id\":\"e8968e47-9acd-4bd4-9e7d-3c415f46df05\",\"trace_id\":\"287fba85-f395-41ab-813d-49cb5d0ea06d\"}\n[2026-04-27 10:30:39] local.INFO: Running conference:monitor:start command for activities in (2026-04-27 10:20:00, 2026-04-27 10:25:00] {\"correlation_id\":\"e8968e47-9acd-4bd4-9e7d-3c415f46df05\",\"trace_id\":\"287fba85-f395-41ab-813d-49cb5d0ea06d\"}\n[2026-04-27 10:30:39] local.INFO: [conference:monitor:start] No activities found in (2026-04-27 10:20:00, 2026-04-27 10:25:00] {\"correlation_id\":\"e8968e47-9acd-4bd4-9e7d-3c415f46df05\",\"trace_id\":\"287fba85-f395-41ab-813d-49cb5d0ea06d\"}\n[2026-04-27 10:30:39] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage for command {\"command\":\"conference:monitor:start\",\"memoryBeforeCommandInMb\":60.0,\"memoryAfterCommandInMB\":62.0,\"memoryPeakBeforeCommandInMb\":99.727,\"memoryPeakAfterCommandInMB\":99.727} {\"correlation_id\":\"e8968e47-9acd-4bd4-9e7d-3c415f46df05\",\"trace_id\":\"287fba85-f395-41ab-813d-49cb5d0ea06d\"}\n[2026-04-27 10:30:43] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage before starting command {\"command\":\"conference:monitor:end\",\"memoryBeforeCommandInMb\":60.0,\"memoryPeakBeforeCommandInMb\":99.727} {\"correlation_id\":\"b87eb011-7108-46bd-aa90-975ebe41f483\",\"trace_id\":\"1f54c2a6-fad1-4804-87d8-4972a378e91b\"}\n[2026-04-27 10:30:43] local.INFO: conference:monitor:end:Jiminny\\Console\\Commands\\Activities\\MonitorMeetingEndCommand::logActivitiesEnded {\"from\":\"10:25\",\"to\":\"10:30\"} {\"correlation_id\":\"b87eb011-7108-46bd-aa90-975ebe41f483\",\"trace_id\":\"1f54c2a6-fad1-4804-87d8-4972a378e91b\"}\n[2026-04-27 10:30:44] local.INFO: conference:monitor:end:Jiminny\\Console\\Commands\\Activities\\MonitorMeetingEndCommand::logActivitiesWithUnfinishedSession {\"from\":\"00:20\",\"to\":\"00:25\"} {\"correlation_id\":\"b87eb011-7108-46bd-aa90-975ebe41f483\",\"trace_id\":\"1f54c2a6-fad1-4804-87d8-4972a378e91b\"}\n[2026-04-27 10:30:44] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage for command {\"command\":\"conference:monitor:end\",\"memoryBeforeCommandInMb\":60.0,\"memoryAfterCommandInMB\":62.0,\"memoryPeakBeforeCommandInMb\":99.727,\"memoryPeakAfterCommandInMB\":99.727} {\"correlation_id\":\"b87eb011-7108-46bd-aa90-975ebe41f483\",\"trace_id\":\"1f54c2a6-fad1-4804-87d8-4972a378e91b\"}\n[2026-04-27 10:30:51] local.NOTICE: Repairing HubSpot tokens start {\"correlation_id\":\"2e6dfa3d-f07d-4eee-8232-6138133e2fbb\",\"trace_id\":\"41dea86e-0ecc-414e-8303-e60d8ae09976\"}\n[2026-04-27 10:30:51] local.INFO: Trying to refresh HubSpot token {\"account_id\":59,\"updated_at\":\"2025-10-03 09:32:05\"} {\"correlation_id\":\"2e6dfa3d-f07d-4eee-8232-6138133e2fbb\",\"trace_id\":\"41dea86e-0ecc-414e-8303-e60d8ae09976\"}\n[2026-04-27 10:30:51] local.INFO: [EncryptedTokenManager] Generating access token. {\"mode\":\"legacy\"} {\"correlation_id\":\"2e6dfa3d-f07d-4eee-8232-6138133e2fbb\",\"trace_id\":\"41dea86e-0ecc-414e-8303-e60d8ae09976\"}\n[2026-04-27 10:30:51] local.INFO: [SocialAccountService] Refreshing token from provider {\"socialAccountId\":59,\"provider\":\"hubspot\",\"refreshToken\":\"97b78f6e2cc49965c00c2492b602b02708b1392551e6b3f113fbaa48992af90b\",\"state\":\"full-refresh\"} {\"correlation_id\":\"2e6dfa3d-f07d-4eee-8232-6138133e2fbb\",\"trace_id\":\"41dea86e-0ecc-414e-8303-e60d8ae09976\"}\n[2026-04-27 10:30:52] local.ERROR: Failed to refresh HubSpot token {\"account_id\":59,\"updated_at\":\"2025-10-03 09:32:05\",\"reason\":\"missing or invalid refresh token\",\"previous\":\"\"} {\"correlation_id\":\"2e6dfa3d-f07d-4eee-8232-6138133e2fbb\",\"trace_id\":\"41dea86e-0ecc-414e-8303-e60d8ae09976\"}\n[2026-04-27 10:30:52] local.INFO: Trying to refresh HubSpot token {\"account_id\":306,\"updated_at\":\"2023-11-27 09:30:03\"} {\"correlation_id\":\"2e6dfa3d-f07d-4eee-8232-6138133e2fbb\",\"trace_id\":\"41dea86e-0ecc-414e-8303-e60d8ae09976\"}\n[2026-04-27 10:30:52] local.INFO: [EncryptedTokenManager] Generating access token. {\"mode\":\"legacy\"} {\"correlation_id\":\"2e6dfa3d-f07d-4eee-8232-6138133e2fbb\",\"trace_id\":\"41dea86e-0ecc-414e-8303-e60d8ae09976\"}\n[2026-04-27 10:30:52] local.INFO: [SocialAccountService] Refreshing token from provider {\"socialAccountId\":306,\"provider\":\"hubspot\",\"refreshToken\":\"6fa6aa8cc641d131231acc3470f5c03cb3b07b2e580fb18f8acb3b1dbb72549b\",\"state\":\"full-refresh\"} {\"correlation_id\":\"2e6dfa3d-f07d-4eee-8232-6138133e2fbb\",\"trace_id\":\"41dea86e-0ecc-414e-8303-e60d8ae09976\"}\n[2026-04-27 10:30:52] local.ERROR: Failed to refresh HubSpot token {\"account_id\":306,\"updated_at\":\"2023-11-27 09:30:03\",\"reason\":\"missing or invalid refresh token\",\"previous\":\"\"} {\"correlation_id\":\"2e6dfa3d-f07d-4eee-8232-6138133e2fbb\",\"trace_id\":\"41dea86e-0ecc-414e-8303-e60d8ae09976\"}\n[2026-04-27 10:30:52] local.INFO: Trying to refresh HubSpot token {\"account_id\":1372,\"updated_at\":\"2025-10-02 14:47:06\"} {\"correlation_id\":\"2e6dfa3d-f07d-4eee-8232-6138133e2fbb\",\"trace_id\":\"41dea86e-0ecc-414e-8303-e60d8ae09976\"}\n[2026-04-27 10:30:52] local.INFO: [EncryptedTokenManager] Generating access token. {\"mode\":\"legacy\"} {\"correlation_id\":\"2e6dfa3d-f07d-4eee-8232-6138133e2fbb\",\"trace_id\":\"41dea86e-0ecc-414e-8303-e60d8ae09976\"}\n[2026-04-27 10:30:52] local.INFO: [SocialAccountService] Refreshing token from provider {\"socialAccountId\":1372,\"provider\":\"hubspot\",\"refreshToken\":\"9aa73948c761da29dce46c177cf9aee1fde483a44169ca38723f9f0597d7a8c4\",\"state\":\"full-refresh\"} {\"correlation_id\":\"2e6dfa3d-f07d-4eee-8232-6138133e2fbb\",\"trace_id\":\"41dea86e-0ecc-414e-8303-e60d8ae09976\"}\n[2026-04-27 10:30:52] local.ERROR: Failed to refresh HubSpot token {\"account_id\":1372,\"updated_at\":\"2025-10-02 14:47:06\",\"reason\":\"missing or invalid refresh token\",\"previous\":\"\"} {\"correlation_id\":\"2e6dfa3d-f07d-4eee-8232-6138133e2fbb\",\"trace_id\":\"41dea86e-0ecc-414e-8303-e60d8ae09976\"}\n[2026-04-27 10:30:52] local.NOTICE: Repairing HubSpot tokens end {\"total\":3,\"fixed\":0,\"failed\":3} {\"correlation_id\":\"2e6dfa3d-f07d-4eee-8232-6138133e2fbb\",\"trace_id\":\"41dea86e-0ecc-414e-8303-e60d8ae09976\"}\n[2026-04-27 10:30:56] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage before starting command {\"command\":\"jiminny:transcription:retry-failed\",\"memoryBeforeCommandInMb\":60.0,\"memoryPeakBeforeCommandInMb\":99.727} {\"correlation_id\":\"a6655a64-38ad-4c65-96f1-f5a294dbb343\",\"trace_id\":\"52cdca32-c0c8-4215-81a2-4263bbde2158\"}\n[2026-04-27 10:30:56] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage before starting command {\"command\":\"conference:pre-meeting-reminder\",\"memoryBeforeCommandInMb\":60.0,\"memoryPeakBeforeCommandInMb\":99.727} {\"correlation_id\":\"ec2d49dd-ab36-4f4e-8a60-7bf5ea384cac\",\"trace_id\":\"b3e89960-9a27-4bf7-8b55-95f5704f9a10\"}\n[2026-04-27 10:30:57] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage for command {\"command\":\"jiminny:transcription:retry-failed\",\"memoryBeforeCommandInMb\":60.0,\"memoryAfterCommandInMB\":62.0,\"memoryPeakBeforeCommandInMb\":99.727,\"memoryPeakAfterCommandInMB\":99.727} {\"correlation_id\":\"a6655a64-38ad-4c65-96f1-f5a294dbb343\",\"trace_id\":\"52cdca32-c0c8-4215-81a2-4263bbde2158\"}\n[2026-04-27 10:30:57] local.INFO: [HubSpot Journal Polling] Getting offset from database {\"offset\":\"\",\"jiminny_team_id\":1} {\"correlation_id\":\"1f928e93-b17e-4518-a1b9-a8280d1d95e8\",\"trace_id\":\"ba203ed6-fe98-4fd7-aa62-8de9fb8ffeca\"}\n[2026-04-27 10:30:57] local.INFO: [HubSpot Journal Command] Starting polling service {\"correlation_id\":\"1f928e93-b17e-4518-a1b9-a8280d1d95e8\",\"trace_id\":\"ba203ed6-fe98-4fd7-aa62-8de9fb8ffeca\"}\n[2026-04-27 10:30:57] local.INFO: [HubSpot Journal Polling] Service starting {\"memory_limit\":\"256M\",\"max_execution_time\":\"0\",\"initial_memory_mb\":62.0} {\"correlation_id\":\"1f928e93-b17e-4518-a1b9-a8280d1d95e8\",\"trace_id\":\"ba203ed6-fe98-4fd7-aa62-8de9fb8ffeca\"}\n[2026-04-27 10:30:57] local.INFO: [HubSpot Journal Polling] Acquired polling lock {\"expires_at\":\"2026-04-27T10:32:57.492763Z\"} {\"correlation_id\":\"1f928e93-b17e-4518-a1b9-a8280d1d95e8\",\"trace_id\":\"ba203ed6-fe98-4fd7-aa62-8de9fb8ffeca\"}\n[2026-04-27 10:30:57] local.INFO: [HubSpot Journal Polling] Getting offset from database {\"offset\":\"\",\"jiminny_team_id\":1} {\"correlation_id\":\"1f928e93-b17e-4518-a1b9-a8280d1d95e8\",\"trace_id\":\"ba203ed6-fe98-4fd7-aa62-8de9fb8ffeca\"}\n[2026-04-27 10:30:57] local.INFO: [HubSpot Journal API] Fetching latest journal entry {\"url\":\"https://api.hubapi.com/webhooks/v4/journal/latest\"} {\"correlation_id\":\"1f928e93-b17e-4518-a1b9-a8280d1d95e8\",\"trace_id\":\"ba203ed6-fe98-4fd7-aa62-8de9fb8ffeca\"}\n[2026-04-27 10:30:57] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage for command {\"command\":\"conference:pre-meeting-reminder\",\"memoryBeforeCommandInMb\":60.0,\"memoryAfterCommandInMB\":62.0,\"memoryPeakBeforeCommandInMb\":99.727,\"memoryPeakAfterCommandInMB\":99.727} {\"correlation_id\":\"ec2d49dd-ab36-4f4e-8a60-7bf5ea384cac\",\"trace_id\":\"b3e89960-9a27-4bf7-8b55-95f5704f9a10\"}\n[2026-04-27 10:30:58] local.INFO: [HubSpot Journal Polling] No data {\"correlation_id\":\"1f928e93-b17e-4518-a1b9-a8280d1d95e8\",\"trace_id\":\"ba203ed6-fe98-4fd7-aa62-8de9fb8ffeca\"}\n[2026-04-27 10:31:02] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage before starting command {\"command\":\"crm:reset-governor\",\"memoryBeforeCommandInMb\":60.0,\"memoryPeakBeforeCommandInMb\":99.727} {\"correlation_id\":\"f261cd75-c916-4f21-b4f4-856fe0ed315c\",\"trace_id\":\"6db8819d-ffaa-4fe8-8ccd-0dcc326b8cdc\"}\n[2026-04-27 10:31:02] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage for command {\"command\":\"crm:reset-governor\",\"memoryBeforeCommandInMb\":60.0,\"memoryAfterCommandInMB\":62.0,\"memoryPeakBeforeCommandInMb\":99.727,\"memoryPeakAfterCommandInMB\":99.727} {\"correlation_id\":\"f261cd75-c916-4f21-b4f4-856fe0ed315c\",\"trace_id\":\"6db8819d-ffaa-4fe8-8ccd-0dcc326b8cdc\"}\n[2026-04-27 10:31:03] local.INFO: [HubSpot Journal Polling] Getting offset from database {\"offset\":\"\",\"jiminny_team_id\":1} {\"correlation_id\":\"1f928e93-b17e-4518-a1b9-a8280d1d95e8\",\"trace_id\":\"ba203ed6-fe98-4fd7-aa62-8de9fb8ffeca\"}\n[2026-04-27 10:31:03] local.INFO: [HubSpot Journal API] Fetching latest journal entry {\"url\":\"https://api.hubapi.com/webhooks/v4/journal/latest\"} {\"correlation_id\":\"1f928e93-b17e-4518-a1b9-a8280d1d95e8\",\"trace_id\":\"ba203ed6-fe98-4fd7-aa62-8de9fb8ffeca\"}\n[2026-04-27 10:31:03] local.INFO: [HubSpot Journal Polling] No data {\"correlation_id\":\"1f928e93-b17e-4518-a1b9-a8280d1d95e8\",\"trace_id\":\"ba203ed6-fe98-4fd7-aa62-8de9fb8ffeca\"}\n[2026-04-27 10:31:06] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage before starting command {\"command\":\"activity:sync\",\"memoryBeforeCommandInMb\":60.0,\"memoryPeakBeforeCommandInMb\":99.727} {\"correlation_id\":\"f9151ed9-60aa-4b2b-b962-9eab5f75405b\",\"trace_id\":\"dba432b8-cb4b-4e76-8684-01d96f91e453\"}\n[2026-04-27 10:31:07] local.INFO: Dispatching activity sync job {\"import_id\":811739,\"provider\":\"twilio-flex\",\"team\":\"jiminny\"} {\"correlation_id\":\"f9151ed9-60aa-4b2b-b962-9eab5f75405b\",\"trace_id\":\"dba432b8-cb4b-4e76-8684-01d96f91e453\"}\n[2026-04-27 10:31:07] local.INFO: Dispatching activity sync job {\"import_id\":811740,\"provider\":\"xant\",\"team\":\"jiminny\"} {\"correlation_id\":\"f9151ed9-60aa-4b2b-b962-9eab5f75405b\",\"trace_id\":\"dba432b8-cb4b-4e76-8684-01d96f91e453\"}\n[2026-04-27 10:31:07] local.INFO: Dispatching activity sync job {\"import_id\":811741,\"provider\":\"apollo\",\"team\":\"jiminny\"} {\"correlation_id\":\"f9151ed9-60aa-4b2b-b962-9eab5f75405b\",\"trace_id\":\"dba432b8-cb4b-4e76-8684-01d96f91e453\"}\n[2026-04-27 10:31:07] local.INFO: Dispatching activity sync job {\"import_id\":811742,\"provider\":\"groove\",\"team\":\"jiminny\"} {\"correlation_id\":\"f9151ed9-60aa-4b2b-b962-9eab5f75405b\",\"trace_id\":\"dba432b8-cb4b-4e76-8684-01d96f91e453\"}\n[2026-04-27 10:31:07] local.INFO: Dispatching activity sync job {\"import_id\":811743,\"provider\":\"twilio-video\",\"team\":\"jiminny\"} {\"correlation_id\":\"f9151ed9-60aa-4b2b-b962-9eab5f75405b\",\"trace_id\":\"dba432b8-cb4b-4e76-8684-01d96f91e453\"}\n[2026-04-27 10:31:07] local.INFO: Dispatching activity sync job {\"import_id\":811744,\"provider\":\"hubspot\",\"team\":\"hubspot\"} {\"correlation_id\":\"f9151ed9-60aa-4b2b-b962-9eab5f75405b\",\"trace_id\":\"dba432b8-cb4b-4e76-8684-01d96f91e453\"}\n[2026-04-27 10:31:07] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage for command {\"command\":\"activity:sync\",\"memoryBeforeCommandInMb\":60.0,\"memoryAfterCommandInMB\":62.0,\"memoryPeakBeforeCommandInMb\":99.727,\"memoryPeakAfterCommandInMB\":99.727} {\"correlation_id\":\"f9151ed9-60aa-4b2b-b962-9eab5f75405b\",\"trace_id\":\"dba432b8-cb4b-4e76-8684-01d96f91e453\"}\n[2026-04-27 10:31:07] local.WARNING: [Salesforce] Account not connected for user {\"userId\":\"cdf8b554-d951-4758-bc2b-c1b85d1cd0b9\",\"account\":null} {\"correlation_id\":\"e7a521c5-fad4-48c2-b880-d572b5d237f4\",\"trace_id\":\"dba432b8-cb4b-4e76-8684-01d96f91e453\"}\n[2026-04-27 10:31:07] local.INFO: [CrmOwnerResolver] Integration owner is not connected, attempting team members {\"crm_provider\":\"salesforce\",\"crm_owner\":3,\"team_id\":1} {\"correlation_id\":\"e7a521c5-fad4-48c2-b880-d572b5d237f4\",\"trace_id\":\"dba432b8-cb4b-4e76-8684-01d96f91e453\"}\n[2026-04-27 10:31:07] local.INFO: [SocialAccountService] Fetching token {\"socialAccountId\":1500,\"provider\":\"salesforce\"} {\"correlation_id\":\"e7a521c5-fad4-48c2-b880-d572b5d237f4\",\"trace_id\":\"dba432b8-cb4b-4e76-8684-01d96f91e453\"}\n[2026-04-27 10:31:07] local.INFO: [SocialAccountService] Token retrieved {\"socialAccountId\":1500,\"provider\":\"salesforce\"} {\"correlation_id\":\"e7a521c5-fad4-48c2-b880-d572b5d237f4\",\"trace_id\":\"dba432b8-cb4b-4e76-8684-01d96f91e453\"}\n[2026-04-27 10:31:07] local.INFO: [EncryptedTokenManager] Generating access token. {\"mode\":\"legacy\"} {\"correlation_id\":\"e7a521c5-fad4-48c2-b880-d572b5d237f4\",\"trace_id\":\"dba432b8-cb4b-4e76-8684-01d96f91e453\"}\n[2026-04-27 10:31:07] local.INFO: [CrmOwnerResolver] TeamMember found with active crm connection {\"crm_provider\":\"salesforce\",\"crm_owner\":143,\"team_id\":1} {\"correlation_id\":\"e7a521c5-fad4-48c2-b880-d572b5d237f4\",\"trace_id\":\"dba432b8-cb4b-4e76-8684-01d96f91e453\"}\n[2026-04-27 10:31:07] local.INFO: [SocialAccountService] Fetching token {\"socialAccountId\":1194,\"provider\":\"twilio-flex\"} {\"correlation_id\":\"e7a521c5-fad4-48c2-b880-d572b5d237f4\",\"trace_id\":\"dba432b8-cb4b-4e76-8684-01d96f91e453\"}\n[2026-04-27 10:31:07] local.INFO: [SocialAccountService] Token retrieved {\"socialAccountId\":1194,\"provider\":\"twilio-flex\"} {\"correlation_id\":\"e7a521c5-fad4-48c2-b880-d572b5d237f4\",\"trace_id\":\"dba432b8-cb4b-4e76-8684-01d96f91e453\"}\n[2026-04-27 10:31:07] local.INFO: [EncryptedTokenManager] Generating access token. {\"mode\":\"legacy\"} {\"correlation_id\":\"e7a521c5-fad4-48c2-b880-d572b5d237f4\",\"trace_id\":\"dba432b8-cb4b-4e76-8684-01d96f91e453\"}\n[2026-04-27 10:31:07] local.INFO: [SyncActivity] Start {\"import_id\":811739,\"provider\":\"twilio-flex\",\"provider_id\":317,\"team\":\"jiminny\",\"team_id\":1} {\"correlation_id\":\"e7a521c5-fad4-48c2-b880-d572b5d237f4\",\"trace_id\":\"dba432b8-cb4b-4e76-8684-01d96f91e453\"}\n[2026-04-27 10:31:07] local.NOTICE: [TwilioFlex] Calls import start {\"correlation_id\":\"e7a521c5-fad4-48c2-b880-d572b5d237f4\",\"trace_id\":\"dba432b8-cb4b-4e76-8684-01d96f91e453\"}\n[2026-04-27 10:31:08] local.ALERT: [SyncActivity] Failed {\"import_id\":811739,\"provider\":\"twilio-flex\",\"provider_id\":317,\"team\":\"jiminny\",\"team_id\":1,\"reason\":\"[HTTP 401] Unable to fetch page: Authenticate\",\"file\":\"/home/jiminny/vendor/twilio/sdk/src/Twilio/Page.php\",\"line\":60} {\"correlation_id\":\"e7a521c5-fad4-48c2-b880-d572b5d237f4\",\"trace_id\":\"dba432b8-cb4b-4e76-8684-01d96f91e453\"}\n[2026-04-27 10:31:08] local.INFO: [HubSpot Journal Polling] Getting offset from database {\"offset\":\"\",\"jiminny_team_id\":1} {\"correlation_id\":\"1f928e93-b17e-4518-a1b9-a8280d1d95e8\",\"trace_id\":\"ba203ed6-fe98-4fd7-aa62-8de9fb8ffeca\"}\n[2026-04-27 10:31:08] local.INFO: [HubSpot Journal API] Fetching latest journal entry {\"url\":\"https://api.hubapi.com/webhooks/v4/journal/latest\"} {\"correlation_id\":\"1f928e93-b17e-4518-a1b9-a8280d1d95e8\",\"trace_id\":\"ba203ed6-fe98-4fd7-aa62-8de9fb8ffeca\"}\n[2026-04-27 10:31:09] local.INFO: [SocialAccountService] Fetching token {\"socialAccountId\":1500,\"provider\":\"salesforce\"} {\"correlation_id\":\"73848601-79bb-4161-91ae-37acf4385d24\",\"trace_id\":\"dba432b8-cb4b-4e76-8684-01d96f91e453\"}\n[2026-04-27 10:31:09] local.INFO: [SocialAccountService] Token retrieved {\"socialAccountId\":1500,\"provider\":\"salesforce\"} {\"correlation_id\":\"73848601-79bb-4161-91ae-37acf4385d24\",\"trace_id\":\"dba432b8-cb4b-4e76-8684-01d96f91e453\"}\n[2026-04-27 10:31:09] local.INFO: [EncryptedTokenManager] Generating access token. {\"mode\":\"legacy\"} {\"correlation_id\":\"73848601-79bb-4161-91ae-37acf4385d24\",\"trace_id\":\"dba432b8-cb4b-4e76-8684-01d96f91e453\"}\n[2026-04-27 10:31:09] local.INFO: [CrmOwnerResolver] Integration owner matched as CRM Owner {\"crm_provider\":\"salesforce\",\"crm_owner\":143,\"team_id\":1} {\"correlation_id\":\"73848601-79bb-4161-91ae-37acf4385d24\",\"trace_id\":\"dba432b8-cb4b-4e76-8684-01d96f91e453\"}\n[2026-04-27 10:31:09] local.INFO: [SocialAccountService] Fetching token {\"socialAccountId\":1500,\"provider\":\"salesforce\"} {\"correlation_id\":\"73848601-79bb-4161-91ae-37acf4385d24\",\"trace_id\":\"dba432b8-cb4b-4e76-8684-01d96f91e453\"}\n[2026-04-27 10:31:09] local.INFO: [HubSpot Journal Polling] No data {\"correlation_id\":\"1f928e93-b17e-4518-a1b9-a8280d1d95e8\",\"trace_id\":\"ba203ed6-fe98-4fd7-aa62-8de9fb8ffeca\"}\n[2026-04-27 10:31:09] local.INFO: [SocialAccountService] Token retrieved {\"socialAccountId\":1500,\"provider\":\"salesforce\"} {\"correlation_id\":\"73848601-79bb-4161-91ae-37acf4385d24\",\"trace_id\":\"dba432b8-cb4b-4e76-8684-01d96f91e453\"}\n[2026-04-27 10:31:09] local.INFO: [EncryptedTokenManager] Generating access token. {\"mode\":\"legacy\"} {\"correlation_id\":\"73848601-79bb-4161-91ae-37acf4385d24\",\"trace_id\":\"dba432b8-cb4b-4e76-8684-01d96f91e453\"}\n[2026-04-27 10:31:09] local.INFO: [SyncActivity] Start {\"import_id\":811740,\"provider\":\"xant\",\"provider_id\":161,\"team\":\"jiminny\",\"team_id\":1} {\"correlation_id\":\"73848601-79bb-4161-91ae-37acf4385d24\",\"trace_id\":\"dba432b8-cb4b-4e76-8684-01d96f91e453\"}\n[2026-04-27 10:31:09] local.INFO: [Salesforce] Performing query {\"query\":\"\n SELECT Playbooks_Call_Date__c,Playbooks_Call_Recording__c,CreatedDate,TaskSubtype,CallType,CallDurationInSeconds,Id,OwnerId,WhoId,WhatId,Priority,ActivityDate,Subject,Description,Status,Type\n FROM Task\n WHERE IsDeleted = false\n AND LastModifiedDate >= :from\n AND LastModifiedDate <= :to\n ORDER BY LastModifiedDate ASC\n LIMIT :limit\",\"params\":{\"from\":\"2026-04-27T10:14:00Z\",\"to\":\"2026-04-27T10:30:00Z\",\"ownerId\":null,\"subType\":null,\"limit\":5000}} {\"correlation_id\":\"73848601-79bb-4161-91ae-37acf4385d24\",\"trace_id\":\"dba432b8-cb4b-4e76-8684-01d96f91e453\"}\n[2026-04-27 10:31:09] local.INFO: [Salesforce] Sending request {\"endpoint\":\"https://jiminny--stagingenv.sandbox.my.salesforce.com/services/data/v50.0/query/?q=%0A++++++++++++SELECT+Playbooks_Call_Date__c%2CPlaybooks_Call_Recording__c%2CCreatedDate%2CTaskSubtype%2CCallType%2CCallDurationInSeconds%2CId%2COwnerId%2CWhoId%2CWhatId%2CPriority%2CActivityDate%2CSubject%2CDescription%2CStatus%2CType%0A++++++++++++++FROM+Task%0A+++++++++++++WHERE+IsDeleted+%3D+false%0A+++++++++++++++AND+LastModifiedDate+%3E%3D+2026-04-27T10%3A14%3A00Z%0A+++++++++++++++AND+LastModifiedDate+%3C%3D+2026-04-27T10%3A30%3A00Z%0A++++++++++ORDER+BY+LastModifiedDate+ASC%0A+++++++++++++LIMIT+5000 GET\",\"team_id\":1} {\"correlation_id\":\"73848601-79bb-4161-91ae-37acf4385d24\",\"trace_id\":\"dba432b8-cb4b-4e76-8684-01d96f91e453\"}\n[2026-04-27 10:31:09] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage before starting command {\"command\":\"mailbox:batch:fail-stalled\",\"memoryBeforeCommandInMb\":60.0,\"memoryPeakBeforeCommandInMb\":99.727} {\"correlation_id\":\"0d30446d-c469-4ade-80dc-f57f997ea610\",\"trace_id\":\"61eea326-3f40-4a50-a5e6-bddd7fa25d27\"}\n[2026-04-27 10:31:09] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage for command {\"command\":\"mailbox:batch:fail-stalled\",\"memoryBeforeCommandInMb\":60.0,\"memoryAfterCommandInMB\":62.0,\"memoryPeakBeforeCommandInMb\":99.727,\"memoryPeakAfterCommandInMB\":99.727} {\"correlation_id\":\"0d30446d-c469-4ade-80dc-f57f997ea610\",\"trace_id\":\"61eea326-3f40-4a50-a5e6-bddd7fa25d27\"}\n[2026-04-27 10:31:09] local.INFO: [Xant (InsideSales)] No calls found. {\"correlation_id\":\"73848601-79bb-4161-91ae-37acf4385d24\",\"trace_id\":\"dba432b8-cb4b-4e76-8684-01d96f91e453\"}\n[2026-04-27 10:31:09] local.INFO: [SyncActivity] End {\"import_id\":811740,\"provider\":\"xant\",\"provider_id\":161,\"team\":\"jiminny\",\"team_id\":1} {\"correlation_id\":\"73848601-79bb-4161-91ae-37acf4385d24\",\"trace_id\":\"dba432b8-cb4b-4e76-8684-01d96f91e453\"}\n[2026-04-27 10:31:09] local.INFO: [SyncActivity] Memory usage {\"import_id\":811740,\"provider\":\"xant\",\"provider_id\":161,\"team\":\"jiminny\",\"team_id\":1,\"memory_usage\":25149472,\"memory_real_usage\":65011712,\"pid\":13616} {\"correlation_id\":\"73848601-79bb-4161-91ae-37acf4385d24\",\"trace_id\":\"dba432b8-cb4b-4e76-8684-01d96f91e453\"}\n[2026-04-27 10:31:09] local.INFO: [SocialAccountService] Fetching token {\"socialAccountId\":1500,\"provider\":\"salesforce\"} {\"correlation_id\":\"11c977fb-9603-4dde-a4d7-f821d47fcdc9\",\"trace_id\":\"dba432b8-cb4b-4e76-8684-01d96f91e453\"}\n[2026-04-27 10:31:09] local.INFO: [SocialAccountService] Token retrieved {\"socialAccountId\":1500,\"provider\":\"salesforce\"} {\"correlation_id\":\"11c977fb-9603-4dde-a4d7-f821d47fcdc9\",\"trace_id\":\"dba432b8-cb4b-4e76-8684-01d96f91e453\"}\n[2026-04-27 10:31:09] local.INFO: [EncryptedTokenManager] Generating access token. {\"mode\":\"legacy\"} {\"correlation_id\":\"11c977fb-9603-4dde-a4d7-f821d47fcdc9\",\"trace_id\":\"dba432b8-cb4b-4e76-8684-01d96f91e453\"}\n[2026-04-27 10:31:09] local.INFO: [CrmOwnerResolver] Integration owner matched as CRM Owner {\"crm_provider\":\"salesforce\",\"crm_owner\":143,\"team_id\":1} {\"correlation_id\":\"11c977fb-9603-4dde-a4d7-f821d47fcdc9\",\"trace_id\":\"dba432b8-cb4b-4e76-8684-01d96f91e453\"}\n[2026-04-27 10:31:09] local.INFO: [SocialAccountService] Fetching token {\"socialAccountId\":1500,\"provider\":\"salesforce\"} {\"correlation_id\":\"11c977fb-9603-4dde-a4d7-f821d47fcdc9\",\"trace_id\":\"dba432b8-cb4b-4e76-8684-01d96f91e453\"}\n[2026-04-27 10:31:09] local.INFO: [SocialAccountService] Token retrieved {\"socialAccountId\":1500,\"provider\":\"salesforce\"} {\"correlation_id\":\"11c977fb-9603-4dde-a4d7-f821d47fcdc9\",\"trace_id\":\"dba432b8-cb4b-4e76-8684-01d96f91e453\"}\n[2026-04-27 10:31:09] local.INFO: [EncryptedTokenManager] Generating access token. {\"mode\":\"legacy\"} {\"correlation_id\":\"11c977fb-9603-4dde-a4d7-f821d47fcdc9\",\"trace_id\":\"dba432b8-cb4b-4e76-8684-01d96f91e453\"}\n[2026-04-27 10:31:09] local.INFO: [SyncActivity] Start {\"import_id\":811741,\"provider\":\"apollo\",\"provider_id\":441,\"team\":\"jiminny\",\"team_id\":1} {\"correlation_id\":\"11c977fb-9603-4dde-a4d7-f821d47fcdc9\",\"trace_id\":\"dba432b8-cb4b-4e76-8684-01d96f91e453\"}\n[2026-04-27 10:31:09] local.INFO: [Salesforce] Performing query {\"query\":\"\n SELECT AccountId,CreatedDate,TaskSubtype,CallType,Id,OwnerId,WhoId,WhatId,Priority,ActivityDate,Subject,Description,Status,Type\n FROM Task\n WHERE IsDeleted = false\n AND LastModifiedDate >= :from\n AND LastModifiedDate <= :to\n ORDER BY LastModifiedDate ASC\n LIMIT :limit\",\"params\":{\"from\":\"2026-04-27T10:14:00Z\",\"to\":\"2026-04-27T10:30:00Z\",\"ownerId\":null,\"subType\":null,\"limit\":5000}} {\"correlation_id\":\"11c977fb-9603-4dde-a4d7-f821d47fcdc9\",\"trace_id\":\"dba432b8-cb4b-4e76-8684-01d96f91e453\"}\n[2026-04-27 10:31:09] local.INFO: [Salesforce] Sending request {\"endpoint\":\"https://jiminny--stagingenv.sandbox.my.salesforce.com/services/data/v50.0/query/?q=%0A++++++++++++SELECT+AccountId%2CCreatedDate%2CTaskSubtype%2CCallType%2CId%2COwnerId%2CWhoId%2CWhatId%2CPriority%2CActivityDate%2CSubject%2CDescription%2CStatus%2CType%0A++++++++++++++FROM+Task%0A+++++++++++++WHERE+IsDeleted+%3D+false%0A+++++++++++++++AND+LastModifiedDate+%3E%3D+2026-04-27T10%3A14%3A00Z%0A+++++++++++++++AND+LastModifiedDate+%3C%3D+2026-04-27T10%3A30%3A00Z%0A++++++++++ORDER+BY+LastModifiedDate+ASC%0A+++++++++++++LIMIT+5000 GET\",\"team_id\":1} {\"correlation_id\":\"11c977fb-9603-4dde-a4d7-f821d47fcdc9\",\"trace_id\":\"dba432b8-cb4b-4e76-8684-01d96f91e453\"}\n[2026-04-27 10:31:10] local.INFO: [Apollo] No calls found. {\"correlation_id\":\"11c977fb-9603-4dde-a4d7-f821d47fcdc9\",\"trace_id\":\"dba432b8-cb4b-4e76-8684-01d96f91e453\"}\n[2026-04-27 10:31:10] local.INFO: [SyncActivity] End {\"import_id\":811741,\"provider\":\"apollo\",\"provider_id\":441,\"team\":\"jiminny\",\"team_id\":1} {\"correlation_id\":\"11c977fb-9603-4dde-a4d7-f821d47fcdc9\",\"trace_id\":\"dba432b8-cb4b-4e76-8684-01d96f91e453\"}\n[2026-04-27 10:31:10] local.INFO: [SyncActivity] Memory usage {\"import_id\":811741,\"provider\":\"apollo\",\"provider_id\":441,\"team\":\"jiminny\",\"team_id\":1,\"memory_usage\":25527752,\"memory_real_usage\":65011712,\"pid\":13616} {\"correlation_id\":\"11c977fb-9603-4dde-a4d7-f821d47fcdc9\",\"trace_id\":\"dba432b8-cb4b-4e76-8684-01d96f91e453\"}\n[2026-04-27 10:31:10] local.INFO: [SocialAccountService] Fetching token {\"socialAccountId\":1500,\"provider\":\"salesforce\"} {\"correlation_id\":\"856f8092-8c5e-409d-8aa6-ffb8efd8bfa1\",\"trace_id\":\"dba432b8-cb4b-4e76-8684-01d96f91e453\"}\n[2026-04-27 10:31:10] local.INFO: [SocialAccountService] Token retrieved {\"socialAccountId\":1500,\"provider\":\"salesforce\"} {\"correlation_id\":\"856f8092-8c5e-409d-8aa6-ffb8efd8bfa1\",\"trace_id\":\"dba432b8-cb4b-4e76-8684-01d96f91e453\"}\n[2026-04-27 10:31:10] local.INFO: [EncryptedTokenManager] Generating access token. {\"mode\":\"legacy\"} {\"correlation_id\":\"856f8092-8c5e-409d-8aa6-ffb8efd8bfa1\",\"trace_id\":\"dba432b8-cb4b-4e76-8684-01d96f91e453\"}\n[2026-04-27 10:31:10] local.INFO: [CrmOwnerResolver] Integration owner matched as CRM Owner {\"crm_provider\":\"salesforce\",\"crm_owner\":143,\"team_id\":1} {\"correlation_id\":\"856f8092-8c5e-409d-8aa6-ffb8efd8bfa1\",\"trace_id\":\"dba432b8-cb4b-4e76-8684-01d96f91e453\"}\n[2026-04-27 10:31:10] local.INFO: [SocialAccountService] Fetching token {\"socialAccountId\":1500,\"provider\":\"salesforce\"} {\"correlation_id\":\"856f8092-8c5e-409d-8aa6-ffb8efd8bfa1\",\"trace_id\":\"dba432b8-cb4b-4e76-8684-01d96f91e453\"}\n[2026-04-27 10:31:10] local.INFO: [SocialAccountService] Token retrieved {\"socialAccountId\":1500,\"provider\":\"salesforce\"} {\"correlation_id\":\"856f8092-8c5e-409d-8aa6-ffb8efd8bfa1\",\"trace_id\":\"dba432b8-cb4b-4e76-8684-01d96f91e453\"}\n[2026-04-27 10:31:10] local.INFO: [EncryptedTokenManager] Generating access token. {\"mode\":\"legacy\"} {\"correlation_id\":\"856f8092-8c5e-409d-8aa6-ffb8efd8bfa1\",\"trace_id\":\"dba432b8-cb4b-4e76-8684-01d96f91e453\"}\n[2026-04-27 10:31:10] local.INFO: [SyncActivity] Start {\"import_id\":811742,\"provider\":\"groove\",\"provider_id\":228,\"team\":\"jiminny\",\"team_id\":1} {\"correlation_id\":\"856f8092-8c5e-409d-8aa6-ffb8efd8bfa1\",\"trace_id\":\"dba432b8-cb4b-4e76-8684-01d96f91e453\"}\n[2026-04-27 10:31:10] local.INFO: [Salesforce] Performing query {\"query\":\"\n SELECT call_recording_url__c,TaskSubtype,CreatedDate,CallType,CallDurationInSeconds,Id,OwnerId,WhoId,WhatId,Priority,ActivityDate,Subject,Description,Status,Type\n FROM Task\n WHERE IsDeleted = false\n AND LastModifiedDate >= :from\n AND LastModifiedDate <= :to\n ORDER BY LastModifiedDate ASC\n LIMIT :limit\",\"params\":{\"from\":\"2026-04-27T10:14:00Z\",\"to\":\"2026-04-27T10:30:00Z\",\"ownerId\":null,\"subType\":null,\"limit\":5000}} {\"correlation_id\":\"856f8092-8c5e-409d-8aa6-ffb8efd8bfa1\",\"trace_id\":\"dba432b8-cb4b-4e76-8684-01d96f91e453\"}\n[2026-04-27 10:31:10] local.INFO: [Salesforce] Sending request {\"endpoint\":\"https://jiminny--stagingenv.sandbox.my.salesforce.com/services/data/v50.0/query/?q=%0A++++++++++++SELECT+call_recording_url__c%2CTaskSubtype%2CCreatedDate%2CCallType%2CCallDurationInSeconds%2CId%2COwnerId%2CWhoId%2CWhatId%2CPriority%2CActivityDate%2CSubject%2CDescription%2CStatus%2CType%0A++++++++++++++FROM+Task%0A+++++++++++++WHERE+IsDeleted+%3D+false%0A+++++++++++++++AND+LastModifiedDate+%3E%3D+2026-04-27T10%3A14%3A00Z%0A+++++++++++++++AND+LastModifiedDate+%3C%3D+2026-04-27T10%3A30%3A00Z%0A++++++++++ORDER+BY+LastModifiedDate+ASC%0A+++++++++++++LIMIT+5000 GET\",\"team_id\":1} {\"correlation_id\":\"856f8092-8c5e-409d-8aa6-ffb8efd8bfa1\",\"trace_id\":\"dba432b8-cb4b-4e76-8684-01d96f91e453\"}\n[2026-04-27 10:31:10] local.ERROR: [Salesforce] Request exception [400] \nSELECT call_recording_url__c,TaskSubtype\n ^\nERROR at Row:1:Column:8\nNo such column 'call_recording_url__c' on entity 'Task'. If you are attempting to use a custom field, be sure to append the '__c' after the custom field name. Please reference your WSDL or the describe call for the appropriate names. {\"url\":\"https://jiminny--stagingenv.sandbox.my.salesforce.com/services/data/v50.0/query/?q=%0A++++++++++++SELECT+call_recording_url__c%2CTaskSubtype%2CCreatedDate%2CCallType%2CCallDurationInSeconds%2CId%2COwnerId%2CWhoId%2CWhatId%2CPriority%2CActivityDate%2CSubject%2CDescription%2CStatus%2CType%0A++++++++++++++FROM+Task%0A+++++++++++++WHERE+IsDeleted+%3D+false%0A+++++++++++++++AND+LastModifiedDate+%3E%3D+2026-04-27T10%3A14%3A00Z%0A+++++++++++++++AND+LastModifiedDate+%3C%3D+2026-04-27T10%3A30%3A00Z%0A++++++++++ORDER+BY+LastModifiedDate+ASC%0A+++++++++++++LIMIT+5000\",\"data\":{\"headers\":{\"Authorization\":\"Bearer 00D2g0000008hH4!AQEAQC41CLa_xP1bAbi9E2OUbAOpW..Wx961OtqTwJ3oWpOKs6yPdm47B4yKRBHbRmmJ.Lvd34sjr0gnAkV8fLb8IZWzIW93\"}},\"response\":{\"GuzzleHttp\\\\Psr7\\\\Stream\":\"[{\\\"message\\\":\\\"\\\\nSELECT call_recording_url__c,TaskSubtype\\\\n ^\\\\nERROR at Row:1:Column:8\\\\nNo such column 'call_recording_url__c' on entity 'Task'. If you are attempting to use a custom field, be sure to append the '__c' after the custom field name. Please reference your WSDL or the describe call for the appropriate names.\\\",\\\"errorCode\\\":\\\"INVALID_FIELD\\\"}]\"},\"fields\":[]} {\"correlation_id\":\"856f8092-8c5e-409d-8aa6-ffb8efd8bfa1\",\"trace_id\":\"dba432b8-cb4b-4e76-8684-01d96f91e453\"}\n[2026-04-27 10:31:10] local.ALERT: [SyncActivity] Failed {\"import_id\":811742,\"provider\":\"groove\",\"provider_id\":228,\"team\":\"jiminny\",\"team_id\":1,\"reason\":\"\nSELECT call_recording_url__c,TaskSubtype\n ^\nERROR at Row:1:Column:8\nNo such column 'call_recording_url__c' on entity 'Task'. If you are attempting to use a custom field, be sure to append the '__c' after the custom field name. Please reference your WSDL or the describe call for the appropriate names.\",\"file\":\"/home/jiminny/app/Services/Crm/Salesforce/Client.php\",\"line\":564} {\"correlation_id\":\"856f8092-8c5e-409d-8aa6-ffb8efd8bfa1\",\"trace_id\":\"dba432b8-cb4b-4e76-8684-01d96f91e453\"}\n[2026-04-27 10:31:10] local.INFO: [SocialAccountService] Fetching token {\"socialAccountId\":1500,\"provider\":\"salesforce\"} {\"correlation_id\":\"0ec6aef6-345c-4602-b6a1-646b95e481c0\",\"trace_id\":\"dba432b8-cb4b-4e76-8684-01d96f91e453\"}\n[2026-04-27 10:31:10] local.INFO: [SocialAccountService] Token retrieved {\"socialAccountId\":1500,\"provider\":\"salesforce\"} {\"correlation_id\":\"0ec6aef6-345c-4602-b6a1-646b95e481c0\",\"trace_id\":\"dba432b8-cb4b-4e76-8684-01d96f91e453\"}\n[2026-04-27 10:31:10] local.INFO: [EncryptedTokenManager] Generating access token. {\"mode\":\"legacy\"} {\"correlation_id\":\"0ec6aef6-345c-4602-b6a1-646b95e481c0\",\"trace_id\":\"dba432b8-cb4b-4e76-8684-01d96f91e453\"}\n[2026-04-27 10:31:10] local.INFO: [CrmOwnerResolver] Integration owner matched as CRM Owner {\"crm_provider\":\"salesforce\",\"crm_owner\":143,\"team_id\":1} {\"correlation_id\":\"0ec6aef6-345c-4602-b6a1-646b95e481c0\",\"trace_id\":\"dba432b8-cb4b-4e76-8684-01d96f91e453\"}\n[2026-04-27 10:31:10] local.INFO: [SocialAccountService] Fetching token {\"socialAccountId\":1500,\"provider\":\"salesforce\"} {\"correlation_id\":\"0ec6aef6-345c-4602-b6a1-646b95e481c0\",\"trace_id\":\"dba432b8-cb4b-4e76-8684-01d96f91e453\"}\n[2026-04-27 10:31:10] local.INFO: [SocialAccountService] Token retrieved {\"socialAccountId\":1500,\"provider\":\"salesforce\"} {\"correlation_id\":\"0ec6aef6-345c-4602-b6a1-646b95e481c0\",\"trace_id\":\"dba432b8-cb4b-4e76-8684-01d96f91e453\"}\n[2026-04-27 10:31:10] local.INFO: [EncryptedTokenManager] Generating access token. {\"mode\":\"legacy\"} {\"correlation_id\":\"0ec6aef6-345c-4602-b6a1-646b95e481c0\",\"trace_id\":\"dba432b8-cb4b-4e76-8684-01d96f91e453\"}\n[2026-04-27 10:31:10] local.INFO: [SyncActivity] Start {\"import_id\":811743,\"provider\":\"twilio-video\",\"provider_id\":243,\"team\":\"jiminny\",\"team_id\":1} {\"correlation_id\":\"0ec6aef6-345c-4602-b6a1-646b95e481c0\",\"trace_id\":\"dba432b8-cb4b-4e76-8684-01d96f91e453\"}\n[2026-04-27 10:31:10] local.INFO: [Salesforce] Performing query {\"query\":\"SELECT Id,OwnerId,WhoId,WhatId,Priority,ActivityDate,Subject,Description,Status,Type,twilio_call_sid__c,Lead_UUID__c,Opportunity__c\n FROM Task\n WHERE Type = 'Video'\n AND isClosed = true\n AND IsDeleted = false\n AND LastModifiedDate >= :from\n AND twilio_call_sid__c != NULL AND LastModifiedDate <= :to ORDER BY LastModifiedDate ASC\n LIMIT :limit\",\"params\":{\"from\":\"2026-04-27T10:14:00Z\",\"to\":\"2026-04-27T10:30:00Z\",\"ownerId\":null,\"subType\":null,\"limit\":5000}} {\"correlation_id\":\"0ec6aef6-345c-4602-b6a1-646b95e481c0\",\"trace_id\":\"dba432b8-cb4b-4e76-8684-01d96f91e453\"}\n[2026-04-27 10:31:10] local.INFO: [Salesforce] Sending request {\"endpoint\":\"https://jiminny--stagingenv.sandbox.my.salesforce.com/services/data/v50.0/query/?q=SELECT+Id%2COwnerId%2CWhoId%2CWhatId%2CPriority%2CActivityDate%2CSubject%2CDescription%2CStatus%2CType%2Ctwilio_call_sid__c%2CLead_UUID__c%2COpportunity__c%0A++++++++++++++FROM+Task%0A++++++++++++WHERE+Type+%3D+%27Video%27%0A++++++++++++++AND+isClosed+%3D+true%0A++++++++++++++AND+IsDeleted+%3D+false%0A++++++++++++++AND+LastModifiedDate+%3E%3D+2026-04-27T10%3A14%3A00Z%0A++++++++++++++AND+twilio_call_sid__c+%21%3D+NULL+AND+LastModifiedDate+%3C%3D+2026-04-27T10%3A30%3A00Z+ORDER+BY+LastModifiedDate+ASC%0A+++++++++++++LIMIT+5000 GET\",\"team_id\":1} {\"correlation_id\":\"0ec6aef6-345c-4602-b6a1-646b95e481c0\",\"trace_id\":\"dba432b8-cb4b-4e76-8684-01d96f91e453\"}\n[2026-04-27 10:31:11] local.INFO: [Twilio Video] No calls found. {\"correlation_id\":\"0ec6aef6-345c-4602-b6a1-646b95e481c0\",\"trace_id\":\"dba432b8-cb4b-4e76-8684-01d96f91e453\"}\n[2026-04-27 10:31:11] local.INFO: [SyncActivity] End {\"import_id\":811743,\"provider\":\"twilio-video\",\"provider_id\":243,\"team\":\"jiminny\",\"team_id\":1} {\"correlation_id\":\"0ec6aef6-345c-4602-b6a1-646b95e481c0\",\"trace_id\":\"dba432b8-cb4b-4e76-8684-01d96f91e453\"}\n[2026-04-27 10:31:11] local.INFO: [SyncActivity] Memory usage {\"import_id\":811743,\"provider\":\"twilio-video\",\"provider_id\":243,\"team\":\"jiminny\",\"team_id\":1,\"memory_usage\":25679680,\"memory_real_usage\":65011712,\"pid\":13616} {\"correlation_id\":\"0ec6aef6-345c-4602-b6a1-646b95e481c0\",\"trace_id\":\"dba432b8-cb4b-4e76-8684-01d96f91e453\"}\n[2026-04-27 10:31:11] local.INFO: [SocialAccountService] Fetching token {\"socialAccountId\":1499,\"provider\":\"hubspot\"} {\"correlation_id\":\"3b9d7e85-b8c2-4ae2-863e-c343c277afdc\",\"trace_id\":\"dba432b8-cb4b-4e76-8684-01d96f91e453\"}\n[2026-04-27 10:31:11] local.INFO: [SocialAccountService] Token retrieved {\"socialAccountId\":1499,\"provider\":\"hubspot\"} {\"correlation_id\":\"3b9d7e85-b8c2-4ae2-863e-c343c277afdc\",\"trace_id\":\"dba432b8-cb4b-4e76-8684-01d96f91e453\"}\n[2026-04-27 10:31:11] local.INFO: [EncryptedTokenManager] Generating access token. {\"mode\":\"legacy\"} {\"correlation_id\":\"3b9d7e85-b8c2-4ae2-863e-c343c277afdc\",\"trace_id\":\"dba432b8-cb4b-4e76-8684-01d96f91e453\"}\n[2026-04-27 10:31:11] local.INFO: [CrmOwnerResolver] Integration owner matched as CRM Owner {\"crm_provider\":\"hubspot\",\"crm_owner\":89,\"team_id\":2} {\"correlation_id\":\"3b9d7e85-b8c2-4ae2-863e-c343c277afdc\",\"trace_id\":\"dba432b8-cb4b-4e76-8684-01d96f91e453\"}\n[2026-04-27 10:31:11] local.INFO: [SocialAccountService] Fetching token {\"socialAccountId\":408,\"provider\":\"hubspot\"} {\"correlation_id\":\"3b9d7e85-b8c2-4ae2-863e-c343c277afdc\",\"trace_id\":\"dba432b8-cb4b-4e76-8684-01d96f91e453\"}\n[2026-04-27 10:31:11] local.INFO: [SocialAccountService] Token needs refreshing {\"socialAccountId\":408,\"provider\":\"hubspot\"} {\"correlation_id\":\"3b9d7e85-b8c2-4ae2-863e-c343c277afdc\",\"trace_id\":\"dba432b8-cb4b-4e76-8684-01d96f91e453\"}\n[2026-04-27 10:31:11] local.INFO: [EncryptedTokenManager] Generating access token. {\"mode\":\"legacy\"} {\"correlation_id\":\"3b9d7e85-b8c2-4ae2-863e-c343c277afdc\",\"trace_id\":\"dba432b8-cb4b-4e76-8684-01d96f91e453\"}\n[2026-04-27 10:31:11] local.INFO: [SocialAccountService] Refreshing token from provider {\"socialAccountId\":408,\"provider\":\"hubspot\",\"refreshToken\":\"de4e47eb985578f4218833e763e31059e88b562e87e10749b3389be2328f0aa7\",\"state\":\"connected\"} {\"correlation_id\":\"3b9d7e85-b8c2-4ae2-863e-c343c277afdc\",\"trace_id\":\"dba432b8-cb4b-4e76-8684-01d96f91e453\"}\n[2026-04-27 10:31:11] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage before starting command {\"command\":\"crm:bullhorn:ping\",\"memoryBeforeCommandInMb\":60.0,\"memoryPeakBeforeCommandInMb\":99.727} {\"correlation_id\":\"a2085168-092a-44b0-8dd2-c8845bc36f86\",\"trace_id\":\"9445425b-6aaf-4a23-9323-ecf85939f967\"}\n[2026-04-27 10:31:11] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage for command {\"command\":\"crm:bullhorn:ping\",\"memoryBeforeCommandInMb\":60.0,\"memoryAfterCommandInMB\":62.0,\"memoryPeakBeforeCommandInMb\":99.727,\"memoryPeakAfterCommandInMB\":99.727} {\"correlation_id\":\"a2085168-092a-44b0-8dd2-c8845bc36f86\",\"trace_id\":\"9445425b-6aaf-4a23-9323-ecf85939f967\"}\n[2026-04-27 10:31:12] local.INFO: [SocialAccountObserver] Saving model {\"correlation_id\":\"3b9d7e85-b8c2-4ae2-863e-c343c277afdc\",\"trace_id\":\"dba432b8-cb4b-4e76-8684-01d96f91e453\"}","depth":4,"on_screen":true,"value":"[2026-04-27 10:26:25] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage before starting command {\"command\":\"mailbox:sync\",\"memoryBeforeCommandInMb\":60.0,\"memoryPeakBeforeCommandInMb\":99.727} {\"correlation_id\":\"7b1ea2fb-8f36-4897-9970-91b4abba2cc7\",\"trace_id\":\"265be7c0-bf8f-4037-a0d2-57df3ef1d7e1\"}\n[2026-04-27 10:26:25] local.INFO: [EmailSchedule] STARTING Inbox Sync {\"host\":\"docker_lamp_1\"} {\"correlation_id\":\"7b1ea2fb-8f36-4897-9970-91b4abba2cc7\",\"trace_id\":\"265be7c0-bf8f-4037-a0d2-57df3ef1d7e1\"}\n[2026-04-27 10:26:25] local.INFO: [EmailSchedule] FINISHED Inbox Sync {\"host\":\"docker_lamp_1\",\"events\":2} {\"correlation_id\":\"7b1ea2fb-8f36-4897-9970-91b4abba2cc7\",\"trace_id\":\"265be7c0-bf8f-4037-a0d2-57df3ef1d7e1\"}\n[2026-04-27 10:26:25] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage for command {\"command\":\"mailbox:sync\",\"memoryBeforeCommandInMb\":60.0,\"memoryAfterCommandInMB\":62.0,\"memoryPeakBeforeCommandInMb\":99.727,\"memoryPeakAfterCommandInMB\":99.727} {\"correlation_id\":\"7b1ea2fb-8f36-4897-9970-91b4abba2cc7\",\"trace_id\":\"265be7c0-bf8f-4037-a0d2-57df3ef1d7e1\"}\n[2026-04-27 10:26:26] local.INFO: [automated-reports] Started {\"correlation_id\":\"d3f79034-397b-4936-9305-bdd99b23f2dd\",\"trace_id\":\"aa0f8702-73eb-4947-a765-2e89244599c3\"}\n[2026-04-27 10:26:26] local.INFO: [automated-reports] Checking conditions {\"isMonday\":true,\"isWeekend\":false,\"isFirstDayOfMonth\":false,\"currentMonth\":4,\"isQuarterlyMonth\":true} {\"correlation_id\":\"d3f79034-397b-4936-9305-bdd99b23f2dd\",\"trace_id\":\"aa0f8702-73eb-4947-a765-2e89244599c3\"}\n[2026-04-27 10:26:26] local.INFO: [automated-reports] Processing daily reports {\"correlation_id\":\"d3f79034-397b-4936-9305-bdd99b23f2dd\",\"trace_id\":\"aa0f8702-73eb-4947-a765-2e89244599c3\"}\n[2026-04-27 10:26:26] local.INFO: [automated-reports] Found 1 daily reports to process {\"correlation_id\":\"d3f79034-397b-4936-9305-bdd99b23f2dd\",\"trace_id\":\"aa0f8702-73eb-4947-a765-2e89244599c3\"}\n[2026-04-27 10:26:26] local.INFO: [automated-reports] Dispatching Generate Report job for report {\"reportUuid\":\"4f6ca2b5-1993-48aa-99ad-b66f19f15d43\",\"teamId\":1,\"frequency\":\"weekly\",\"type\":\"ask_jiminny\"} {\"correlation_id\":\"d3f79034-397b-4936-9305-bdd99b23f2dd\",\"trace_id\":\"aa0f8702-73eb-4947-a765-2e89244599c3\"}\n[2026-04-27 10:26:26] local.INFO: [automated-reports] Processing weekly reports {\"correlation_id\":\"d3f79034-397b-4936-9305-bdd99b23f2dd\",\"trace_id\":\"aa0f8702-73eb-4947-a765-2e89244599c3\"}\n[2026-04-27 10:26:26] local.INFO: [automated-reports] Found 1 weekly reports to process {\"correlation_id\":\"d3f79034-397b-4936-9305-bdd99b23f2dd\",\"trace_id\":\"aa0f8702-73eb-4947-a765-2e89244599c3\"}\n[2026-04-27 10:26:26] local.INFO: [automated-reports] Dispatching Generate Report job for report {\"reportUuid\":\"4f6ca2b5-1993-48aa-99ad-b66f19f15d43\",\"teamId\":1,\"frequency\":\"weekly\",\"type\":\"ask_jiminny\"} {\"correlation_id\":\"d3f79034-397b-4936-9305-bdd99b23f2dd\",\"trace_id\":\"aa0f8702-73eb-4947-a765-2e89244599c3\"}\n[2026-04-27 10:26:26] local.INFO: [automated-reports] Completed {\"correlation_id\":\"d3f79034-397b-4936-9305-bdd99b23f2dd\",\"trace_id\":\"aa0f8702-73eb-4947-a765-2e89244599c3\"}\n[2026-04-27 10:26:26] local.INFO: [HubSpot Journal Polling] Getting offset from database {\"offset\":\"\",\"jiminny_team_id\":1} {\"correlation_id\":\"b453f0f4-0d2c-491c-a935-14c60265ec18\",\"trace_id\":\"a50202d6-5601-46b4-a02e-cf5ac8d061fa\"}\n[2026-04-27 10:26:26] local.INFO: [HubSpot Journal API] Fetching latest journal entry {\"url\":\"https://api.hubapi.com/webhooks/v4/journal/latest\"} {\"correlation_id\":\"b453f0f4-0d2c-491c-a935-14c60265ec18\",\"trace_id\":\"a50202d6-5601-46b4-a02e-cf5ac8d061fa\"}\n[2026-04-27 10:26:26] local.INFO: [AskJiminnyReport:Generate] Started {\"automatedReportUuid\":\"4f6ca2b5-1993-48aa-99ad-b66f19f15d43\"} {\"correlation_id\":\"c6277ddd-09a6-4b04-8d42-bd395fc07d0a\",\"trace_id\":\"aa0f8702-73eb-4947-a765-2e89244599c3\"}\n[2026-04-27 10:26:26] local.INFO: [HubSpot Journal Polling] No data {\"correlation_id\":\"b453f0f4-0d2c-491c-a935-14c60265ec18\",\"trace_id\":\"a50202d6-5601-46b4-a02e-cf5ac8d061fa\"}\n[2026-04-27 10:26:26] local.WARNING: [HubSpot Journal Polling] Maximum empty results reached, stopping {\"empty_results\":5,\"max_empty_results\":5} {\"correlation_id\":\"b453f0f4-0d2c-491c-a935-14c60265ec18\",\"trace_id\":\"a50202d6-5601-46b4-a02e-cf5ac8d061fa\"}\n[2026-04-27 10:26:26] local.WARNING: [HubSpot Journal Polling] Maximum empty results reached, stopping {\"empty_results\":5,\"max_empty_results\":5} {\"correlation_id\":\"b453f0f4-0d2c-491c-a935-14c60265ec18\",\"trace_id\":\"a50202d6-5601-46b4-a02e-cf5ac8d061fa\"}\n[2026-04-27 10:26:26] local.INFO: [HubSpot Journal Polling] Service ending {\"runtime_seconds\":56,\"total_cycles\":5,\"files_downloaded\":0,\"empty_files\":0,\"other_portal_skipped\":0,\"total_events\":0,\"events_per_file\":0,\"avg_api_ms\":273.7,\"avg_download_ms\":0.0,\"avg_transform_ms\":0.0,\"avg_process_ms\":0.0,\"peak_memory_mb\":99.73} {\"correlation_id\":\"b453f0f4-0d2c-491c-a935-14c60265ec18\",\"trace_id\":\"a50202d6-5601-46b4-a02e-cf5ac8d061fa\"}\n[2026-04-27 10:26:26] local.INFO: [HubSpot Journal Polling] Released polling lock {\"correlation_id\":\"b453f0f4-0d2c-491c-a935-14c60265ec18\",\"trace_id\":\"a50202d6-5601-46b4-a02e-cf5ac8d061fa\"}\n[2026-04-27 10:26:27] local.INFO: [AskJiminnyReport] Fetched activity IDs for saved search {\"saved_search_id\":1977,\"user_id\":143,\"activity_count\":0} {\"correlation_id\":\"c6277ddd-09a6-4b04-8d42-bd395fc07d0a\",\"trace_id\":\"aa0f8702-73eb-4947-a765-2e89244599c3\"}\n[2026-04-27 10:26:27] local.INFO: [AskJiminnyReport:Generate] Fetched activity IDs {\"automatedReportUuid\":\"4f6ca2b5-1993-48aa-99ad-b66f19f15d43\",\"activityCount\":0} {\"correlation_id\":\"c6277ddd-09a6-4b04-8d42-bd395fc07d0a\",\"trace_id\":\"aa0f8702-73eb-4947-a765-2e89244599c3\"}\n[2026-04-27 10:26:27] local.INFO: [AskJiminnyReport:Generate] Not enough activities, skipped {\"automatedReportUuid\":\"4f6ca2b5-1993-48aa-99ad-b66f19f15d43\",\"activityCount\":0} {\"correlation_id\":\"c6277ddd-09a6-4b04-8d42-bd395fc07d0a\",\"trace_id\":\"aa0f8702-73eb-4947-a765-2e89244599c3\"}\n[2026-04-27 10:26:27] local.INFO: [AskJiminnyReport:Generate] Dispatched not-generated notifications {\"automatedReportUuid\":\"4f6ca2b5-1993-48aa-99ad-b66f19f15d43\",\"recipientsCount\":1} {\"correlation_id\":\"c6277ddd-09a6-4b04-8d42-bd395fc07d0a\",\"trace_id\":\"aa0f8702-73eb-4947-a765-2e89244599c3\"}\n[2026-04-27 10:26:27] local.INFO: [AskJiminnyReport:Generate] Started {\"automatedReportUuid\":\"4f6ca2b5-1993-48aa-99ad-b66f19f15d43\"} {\"correlation_id\":\"f1d3e4df-9591-487f-ac2a-a43ddb89a466\",\"trace_id\":\"aa0f8702-73eb-4947-a765-2e89244599c3\"}\n[2026-04-27 10:26:27] local.INFO: [AskJiminnyReport] Fetched activity IDs for saved search {\"saved_search_id\":1977,\"user_id\":143,\"activity_count\":0} {\"correlation_id\":\"f1d3e4df-9591-487f-ac2a-a43ddb89a466\",\"trace_id\":\"aa0f8702-73eb-4947-a765-2e89244599c3\"}\n[2026-04-27 10:26:27] local.INFO: [AskJiminnyReport:Generate] Fetched activity IDs {\"automatedReportUuid\":\"4f6ca2b5-1993-48aa-99ad-b66f19f15d43\",\"activityCount\":0} {\"correlation_id\":\"f1d3e4df-9591-487f-ac2a-a43ddb89a466\",\"trace_id\":\"aa0f8702-73eb-4947-a765-2e89244599c3\"}\n[2026-04-27 10:26:27] local.INFO: [AskJiminnyReport:Generate] Not enough activities, skipped {\"automatedReportUuid\":\"4f6ca2b5-1993-48aa-99ad-b66f19f15d43\",\"activityCount\":0} {\"correlation_id\":\"f1d3e4df-9591-487f-ac2a-a43ddb89a466\",\"trace_id\":\"aa0f8702-73eb-4947-a765-2e89244599c3\"}\n[2026-04-27 10:26:27] local.INFO: [AskJiminnyReport:Generate] Dispatched not-generated notifications {\"automatedReportUuid\":\"4f6ca2b5-1993-48aa-99ad-b66f19f15d43\",\"recipientsCount\":1} {\"correlation_id\":\"f1d3e4df-9591-487f-ac2a-a43ddb89a466\",\"trace_id\":\"aa0f8702-73eb-4947-a765-2e89244599c3\"}\n[2026-04-27 10:26:27] local.INFO: [Send Report Not Generated Mail] Email sent {\"uuid\":\"cd5b5081-22f8-4d1a-9d6f-f82aca1121f6\",\"email\":\"lukas.kovalik@jiminny.com\"} {\"correlation_id\":\"4255983f-07a4-4872-9ea6-5491fa32bdd5\",\"trace_id\":\"aa0f8702-73eb-4947-a765-2e89244599c3\"}\n[2026-04-27 10:26:28] local.INFO: [Sync Mailbox] Sync start {\"inbox_id\":59} {\"correlation_id\":\"280d5bf6-c34c-45ea-bea7-05d9c028e9d3\",\"trace_id\":\"265be7c0-bf8f-4037-a0d2-57df3ef1d7e1\"}\n[2026-04-27 10:26:28] local.INFO: [Inbox service] Skipping METADATA SYNC for inbox 59 due to unauthorized access to the mailbox {\"correlation_id\":\"280d5bf6-c34c-45ea-bea7-05d9c028e9d3\",\"trace_id\":\"265be7c0-bf8f-4037-a0d2-57df3ef1d7e1\"}\n[2026-04-27 10:26:28] local.INFO: [Sync Mailbox] Sync complete {\"inbox_id\":59} {\"correlation_id\":\"280d5bf6-c34c-45ea-bea7-05d9c028e9d3\",\"trace_id\":\"265be7c0-bf8f-4037-a0d2-57df3ef1d7e1\"}\n[2026-04-27 10:26:28] local.INFO: [Sync Mailbox] Sync start {\"inbox_id\":212} {\"correlation_id\":\"d3e3eb3d-5c83-4d6e-9592-aaa38c1843a4\",\"trace_id\":\"265be7c0-bf8f-4037-a0d2-57df3ef1d7e1\"}\n[2026-04-27 10:26:28] local.INFO: [SocialAccountService] Fetching token {\"socialAccountId\":1500,\"provider\":\"salesforce\"} {\"correlation_id\":\"d3e3eb3d-5c83-4d6e-9592-aaa38c1843a4\",\"trace_id\":\"265be7c0-bf8f-4037-a0d2-57df3ef1d7e1\"}\n[2026-04-27 10:26:28] local.INFO: [SocialAccountService] Token retrieved {\"socialAccountId\":1500,\"provider\":\"salesforce\"} {\"correlation_id\":\"d3e3eb3d-5c83-4d6e-9592-aaa38c1843a4\",\"trace_id\":\"265be7c0-bf8f-4037-a0d2-57df3ef1d7e1\"}\n[2026-04-27 10:26:28] local.INFO: [EncryptedTokenManager] Generating access token. {\"mode\":\"legacy\"} {\"correlation_id\":\"d3e3eb3d-5c83-4d6e-9592-aaa38c1843a4\",\"trace_id\":\"265be7c0-bf8f-4037-a0d2-57df3ef1d7e1\"}\n[2026-04-27 10:26:28] local.INFO: [CrmOwnerResolver] Integration owner matched as CRM Owner {\"crm_provider\":\"salesforce\",\"crm_owner\":143,\"team_id\":1} {\"correlation_id\":\"d3e3eb3d-5c83-4d6e-9592-aaa38c1843a4\",\"trace_id\":\"265be7c0-bf8f-4037-a0d2-57df3ef1d7e1\"}\n[2026-04-27 10:26:28] local.INFO: [SocialAccountService] Fetching token {\"socialAccountId\":1354,\"provider\":\"google\"} {\"correlation_id\":\"d3e3eb3d-5c83-4d6e-9592-aaa38c1843a4\",\"trace_id\":\"265be7c0-bf8f-4037-a0d2-57df3ef1d7e1\"}\n[2026-04-27 10:26:28] local.INFO: [SocialAccountService] Token retrieved {\"socialAccountId\":1354,\"provider\":\"google\"} {\"correlation_id\":\"d3e3eb3d-5c83-4d6e-9592-aaa38c1843a4\",\"trace_id\":\"265be7c0-bf8f-4037-a0d2-57df3ef1d7e1\"}\n[2026-04-27 10:26:28] local.INFO: [EncryptedTokenManager] Generating access token. {\"mode\":\"legacy\"} {\"correlation_id\":\"d3e3eb3d-5c83-4d6e-9592-aaa38c1843a4\",\"trace_id\":\"265be7c0-bf8f-4037-a0d2-57df3ef1d7e1\"}\n[2026-04-27 10:26:28] local.INFO: [Gmail] Performing incremental sync for inbox 212 using history ID: @1777284288 {\"correlation_id\":\"d3e3eb3d-5c83-4d6e-9592-aaa38c1843a4\",\"trace_id\":\"265be7c0-bf8f-4037-a0d2-57df3ef1d7e1\"}\n[2026-04-27 10:26:28] local.INFO: [Gmail] imported 7 emails via full sync workflow for inbox 212 {\"correlation_id\":\"d3e3eb3d-5c83-4d6e-9592-aaa38c1843a4\",\"trace_id\":\"265be7c0-bf8f-4037-a0d2-57df3ef1d7e1\"}\n[2026-04-27 10:26:28] local.INFO: [Gmail] seeding inbox 212 with last message time : 2026-04-27 10:24:57 {\"correlation_id\":\"d3e3eb3d-5c83-4d6e-9592-aaa38c1843a4\",\"trace_id\":\"265be7c0-bf8f-4037-a0d2-57df3ef1d7e1\"}\n[2026-04-27 10:26:28] local.INFO: [Sync Mailbox] Sync complete {\"inbox_id\":212} {\"correlation_id\":\"d3e3eb3d-5c83-4d6e-9592-aaa38c1843a4\",\"trace_id\":\"265be7c0-bf8f-4037-a0d2-57df3ef1d7e1\"}\n[2026-04-27 10:27:04] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage before starting command {\"command\":\"meeting-bot:schedule-bot\",\"memoryBeforeCommandInMb\":60.0,\"memoryPeakBeforeCommandInMb\":99.727} {\"correlation_id\":\"f55fbf77-7e5f-4c87-accd-011697ca0fa3\",\"trace_id\":\"9bd1c431-26de-4e31-83d8-3129ed9b3b79\"}\n[2026-04-27 10:27:04] local.INFO: [ScheduleBotCommand] Number of activities to be captured: 0 {\"correlation_id\":\"f55fbf77-7e5f-4c87-accd-011697ca0fa3\",\"trace_id\":\"9bd1c431-26de-4e31-83d8-3129ed9b3b79\"}\n[2026-04-27 10:27:04] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage for command {\"command\":\"meeting-bot:schedule-bot\",\"memoryBeforeCommandInMb\":60.0,\"memoryAfterCommandInMB\":62.0,\"memoryPeakBeforeCommandInMb\":99.727,\"memoryPeakAfterCommandInMB\":99.727} {\"correlation_id\":\"f55fbf77-7e5f-4c87-accd-011697ca0fa3\",\"trace_id\":\"9bd1c431-26de-4e31-83d8-3129ed9b3b79\"}\n[2026-04-27 10:27:05] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage before starting command {\"command\":\"dialers:monitor-activities\",\"memoryBeforeCommandInMb\":60.0,\"memoryPeakBeforeCommandInMb\":99.727} {\"correlation_id\":\"da241053-5f3b-4d02-9504-4ef2987cdbd7\",\"trace_id\":\"364d3fc7-0f11-40c6-8697-d18eff2c4cb6\"}\n[2026-04-27 10:27:05] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage for command {\"command\":\"dialers:monitor-activities\",\"memoryBeforeCommandInMb\":60.0,\"memoryAfterCommandInMB\":62.0,\"memoryPeakBeforeCommandInMb\":99.727,\"memoryPeakAfterCommandInMB\":99.727} {\"correlation_id\":\"da241053-5f3b-4d02-9504-4ef2987cdbd7\",\"trace_id\":\"364d3fc7-0f11-40c6-8697-d18eff2c4cb6\"}\n[2026-04-27 10:27:06] local.NOTICE: Monitoring start {\"correlation_id\":\"ad0d871a-a1fd-4725-b2f7-ac8f0fc1857e\",\"trace_id\":\"cb504e84-d924-43a0-82d5-faf1840134ba\"}\n[2026-04-27 10:27:06] local.NOTICE: Monitoring end {\"correlation_id\":\"ad0d871a-a1fd-4725-b2f7-ac8f0fc1857e\",\"trace_id\":\"cb504e84-d924-43a0-82d5-faf1840134ba\"}\n[2026-04-27 10:27:08] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage before starting command {\"command\":\"mailbox:skip-lists:refresh\",\"memoryBeforeCommandInMb\":60.0,\"memoryPeakBeforeCommandInMb\":99.727} {\"correlation_id\":\"b8652f0d-1cee-44ae-a0ea-3d3137a882c1\",\"trace_id\":\"7fe19940-0813-4199-a2a8-958de09bd0ee\"}\n[2026-04-27 10:27:08] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage for command {\"command\":\"mailbox:skip-lists:refresh\",\"memoryBeforeCommandInMb\":60.0,\"memoryAfterCommandInMB\":62.0,\"memoryPeakBeforeCommandInMb\":99.727,\"memoryPeakAfterCommandInMB\":99.727} {\"correlation_id\":\"b8652f0d-1cee-44ae-a0ea-3d3137a882c1\",\"trace_id\":\"7fe19940-0813-4199-a2a8-958de09bd0ee\"}\n[2026-04-27 10:27:09] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage before starting command {\"command\":\"mailbox:batch:process\",\"memoryBeforeCommandInMb\":60.0,\"memoryPeakBeforeCommandInMb\":99.727} {\"correlation_id\":\"06190462-0f47-403f-8e95-ad5d05bff1fe\",\"trace_id\":\"4aa6bed6-05a1-4661-a9c9-edadc61d84ac\"}\n[2026-04-27 10:27:09] local.INFO: [EmailSchedule] STARTING batch process {\"host\":\"docker_lamp_1\"} {\"correlation_id\":\"06190462-0f47-403f-8e95-ad5d05bff1fe\",\"trace_id\":\"4aa6bed6-05a1-4661-a9c9-edadc61d84ac\"}\n[2026-04-27 10:27:09] local.INFO: [EmailSchedule] FINISHED batch process {\"host\":\"docker_lamp_1\",\"processed\":0} {\"correlation_id\":\"06190462-0f47-403f-8e95-ad5d05bff1fe\",\"trace_id\":\"4aa6bed6-05a1-4661-a9c9-edadc61d84ac\"}\n[2026-04-27 10:27:09] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage for command {\"command\":\"mailbox:batch:process\",\"memoryBeforeCommandInMb\":60.0,\"memoryAfterCommandInMB\":62.0,\"memoryPeakBeforeCommandInMb\":99.727,\"memoryPeakAfterCommandInMB\":99.727} {\"correlation_id\":\"06190462-0f47-403f-8e95-ad5d05bff1fe\",\"trace_id\":\"4aa6bed6-05a1-4661-a9c9-edadc61d84ac\"}\n[2026-04-27 10:27:11] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage before starting command {\"command\":\"mailbox:batch:create\",\"memoryBeforeCommandInMb\":60.0,\"memoryPeakBeforeCommandInMb\":99.727} {\"correlation_id\":\"b561c5d5-e6e3-4091-9517-b0458190a360\",\"trace_id\":\"bf9679fb-b785-40cb-b814-04a1dc385a1c\"}\n[2026-04-27 10:27:11] local.INFO: [EmailSchedule] STARTING batch create {\"host\":\"docker_lamp_1\"} {\"correlation_id\":\"b561c5d5-e6e3-4091-9517-b0458190a360\",\"trace_id\":\"bf9679fb-b785-40cb-b814-04a1dc385a1c\"}\n[2026-04-27 10:27:11] local.INFO: [EmailSchedule] FINISHED batch create {\"host\":\"docker_lamp_1\"} {\"correlation_id\":\"b561c5d5-e6e3-4091-9517-b0458190a360\",\"trace_id\":\"bf9679fb-b785-40cb-b814-04a1dc385a1c\"}\n[2026-04-27 10:27:11] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage for command {\"command\":\"mailbox:batch:create\",\"memoryBeforeCommandInMb\":60.0,\"memoryAfterCommandInMB\":62.0,\"memoryPeakBeforeCommandInMb\":99.727,\"memoryPeakAfterCommandInMB\":99.727} {\"correlation_id\":\"b561c5d5-e6e3-4091-9517-b0458190a360\",\"trace_id\":\"bf9679fb-b785-40cb-b814-04a1dc385a1c\"}\n[2026-04-27 10:27:14] local.INFO: [Jiminny\\Jobs\\Mailbox\\CreateBatches] processed 2 inboxes and created 1 batches {\"userId\":null,\"batchSize\":30,\"maxBatches\":1000} {\"correlation_id\":\"98416296-43c2-4834-927e-4a0e86dc99cf\",\"trace_id\":\"bf9679fb-b785-40cb-b814-04a1dc385a1c\"}\n[2026-04-27 10:28:04] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage before starting command {\"command\":\"meeting-bot:schedule-bot\",\"memoryBeforeCommandInMb\":60.0,\"memoryPeakBeforeCommandInMb\":99.727} {\"correlation_id\":\"5852bcf1-af92-4d24-8a2d-08a9d4a5cfac\",\"trace_id\":\"25af5e8c-a148-493e-ac9f-4d53f1960177\"}\n[2026-04-27 10:28:04] local.INFO: [ScheduleBotCommand] Number of activities to be captured: 0 {\"correlation_id\":\"5852bcf1-af92-4d24-8a2d-08a9d4a5cfac\",\"trace_id\":\"25af5e8c-a148-493e-ac9f-4d53f1960177\"}\n[2026-04-27 10:28:04] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage for command {\"command\":\"meeting-bot:schedule-bot\",\"memoryBeforeCommandInMb\":60.0,\"memoryAfterCommandInMB\":62.0,\"memoryPeakBeforeCommandInMb\":99.727,\"memoryPeakAfterCommandInMB\":99.727} {\"correlation_id\":\"5852bcf1-af92-4d24-8a2d-08a9d4a5cfac\",\"trace_id\":\"25af5e8c-a148-493e-ac9f-4d53f1960177\"}\n[2026-04-27 10:28:06] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage before starting command {\"command\":\"dialers:monitor-activities\",\"memoryBeforeCommandInMb\":60.0,\"memoryPeakBeforeCommandInMb\":99.727} {\"correlation_id\":\"cc1cff1f-71b4-4bb1-800f-66e27c0f2b3a\",\"trace_id\":\"c03103a3-9ab8-480b-9753-7c7c6488f246\"}\n[2026-04-27 10:28:06] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage for command {\"command\":\"dialers:monitor-activities\",\"memoryBeforeCommandInMb\":60.0,\"memoryAfterCommandInMB\":62.0,\"memoryPeakBeforeCommandInMb\":99.727,\"memoryPeakAfterCommandInMB\":99.727} {\"correlation_id\":\"cc1cff1f-71b4-4bb1-800f-66e27c0f2b3a\",\"trace_id\":\"c03103a3-9ab8-480b-9753-7c7c6488f246\"}\n[2026-04-27 10:28:09] local.ERROR: Target class [Jiminny\\Repositories\\AjReportsRepository] does not exist. {\"exception\":\"[object] (Illuminate\\\\Contracts\\\\Container\\\\BindingResolutionException(code: 0): Target class [Jiminny\\\\Repositories\\\\AjReportsRepository] does not exist. at /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php:1124)\n[stacktrace]\n#0 /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php(933): Illuminate\\\\Container\\\\Container->build('Jiminny\\\\\\\\Reposit...')\n#1 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Application.php(1078): Illuminate\\\\Container\\\\Container->resolve('Jiminny\\\\\\\\Reposit...', Array, true)\n#2 /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php(864): Illuminate\\\\Foundation\\\\Application->resolve('Jiminny\\\\\\\\Reposit...', Array)\n#3 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Application.php(1058): Illuminate\\\\Container\\\\Container->make('Jiminny\\\\\\\\Reposit...', Array)\n#4 /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php(1336): Illuminate\\\\Foundation\\\\Application->make('Jiminny\\\\\\\\Reposit...')\n#5 /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php(1237): Illuminate\\\\Container\\\\Container->resolveClass(Object(ReflectionParameter))\n#6 /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php(1162): Illuminate\\\\Container\\\\Container->resolveDependencies(Array)\n#7 /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php(933): Illuminate\\\\Container\\\\Container->build('Jiminny\\\\\\\\Http\\\\\\\\Co...')\n#8 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Application.php(1078): Illuminate\\\\Container\\\\Container->resolve('Jiminny\\\\\\\\Http\\\\\\\\Co...', Array, true)\n#9 /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php(864): Illuminate\\\\Foundation\\\\Application->resolve('Jiminny\\\\\\\\Http\\\\\\\\Co...', Array)\n#10 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Application.php(1058): Illuminate\\\\Container\\\\Container->make('Jiminny\\\\\\\\Http\\\\\\\\Co...', Array)\n#11 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Route.php(286): Illuminate\\\\Foundation\\\\Application->make('Jiminny\\\\\\\\Http\\\\\\\\Co...')\n#12 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Route.php(1133): Illuminate\\\\Routing\\\\Route->getController()\n#13 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Route.php(1062): Illuminate\\\\Routing\\\\Route->controllerMiddleware()\n#14 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Router.php(834): Illuminate\\\\Routing\\\\Route->gatherMiddleware()\n#15 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Router.php(816): Illuminate\\\\Routing\\\\Router->gatherRouteMiddleware(Object(Illuminate\\\\Routing\\\\Route))\n#16 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Router.php(800): Illuminate\\\\Routing\\\\Router->runRouteWithinStack(Object(Illuminate\\\\Routing\\\\Route), Object(Illuminate\\\\Http\\\\Request))\n#17 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Router.php(764): Illuminate\\\\Routing\\\\Router->runRoute(Object(Illuminate\\\\Http\\\\Request), Object(Illuminate\\\\Routing\\\\Route))\n#18 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Router.php(753): Illuminate\\\\Routing\\\\Router->dispatchToRoute(Object(Illuminate\\\\Http\\\\Request))\n#19 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Http/Kernel.php(200): Illuminate\\\\Routing\\\\Router->dispatch(Object(Illuminate\\\\Http\\\\Request))\n#20 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(180): Illuminate\\\\Foundation\\\\Http\\\\Kernel->Illuminate\\\\Foundation\\\\Http\\\\{closure}(Object(Illuminate\\\\Http\\\\Request))\n#21 /home/jiminny/vendor/barryvdh/laravel-debugbar/src/Middleware/InjectDebugbar.php(59): Illuminate\\\\Pipeline\\\\Pipeline->Illuminate\\\\Pipeline\\\\{closure}(Object(Illuminate\\\\Http\\\\Request))\n#22 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(219): Barryvdh\\\\Debugbar\\\\Middleware\\\\InjectDebugbar->handle(Object(Illuminate\\\\Http\\\\Request), Object(Closure))\n#23 /home/jiminny/vendor/laravel/framework/src/Illuminate/Http/Middleware/HandleCors.php(74): Illuminate\\\\Pipeline\\\\Pipeline->Illuminate\\\\Pipeline\\\\{closure}(Object(Illuminate\\\\Http\\\\Request))\n#24 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(219): Illuminate\\\\Http\\\\Middleware\\\\HandleCors->handle(Object(Illuminate\\\\Http\\\\Request), Object(Closure))\n#25 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Http/Middleware/TransformsRequest.php(21): Illuminate\\\\Pipeline\\\\Pipeline->Illuminate\\\\Pipeline\\\\{closure}(Object(Illuminate\\\\Http\\\\Request))\n#26 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Http/Middleware/TrimStrings.php(51): Illuminate\\\\Foundation\\\\Http\\\\Middleware\\\\TransformsRequest->handle(Object(Illuminate\\\\Http\\\\Request), Object(Closure))\n#27 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(219): Illuminate\\\\Foundation\\\\Http\\\\Middleware\\\\TrimStrings->handle(Object(Illuminate\\\\Http\\\\Request), Object(Closure))\n#28 /home/jiminny/vendor/bepsvpt/secure-headers/src/SecureHeadersMiddleware.php(18): Illuminate\\\\Pipeline\\\\Pipeline->Illuminate\\\\Pipeline\\\\{closure}(Object(Illuminate\\\\Http\\\\Request))\n#29 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(219): Bepsvpt\\\\SecureHeaders\\\\SecureHeadersMiddleware->handle(Object(Illuminate\\\\Http\\\\Request), Object(Closure))\n#30 /home/jiminny/app/Http/Middleware/SentryContext.php(60): Illuminate\\\\Pipeline\\\\Pipeline->Illuminate\\\\Pipeline\\\\{closure}(Object(Illuminate\\\\Http\\\\Request))\n#31 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(219): Jiminny\\\\Http\\\\Middleware\\\\SentryContext->handle(Object(Illuminate\\\\Http\\\\Request), Object(Closure))\n#32 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Http/Middleware/PreventRequestsDuringMaintenance.php(109): Illuminate\\\\Pipeline\\\\Pipeline->Illuminate\\\\Pipeline\\\\{closure}(Object(Illuminate\\\\Http\\\\Request))\n#33 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(219): Illuminate\\\\Foundation\\\\Http\\\\Middleware\\\\PreventRequestsDuringMaintenance->handle(Object(Illuminate\\\\Http\\\\Request), Object(Closure))\n#34 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Http/Middleware/InvokeDeferredCallbacks.php(22): Illuminate\\\\Pipeline\\\\Pipeline->Illuminate\\\\Pipeline\\\\{closure}(Object(Illuminate\\\\Http\\\\Request))\n#35 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(219): Illuminate\\\\Foundation\\\\Http\\\\Middleware\\\\InvokeDeferredCallbacks->handle(Object(Illuminate\\\\Http\\\\Request), Object(Closure))\n#36 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(137): Illuminate\\\\Pipeline\\\\Pipeline->Illuminate\\\\Pipeline\\\\{closure}(Object(Illuminate\\\\Http\\\\Request))\n#37 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Http/Kernel.php(175): Illuminate\\\\Pipeline\\\\Pipeline->then(Object(Closure))\n#38 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Http/Kernel.php(144): Illuminate\\\\Foundation\\\\Http\\\\Kernel->sendRequestThroughRouter(Object(Illuminate\\\\Http\\\\Request))\n#39 /home/jiminny/public/index.php(51): Illuminate\\\\Foundation\\\\Http\\\\Kernel->handle(Object(Illuminate\\\\Http\\\\Request))\n#40 {main}\n\n[previous exception] [object] (ReflectionException(code: -1): Class \\\"Jiminny\\\\Repositories\\\\AjReportsRepository\\\" does not exist at /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php:1122)\n[stacktrace]\n#0 /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php(1122): ReflectionClass->__construct('Jiminny\\\\\\\\Reposit...')\n#1 /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php(933): Illuminate\\\\Container\\\\Container->build('Jiminny\\\\\\\\Reposit...')\n#2 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Application.php(1078): Illuminate\\\\Container\\\\Container->resolve('Jiminny\\\\\\\\Reposit...', Array, true)\n#3 /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php(864): Illuminate\\\\Foundation\\\\Application->resolve('Jiminny\\\\\\\\Reposit...', Array)\n#4 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Application.php(1058): Illuminate\\\\Container\\\\Container->make('Jiminny\\\\\\\\Reposit...', Array)\n#5 /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php(1336): Illuminate\\\\Foundation\\\\Application->make('Jiminny\\\\\\\\Reposit...')\n#6 /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php(1237): Illuminate\\\\Container\\\\Container->resolveClass(Object(ReflectionParameter))\n#7 /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php(1162): Illuminate\\\\Container\\\\Container->resolveDependencies(Array)\n#8 /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php(933): Illuminate\\\\Container\\\\Container->build('Jiminny\\\\\\\\Http\\\\\\\\Co...')\n#9 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Application.php(1078): Illuminate\\\\Container\\\\Container->resolve('Jiminny\\\\\\\\Http\\\\\\\\Co...', Array, true)\n#10 /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php(864): Illuminate\\\\Foundation\\\\Application->resolve('Jiminny\\\\\\\\Http\\\\\\\\Co...', Array)\n#11 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Application.php(1058): Illuminate\\\\Container\\\\Container->make('Jiminny\\\\\\\\Http\\\\\\\\Co...', Array)\n#12 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Route.php(286): Illuminate\\\\Foundation\\\\Application->make('Jiminny\\\\\\\\Http\\\\\\\\Co...')\n#13 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Route.php(1133): Illuminate\\\\Routing\\\\Route->getController()\n#14 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Route.php(1062): Illuminate\\\\Routing\\\\Route->controllerMiddleware()\n#15 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Router.php(834): Illuminate\\\\Routing\\\\Route->gatherMiddleware()\n#16 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Router.php(816): Illuminate\\\\Routing\\\\Router->gatherRouteMiddleware(Object(Illuminate\\\\Routing\\\\Route))\n#17 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Router.php(800): Illuminate\\\\Routing\\\\Router->runRouteWithinStack(Object(Illuminate\\\\Routing\\\\Route), Object(Illuminate\\\\Http\\\\Request))\n#18 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Router.php(764): Illuminate\\\\Routing\\\\Router->runRoute(Object(Illuminate\\\\Http\\\\Request), Object(Illuminate\\\\Routing\\\\Route))\n#19 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Router.php(753): Illuminate\\\\Routing\\\\Router->dispatchToRoute(Object(Illuminate\\\\Http\\\\Request))\n#20 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Http/Kernel.php(200): Illuminate\\\\Routing\\\\Router->dispatch(Object(Illuminate\\\\Http\\\\Request))\n#21 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(180): Illuminate\\\\Foundation\\\\Http\\\\Kernel->Illuminate\\\\Foundation\\\\Http\\\\{closure}(Object(Illuminate\\\\Http\\\\Request))\n#22 /home/jiminny/vendor/barryvdh/laravel-debugbar/src/Middleware/InjectDebugbar.php(59): Illuminate\\\\Pipeline\\\\Pipeline->Illuminate\\\\Pipeline\\\\{closure}(Object(Illuminate\\\\Http\\\\Request))\n#23 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(219): Barryvdh\\\\Debugbar\\\\Middleware\\\\InjectDebugbar->handle(Object(Illuminate\\\\Http\\\\Request), Object(Closure))\n#24 /home/jiminny/vendor/laravel/framework/src/Illuminate/Http/Middleware/HandleCors.php(74): Illuminate\\\\Pipeline\\\\Pipeline->Illuminate\\\\Pipeline\\\\{closure}(Object(Illuminate\\\\Http\\\\Request))\n#25 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(219): Illuminate\\\\Http\\\\Middleware\\\\HandleCors->handle(Object(Illuminate\\\\Http\\\\Request), Object(Closure))\n#26 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Http/Middleware/TransformsRequest.php(21): Illuminate\\\\Pipeline\\\\Pipeline->Illuminate\\\\Pipeline\\\\{closure}(Object(Illuminate\\\\Http\\\\Request))\n#27 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Http/Middleware/TrimStrings.php(51): Illuminate\\\\Foundation\\\\Http\\\\Middleware\\\\TransformsRequest->handle(Object(Illuminate\\\\Http\\\\Request), Object(Closure))\n#28 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(219): Illuminate\\\\Foundation\\\\Http\\\\Middleware\\\\TrimStrings->handle(Object(Illuminate\\\\Http\\\\Request), Object(Closure))\n#29 /home/jiminny/vendor/bepsvpt/secure-headers/src/SecureHeadersMiddleware.php(18): Illuminate\\\\Pipeline\\\\Pipeline->Illuminate\\\\Pipeline\\\\{closure}(Object(Illuminate\\\\Http\\\\Request))\n#30 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(219): Bepsvpt\\\\SecureHeaders\\\\SecureHeadersMiddleware->handle(Object(Illuminate\\\\Http\\\\Request), Object(Closure))\n#31 /home/jiminny/app/Http/Middleware/SentryContext.php(60): Illuminate\\\\Pipeline\\\\Pipeline->Illuminate\\\\Pipeline\\\\{closure}(Object(Illuminate\\\\Http\\\\Request))\n#32 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(219): Jiminny\\\\Http\\\\Middleware\\\\SentryContext->handle(Object(Illuminate\\\\Http\\\\Request), Object(Closure))\n#33 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Http/Middleware/PreventRequestsDuringMaintenance.php(109): Illuminate\\\\Pipeline\\\\Pipeline->Illuminate\\\\Pipeline\\\\{closure}(Object(Illuminate\\\\Http\\\\Request))\n#34 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(219): Illuminate\\\\Foundation\\\\Http\\\\Middleware\\\\PreventRequestsDuringMaintenance->handle(Object(Illuminate\\\\Http\\\\Request), Object(Closure))\n#35 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Http/Middleware/InvokeDeferredCallbacks.php(22): Illuminate\\\\Pipeline\\\\Pipeline->Illuminate\\\\Pipeline\\\\{closure}(Object(Illuminate\\\\Http\\\\Request))\n#36 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(219): Illuminate\\\\Foundation\\\\Http\\\\Middleware\\\\InvokeDeferredCallbacks->handle(Object(Illuminate\\\\Http\\\\Request), Object(Closure))\n#37 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(137): Illuminate\\\\Pipeline\\\\Pipeline->Illuminate\\\\Pipeline\\\\{closure}(Object(Illuminate\\\\Http\\\\Request))\n#38 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Http/Kernel.php(175): Illuminate\\\\Pipeline\\\\Pipeline->then(Object(Closure))\n#39 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Http/Kernel.php(144): Illuminate\\\\Foundation\\\\Http\\\\Kernel->sendRequestThroughRouter(Object(Illuminate\\\\Http\\\\Request))\n#40 /home/jiminny/public/index.php(51): Illuminate\\\\Foundation\\\\Http\\\\Kernel->handle(Object(Illuminate\\\\Http\\\\Request))\n#41 {main}\n\"} {\"correlation_id\":\"dfad8817-1dde-4622-8320-f5fc1616a8fe\",\"trace_id\":\"d21232b2-60fb-4ca0-838c-ba42e61593f9\"}\n[2026-04-27 10:28:09] local.NOTICE: Monitoring start {\"correlation_id\":\"93744c67-4444-41ff-aaad-6aa9cafe294e\",\"trace_id\":\"80e396b0-b1ac-4ec8-838c-00021f98072c\"}\n[2026-04-27 10:28:09] local.NOTICE: Monitoring end {\"correlation_id\":\"93744c67-4444-41ff-aaad-6aa9cafe294e\",\"trace_id\":\"80e396b0-b1ac-4ec8-838c-00021f98072c\"}\n[2026-04-27 10:28:10] local.ERROR: Target class [Jiminny\\Repositories\\AjReportsRepository] does not exist. {\"exception\":\"[object] (Illuminate\\\\Contracts\\\\Container\\\\BindingResolutionException(code: 0): Target class [Jiminny\\\\Repositories\\\\AjReportsRepository] does not exist. at /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php:1124)\n[stacktrace]\n#0 /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php(933): Illuminate\\\\Container\\\\Container->build('Jiminny\\\\\\\\Reposit...')\n#1 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Application.php(1078): Illuminate\\\\Container\\\\Container->resolve('Jiminny\\\\\\\\Reposit...', Array, true)\n#2 /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php(864): Illuminate\\\\Foundation\\\\Application->resolve('Jiminny\\\\\\\\Reposit...', Array)\n#3 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Application.php(1058): Illuminate\\\\Container\\\\Container->make('Jiminny\\\\\\\\Reposit...', Array)\n#4 /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php(1336): Illuminate\\\\Foundation\\\\Application->make('Jiminny\\\\\\\\Reposit...')\n#5 /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php(1237): Illuminate\\\\Container\\\\Container->resolveClass(Object(ReflectionParameter))\n#6 /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php(1162): Illuminate\\\\Container\\\\Container->resolveDependencies(Array)\n#7 /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php(933): Illuminate\\\\Container\\\\Container->build('Jiminny\\\\\\\\Http\\\\\\\\Co...')\n#8 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Application.php(1078): Illuminate\\\\Container\\\\Container->resolve('Jiminny\\\\\\\\Http\\\\\\\\Co...', Array, true)\n#9 /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php(864): Illuminate\\\\Foundation\\\\Application->resolve('Jiminny\\\\\\\\Http\\\\\\\\Co...', Array)\n#10 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Application.php(1058): Illuminate\\\\Container\\\\Container->make('Jiminny\\\\\\\\Http\\\\\\\\Co...', Array)\n#11 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Route.php(286): Illuminate\\\\Foundation\\\\Application->make('Jiminny\\\\\\\\Http\\\\\\\\Co...')\n#12 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Route.php(1133): Illuminate\\\\Routing\\\\Route->getController()\n#13 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Route.php(1062): Illuminate\\\\Routing\\\\Route->controllerMiddleware()\n#14 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Router.php(834): Illuminate\\\\Routing\\\\Route->gatherMiddleware()\n#15 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Router.php(816): Illuminate\\\\Routing\\\\Router->gatherRouteMiddleware(Object(Illuminate\\\\Routing\\\\Route))\n#16 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Router.php(800): Illuminate\\\\Routing\\\\Router->runRouteWithinStack(Object(Illuminate\\\\Routing\\\\Route), Object(Illuminate\\\\Http\\\\Request))\n#17 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Router.php(764): Illuminate\\\\Routing\\\\Router->runRoute(Object(Illuminate\\\\Http\\\\Request), Object(Illuminate\\\\Routing\\\\Route))\n#18 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Router.php(753): Illuminate\\\\Routing\\\\Router->dispatchToRoute(Object(Illuminate\\\\Http\\\\Request))\n#19 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Http/Kernel.php(200): Illuminate\\\\Routing\\\\Router->dispatch(Object(Illuminate\\\\Http\\\\Request))\n#20 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(180): Illuminate\\\\Foundation\\\\Http\\\\Kernel->Illuminate\\\\Foundation\\\\Http\\\\{closure}(Object(Illuminate\\\\Http\\\\Request))\n#21 /home/jiminny/vendor/barryvdh/laravel-debugbar/src/Middleware/InjectDebugbar.php(59): Illuminate\\\\Pipeline\\\\Pipeline->Illuminate\\\\Pipeline\\\\{closure}(Object(Illuminate\\\\Http\\\\Request))\n#22 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(219): Barryvdh\\\\Debugbar\\\\Middleware\\\\InjectDebugbar->handle(Object(Illuminate\\\\Http\\\\Request), Object(Closure))\n#23 /home/jiminny/vendor/laravel/framework/src/Illuminate/Http/Middleware/HandleCors.php(74): Illuminate\\\\Pipeline\\\\Pipeline->Illuminate\\\\Pipeline\\\\{closure}(Object(Illuminate\\\\Http\\\\Request))\n#24 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(219): Illuminate\\\\Http\\\\Middleware\\\\HandleCors->handle(Object(Illuminate\\\\Http\\\\Request), Object(Closure))\n#25 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Http/Middleware/TransformsRequest.php(21): Illuminate\\\\Pipeline\\\\Pipeline->Illuminate\\\\Pipeline\\\\{closure}(Object(Illuminate\\\\Http\\\\Request))\n#26 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Http/Middleware/TrimStrings.php(51): Illuminate\\\\Foundation\\\\Http\\\\Middleware\\\\TransformsRequest->handle(Object(Illuminate\\\\Http\\\\Request), Object(Closure))\n#27 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(219): Illuminate\\\\Foundation\\\\Http\\\\Middleware\\\\TrimStrings->handle(Object(Illuminate\\\\Http\\\\Request), Object(Closure))\n#28 /home/jiminny/vendor/bepsvpt/secure-headers/src/SecureHeadersMiddleware.php(18): Illuminate\\\\Pipeline\\\\Pipeline->Illuminate\\\\Pipeline\\\\{closure}(Object(Illuminate\\\\Http\\\\Request))\n#29 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(219): Bepsvpt\\\\SecureHeaders\\\\SecureHeadersMiddleware->handle(Object(Illuminate\\\\Http\\\\Request), Object(Closure))\n#30 /home/jiminny/app/Http/Middleware/SentryContext.php(60): Illuminate\\\\Pipeline\\\\Pipeline->Illuminate\\\\Pipeline\\\\{closure}(Object(Illuminate\\\\Http\\\\Request))\n#31 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(219): Jiminny\\\\Http\\\\Middleware\\\\SentryContext->handle(Object(Illuminate\\\\Http\\\\Request), Object(Closure))\n#32 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Http/Middleware/PreventRequestsDuringMaintenance.php(109): Illuminate\\\\Pipeline\\\\Pipeline->Illuminate\\\\Pipeline\\\\{closure}(Object(Illuminate\\\\Http\\\\Request))\n#33 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(219): Illuminate\\\\Foundation\\\\Http\\\\Middleware\\\\PreventRequestsDuringMaintenance->handle(Object(Illuminate\\\\Http\\\\Request), Object(Closure))\n#34 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Http/Middleware/InvokeDeferredCallbacks.php(22): Illuminate\\\\Pipeline\\\\Pipeline->Illuminate\\\\Pipeline\\\\{closure}(Object(Illuminate\\\\Http\\\\Request))\n#35 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(219): Illuminate\\\\Foundation\\\\Http\\\\Middleware\\\\InvokeDeferredCallbacks->handle(Object(Illuminate\\\\Http\\\\Request), Object(Closure))\n#36 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(137): Illuminate\\\\Pipeline\\\\Pipeline->Illuminate\\\\Pipeline\\\\{closure}(Object(Illuminate\\\\Http\\\\Request))\n#37 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Http/Kernel.php(175): Illuminate\\\\Pipeline\\\\Pipeline->then(Object(Closure))\n#38 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Http/Kernel.php(144): Illuminate\\\\Foundation\\\\Http\\\\Kernel->sendRequestThroughRouter(Object(Illuminate\\\\Http\\\\Request))\n#39 /home/jiminny/public/index.php(51): Illuminate\\\\Foundation\\\\Http\\\\Kernel->handle(Object(Illuminate\\\\Http\\\\Request))\n#40 {main}\n\n[previous exception] [object] (ReflectionException(code: -1): Class \\\"Jiminny\\\\Repositories\\\\AjReportsRepository\\\" does not exist at /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php:1122)\n[stacktrace]\n#0 /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php(1122): ReflectionClass->__construct('Jiminny\\\\\\\\Reposit...')\n#1 /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php(933): Illuminate\\\\Container\\\\Container->build('Jiminny\\\\\\\\Reposit...')\n#2 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Application.php(1078): Illuminate\\\\Container\\\\Container->resolve('Jiminny\\\\\\\\Reposit...', Array, true)\n#3 /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php(864): Illuminate\\\\Foundation\\\\Application->resolve('Jiminny\\\\\\\\Reposit...', Array)\n#4 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Application.php(1058): Illuminate\\\\Container\\\\Container->make('Jiminny\\\\\\\\Reposit...', Array)\n#5 /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php(1336): Illuminate\\\\Foundation\\\\Application->make('Jiminny\\\\\\\\Reposit...')\n#6 /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php(1237): Illuminate\\\\Container\\\\Container->resolveClass(Object(ReflectionParameter))\n#7 /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php(1162): Illuminate\\\\Container\\\\Container->resolveDependencies(Array)\n#8 /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php(933): Illuminate\\\\Container\\\\Container->build('Jiminny\\\\\\\\Http\\\\\\\\Co...')\n#9 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Application.php(1078): Illuminate\\\\Container\\\\Container->resolve('Jiminny\\\\\\\\Http\\\\\\\\Co...', Array, true)\n#10 /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php(864): Illuminate\\\\Foundation\\\\Application->resolve('Jiminny\\\\\\\\Http\\\\\\\\Co...', Array)\n#11 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Application.php(1058): Illuminate\\\\Container\\\\Container->make('Jiminny\\\\\\\\Http\\\\\\\\Co...', Array)\n#12 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Route.php(286): Illuminate\\\\Foundation\\\\Application->make('Jiminny\\\\\\\\Http\\\\\\\\Co...')\n#13 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Route.php(1133): Illuminate\\\\Routing\\\\Route->getController()\n#14 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Route.php(1062): Illuminate\\\\Routing\\\\Route->controllerMiddleware()\n#15 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Router.php(834): Illuminate\\\\Routing\\\\Route->gatherMiddleware()\n#16 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Router.php(816): Illuminate\\\\Routing\\\\Router->gatherRouteMiddleware(Object(Illuminate\\\\Routing\\\\Route))\n#17 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Router.php(800): Illuminate\\\\Routing\\\\Router->runRouteWithinStack(Object(Illuminate\\\\Routing\\\\Route), Object(Illuminate\\\\Http\\\\Request))\n#18 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Router.php(764): Illuminate\\\\Routing\\\\Router->runRoute(Object(Illuminate\\\\Http\\\\Request), Object(Illuminate\\\\Routing\\\\Route))\n#19 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Router.php(753): Illuminate\\\\Routing\\\\Router->dispatchToRoute(Object(Illuminate\\\\Http\\\\Request))\n#20 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Http/Kernel.php(200): Illuminate\\\\Routing\\\\Router->dispatch(Object(Illuminate\\\\Http\\\\Request))\n#21 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(180): Illuminate\\\\Foundation\\\\Http\\\\Kernel->Illuminate\\\\Foundation\\\\Http\\\\{closure}(Object(Illuminate\\\\Http\\\\Request))\n#22 /home/jiminny/vendor/barryvdh/laravel-debugbar/src/Middleware/InjectDebugbar.php(59): Illuminate\\\\Pipeline\\\\Pipeline->Illuminate\\\\Pipeline\\\\{closure}(Object(Illuminate\\\\Http\\\\Request))\n#23 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(219): Barryvdh\\\\Debugbar\\\\Middleware\\\\InjectDebugbar->handle(Object(Illuminate\\\\Http\\\\Request), Object(Closure))\n#24 /home/jiminny/vendor/laravel/framework/src/Illuminate/Http/Middleware/HandleCors.php(74): Illuminate\\\\Pipeline\\\\Pipeline->Illuminate\\\\Pipeline\\\\{closure}(Object(Illuminate\\\\Http\\\\Request))\n#25 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(219): Illuminate\\\\Http\\\\Middleware\\\\HandleCors->handle(Object(Illuminate\\\\Http\\\\Request), Object(Closure))\n#26 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Http/Middleware/TransformsRequest.php(21): Illuminate\\\\Pipeline\\\\Pipeline->Illuminate\\\\Pipeline\\\\{closure}(Object(Illuminate\\\\Http\\\\Request))\n#27 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Http/Middleware/TrimStrings.php(51): Illuminate\\\\Foundation\\\\Http\\\\Middleware\\\\TransformsRequest->handle(Object(Illuminate\\\\Http\\\\Request), Object(Closure))\n#28 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(219): Illuminate\\\\Foundation\\\\Http\\\\Middleware\\\\TrimStrings->handle(Object(Illuminate\\\\Http\\\\Request), Object(Closure))\n#29 /home/jiminny/vendor/bepsvpt/secure-headers/src/SecureHeadersMiddleware.php(18): Illuminate\\\\Pipeline\\\\Pipeline->Illuminate\\\\Pipeline\\\\{closure}(Object(Illuminate\\\\Http\\\\Request))\n#30 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(219): Bepsvpt\\\\SecureHeaders\\\\SecureHeadersMiddleware->handle(Object(Illuminate\\\\Http\\\\Request), Object(Closure))\n#31 /home/jiminny/app/Http/Middleware/SentryContext.php(60): Illuminate\\\\Pipeline\\\\Pipeline->Illuminate\\\\Pipeline\\\\{closure}(Object(Illuminate\\\\Http\\\\Request))\n#32 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(219): Jiminny\\\\Http\\\\Middleware\\\\SentryContext->handle(Object(Illuminate\\\\Http\\\\Request), Object(Closure))\n#33 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Http/Middleware/PreventRequestsDuringMaintenance.php(109): Illuminate\\\\Pipeline\\\\Pipeline->Illuminate\\\\Pipeline\\\\{closure}(Object(Illuminate\\\\Http\\\\Request))\n#34 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(219): Illuminate\\\\Foundation\\\\Http\\\\Middleware\\\\PreventRequestsDuringMaintenance->handle(Object(Illuminate\\\\Http\\\\Request), Object(Closure))\n#35 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Http/Middleware/InvokeDeferredCallbacks.php(22): Illuminate\\\\Pipeline\\\\Pipeline->Illuminate\\\\Pipeline\\\\{closure}(Object(Illuminate\\\\Http\\\\Request))\n#36 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(219): Illuminate\\\\Foundation\\\\Http\\\\Middleware\\\\InvokeDeferredCallbacks->handle(Object(Illuminate\\\\Http\\\\Request), Object(Closure))\n#37 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(137): Illuminate\\\\Pipeline\\\\Pipeline->Illuminate\\\\Pipeline\\\\{closure}(Object(Illuminate\\\\Http\\\\Request))\n#38 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Http/Kernel.php(175): Illuminate\\\\Pipeline\\\\Pipeline->then(Object(Closure))\n#39 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Http/Kernel.php(144): Illuminate\\\\Foundation\\\\Http\\\\Kernel->sendRequestThroughRouter(Object(Illuminate\\\\Http\\\\Request))\n#40 /home/jiminny/public/index.php(51): Illuminate\\\\Foundation\\\\Http\\\\Kernel->handle(Object(Illuminate\\\\Http\\\\Request))\n#41 {main}\n\"} {\"correlation_id\":\"de768781-5c05-418c-baf3-79f77a8c8694\",\"trace_id\":\"fec74062-3f6d-4cfc-968a-1bb285496db3\"}\n[2026-04-27 10:28:12] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage before starting command {\"command\":\"mailbox:skip-lists:refresh\",\"memoryBeforeCommandInMb\":60.0,\"memoryPeakBeforeCommandInMb\":99.727} {\"correlation_id\":\"907d15ed-8c84-41a1-a473-590eebb55dbc\",\"trace_id\":\"ec7bd353-516e-491d-89dd-de44136c1672\"}\n[2026-04-27 10:28:12] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage for command {\"command\":\"mailbox:skip-lists:refresh\",\"memoryBeforeCommandInMb\":60.0,\"memoryAfterCommandInMB\":62.0,\"memoryPeakBeforeCommandInMb\":99.727,\"memoryPeakAfterCommandInMB\":99.727} {\"correlation_id\":\"907d15ed-8c84-41a1-a473-590eebb55dbc\",\"trace_id\":\"ec7bd353-516e-491d-89dd-de44136c1672\"}\n[2026-04-27 10:28:13] local.ERROR: Target class [Jiminny\\Repositories\\AjReportsRepository] does not exist. {\"exception\":\"[object] (Illuminate\\\\Contracts\\\\Container\\\\BindingResolutionException(code: 0): Target class [Jiminny\\\\Repositories\\\\AjReportsRepository] does not exist. at /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php:1124)\n[stacktrace]\n#0 /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php(933): Illuminate\\\\Container\\\\Container->build('Jiminny\\\\\\\\Reposit...')\n#1 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Application.php(1078): Illuminate\\\\Container\\\\Container->resolve('Jiminny\\\\\\\\Reposit...', Array, true)\n#2 /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php(864): Illuminate\\\\Foundation\\\\Application->resolve('Jiminny\\\\\\\\Reposit...', Array)\n#3 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Application.php(1058): Illuminate\\\\Container\\\\Container->make('Jiminny\\\\\\\\Reposit...', Array)\n#4 /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php(1336): Illuminate\\\\Foundation\\\\Application->make('Jiminny\\\\\\\\Reposit...')\n#5 /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php(1237): Illuminate\\\\Container\\\\Container->resolveClass(Object(ReflectionParameter))\n#6 /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php(1162): Illuminate\\\\Container\\\\Container->resolveDependencies(Array)\n#7 /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php(933): Illuminate\\\\Container\\\\Container->build('Jiminny\\\\\\\\Http\\\\\\\\Co...')\n#8 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Application.php(1078): Illuminate\\\\Container\\\\Container->resolve('Jiminny\\\\\\\\Http\\\\\\\\Co...', Array, true)\n#9 /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php(864): Illuminate\\\\Foundation\\\\Application->resolve('Jiminny\\\\\\\\Http\\\\\\\\Co...', Array)\n#10 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Application.php(1058): Illuminate\\\\Container\\\\Container->make('Jiminny\\\\\\\\Http\\\\\\\\Co...', Array)\n#11 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Route.php(286): Illuminate\\\\Foundation\\\\Application->make('Jiminny\\\\\\\\Http\\\\\\\\Co...')\n#12 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Route.php(1133): Illuminate\\\\Routing\\\\Route->getController()\n#13 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Route.php(1062): Illuminate\\\\Routing\\\\Route->controllerMiddleware()\n#14 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Router.php(834): Illuminate\\\\Routing\\\\Route->gatherMiddleware()\n#15 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Router.php(816): Illuminate\\\\Routing\\\\Router->gatherRouteMiddleware(Object(Illuminate\\\\Routing\\\\Route))\n#16 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Router.php(800): Illuminate\\\\Routing\\\\Router->runRouteWithinStack(Object(Illuminate\\\\Routing\\\\Route), Object(Illuminate\\\\Http\\\\Request))\n#17 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Router.php(764): Illuminate\\\\Routing\\\\Router->runRoute(Object(Illuminate\\\\Http\\\\Request), Object(Illuminate\\\\Routing\\\\Route))\n#18 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Router.php(753): Illuminate\\\\Routing\\\\Router->dispatchToRoute(Object(Illuminate\\\\Http\\\\Request))\n#19 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Http/Kernel.php(200): Illuminate\\\\Routing\\\\Router->dispatch(Object(Illuminate\\\\Http\\\\Request))\n#20 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(180): Illuminate\\\\Foundation\\\\Http\\\\Kernel->Illuminate\\\\Foundation\\\\Http\\\\{closure}(Object(Illuminate\\\\Http\\\\Request))\n#21 /home/jiminny/vendor/barryvdh/laravel-debugbar/src/Middleware/InjectDebugbar.php(59): Illuminate\\\\Pipeline\\\\Pipeline->Illuminate\\\\Pipeline\\\\{closure}(Object(Illuminate\\\\Http\\\\Request))\n#22 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(219): Barryvdh\\\\Debugbar\\\\Middleware\\\\InjectDebugbar->handle(Object(Illuminate\\\\Http\\\\Request), Object(Closure))\n#23 /home/jiminny/vendor/laravel/framework/src/Illuminate/Http/Middleware/HandleCors.php(74): Illuminate\\\\Pipeline\\\\Pipeline->Illuminate\\\\Pipeline\\\\{closure}(Object(Illuminate\\\\Http\\\\Request))\n#24 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(219): Illuminate\\\\Http\\\\Middleware\\\\HandleCors->handle(Object(Illuminate\\\\Http\\\\Request), Object(Closure))\n#25 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Http/Middleware/TransformsRequest.php(21): Illuminate\\\\Pipeline\\\\Pipeline->Illuminate\\\\Pipeline\\\\{closure}(Object(Illuminate\\\\Http\\\\Request))\n#26 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Http/Middleware/TrimStrings.php(51): Illuminate\\\\Foundation\\\\Http\\\\Middleware\\\\TransformsRequest->handle(Object(Illuminate\\\\Http\\\\Request), Object(Closure))\n#27 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(219): Illuminate\\\\Foundation\\\\Http\\\\Middleware\\\\TrimStrings->handle(Object(Illuminate\\\\Http\\\\Request), Object(Closure))\n#28 /home/jiminny/vendor/bepsvpt/secure-headers/src/SecureHeadersMiddleware.php(18): Illuminate\\\\Pipeline\\\\Pipeline->Illuminate\\\\Pipeline\\\\{closure}(Object(Illuminate\\\\Http\\\\Request))\n#29 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(219): Bepsvpt\\\\SecureHeaders\\\\SecureHeadersMiddleware->handle(Object(Illuminate\\\\Http\\\\Request), Object(Closure))\n#30 /home/jiminny/app/Http/Middleware/SentryContext.php(60): Illuminate\\\\Pipeline\\\\Pipeline->Illuminate\\\\Pipeline\\\\{closure}(Object(Illuminate\\\\Http\\\\Request))\n#31 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(219): Jiminny\\\\Http\\\\Middleware\\\\SentryContext->handle(Object(Illuminate\\\\Http\\\\Request), Object(Closure))\n#32 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Http/Middleware/PreventRequestsDuringMaintenance.php(109): Illuminate\\\\Pipeline\\\\Pipeline->Illuminate\\\\Pipeline\\\\{closure}(Object(Illuminate\\\\Http\\\\Request))\n#33 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(219): Illuminate\\\\Foundation\\\\Http\\\\Middleware\\\\PreventRequestsDuringMaintenance->handle(Object(Illuminate\\\\Http\\\\Request), Object(Closure))\n#34 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Http/Middleware/InvokeDeferredCallbacks.php(22): Illuminate\\\\Pipeline\\\\Pipeline->Illuminate\\\\Pipeline\\\\{closure}(Object(Illuminate\\\\Http\\\\Request))\n#35 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(219): Illuminate\\\\Foundation\\\\Http\\\\Middleware\\\\InvokeDeferredCallbacks->handle(Object(Illuminate\\\\Http\\\\Request), Object(Closure))\n#36 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(137): Illuminate\\\\Pipeline\\\\Pipeline->Illuminate\\\\Pipeline\\\\{closure}(Object(Illuminate\\\\Http\\\\Request))\n#37 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Http/Kernel.php(175): Illuminate\\\\Pipeline\\\\Pipeline->then(Object(Closure))\n#38 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Http/Kernel.php(144): Illuminate\\\\Foundation\\\\Http\\\\Kernel->sendRequestThroughRouter(Object(Illuminate\\\\Http\\\\Request))\n#39 /home/jiminny/public/index.php(51): Illuminate\\\\Foundation\\\\Http\\\\Kernel->handle(Object(Illuminate\\\\Http\\\\Request))\n#40 {main}\n\n[previous exception] [object] (ReflectionException(code: -1): Class \\\"Jiminny\\\\Repositories\\\\AjReportsRepository\\\" does not exist at /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php:1122)\n[stacktrace]\n#0 /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php(1122): ReflectionClass->__construct('Jiminny\\\\\\\\Reposit...')\n#1 /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php(933): Illuminate\\\\Container\\\\Container->build('Jiminny\\\\\\\\Reposit...')\n#2 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Application.php(1078): Illuminate\\\\Container\\\\Container->resolve('Jiminny\\\\\\\\Reposit...', Array, true)\n#3 /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php(864): Illuminate\\\\Foundation\\\\Application->resolve('Jiminny\\\\\\\\Reposit...', Array)\n#4 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Application.php(1058): Illuminate\\\\Container\\\\Container->make('Jiminny\\\\\\\\Reposit...', Array)\n#5 /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php(1336): Illuminate\\\\Foundation\\\\Application->make('Jiminny\\\\\\\\Reposit...')\n#6 /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php(1237): Illuminate\\\\Container\\\\Container->resolveClass(Object(ReflectionParameter))\n#7 /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php(1162): Illuminate\\\\Container\\\\Container->resolveDependencies(Array)\n#8 /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php(933): Illuminate\\\\Container\\\\Container->build('Jiminny\\\\\\\\Http\\\\\\\\Co...')\n#9 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Application.php(1078): Illuminate\\\\Container\\\\Container->resolve('Jiminny\\\\\\\\Http\\\\\\\\Co...', Array, true)\n#10 /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php(864): Illuminate\\\\Foundation\\\\Application->resolve('Jiminny\\\\\\\\Http\\\\\\\\Co...', Array)\n#11 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Application.php(1058): Illuminate\\\\Container\\\\Container->make('Jiminny\\\\\\\\Http\\\\\\\\Co...', Array)\n#12 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Route.php(286): Illuminate\\\\Foundation\\\\Application->make('Jiminny\\\\\\\\Http\\\\\\\\Co...')\n#13 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Route.php(1133): Illuminate\\\\Routing\\\\Route->getController()\n#14 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Route.php(1062): Illuminate\\\\Routing\\\\Route->controllerMiddleware()\n#15 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Router.php(834): Illuminate\\\\Routing\\\\Route->gatherMiddleware()\n#16 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Router.php(816): Illuminate\\\\Routing\\\\Router->gatherRouteMiddleware(Object(Illuminate\\\\Routing\\\\Route))\n#17 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Router.php(800): Illuminate\\\\Routing\\\\Router->runRouteWithinStack(Object(Illuminate\\\\Routing\\\\Route), Object(Illuminate\\\\Http\\\\Request))\n#18 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Router.php(764): Illuminate\\\\Routing\\\\Router->runRoute(Object(Illuminate\\\\Http\\\\Request), Object(Illuminate\\\\Routing\\\\Route))\n#19 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Router.php(753): Illuminate\\\\Routing\\\\Router->dispatchToRoute(Object(Illuminate\\\\Http\\\\Request))\n#20 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Http/Kernel.php(200): Illuminate\\\\Routing\\\\Router->dispatch(Object(Illuminate\\\\Http\\\\Request))\n#21 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(180): Illuminate\\\\Foundation\\\\Http\\\\Kernel->Illuminate\\\\Foundation\\\\Http\\\\{closure}(Object(Illuminate\\\\Http\\\\Request))\n#22 /home/jiminny/vendor/barryvdh/laravel-debugbar/src/Middleware/InjectDebugbar.php(59): Illuminate\\\\Pipeline\\\\Pipeline->Illuminate\\\\Pipeline\\\\{closure}(Object(Illuminate\\\\Http\\\\Request))\n#23 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(219): Barryvdh\\\\Debugbar\\\\Middleware\\\\InjectDebugbar->handle(Object(Illuminate\\\\Http\\\\Request), Object(Closure))\n#24 /home/jiminny/vendor/laravel/framework/src/Illuminate/Http/Middleware/HandleCors.php(74): Illuminate\\\\Pipeline\\\\Pipeline->Illuminate\\\\Pipeline\\\\{closure}(Object(Illuminate\\\\Http\\\\Request))\n#25 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(219): Illuminate\\\\Http\\\\Middleware\\\\HandleCors->handle(Object(Illuminate\\\\Http\\\\Request), Object(Closure))\n#26 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Http/Middleware/TransformsRequest.php(21): Illuminate\\\\Pipeline\\\\Pipeline->Illuminate\\\\Pipeline\\\\{closure}(Object(Illuminate\\\\Http\\\\Request))\n#27 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Http/Middleware/TrimStrings.php(51): Illuminate\\\\Foundation\\\\Http\\\\Middleware\\\\TransformsRequest->handle(Object(Illuminate\\\\Http\\\\Request), Object(Closure))\n#28 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(219): Illuminate\\\\Foundation\\\\Http\\\\Middleware\\\\TrimStrings->handle(Object(Illuminate\\\\Http\\\\Request), Object(Closure))\n#29 /home/jiminny/vendor/bepsvpt/secure-headers/src/SecureHeadersMiddleware.php(18): Illuminate\\\\Pipeline\\\\Pipeline->Illuminate\\\\Pipeline\\\\{closure}(Object(Illuminate\\\\Http\\\\Request))\n#30 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(219): Bepsvpt\\\\SecureHeaders\\\\SecureHeadersMiddleware->handle(Object(Illuminate\\\\Http\\\\Request), Object(Closure))\n#31 /home/jiminny/app/Http/Middleware/SentryContext.php(60): Illuminate\\\\Pipeline\\\\Pipeline->Illuminate\\\\Pipeline\\\\{closure}(Object(Illuminate\\\\Http\\\\Request))\n#32 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(219): Jiminny\\\\Http\\\\Middleware\\\\SentryContext->handle(Object(Illuminate\\\\Http\\\\Request), Object(Closure))\n#33 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Http/Middleware/PreventRequestsDuringMaintenance.php(109): Illuminate\\\\Pipeline\\\\Pipeline->Illuminate\\\\Pipeline\\\\{closure}(Object(Illuminate\\\\Http\\\\Request))\n#34 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(219): Illuminate\\\\Foundation\\\\Http\\\\Middleware\\\\PreventRequestsDuringMaintenance->handle(Object(Illuminate\\\\Http\\\\Request), Object(Closure))\n#35 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Http/Middleware/InvokeDeferredCallbacks.php(22): Illuminate\\\\Pipeline\\\\Pipeline->Illuminate\\\\Pipeline\\\\{closure}(Object(Illuminate\\\\Http\\\\Request))\n#36 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(219): Illuminate\\\\Foundation\\\\Http\\\\Middleware\\\\InvokeDeferredCallbacks->handle(Object(Illuminate\\\\Http\\\\Request), Object(Closure))\n#37 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(137): Illuminate\\\\Pipeline\\\\Pipeline->Illuminate\\\\Pipeline\\\\{closure}(Object(Illuminate\\\\Http\\\\Request))\n#38 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Http/Kernel.php(175): Illuminate\\\\Pipeline\\\\Pipeline->then(Object(Closure))\n#39 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Http/Kernel.php(144): Illuminate\\\\Foundation\\\\Http\\\\Kernel->sendRequestThroughRouter(Object(Illuminate\\\\Http\\\\Request))\n#40 /home/jiminny/public/index.php(51): Illuminate\\\\Foundation\\\\Http\\\\Kernel->handle(Object(Illuminate\\\\Http\\\\Request))\n#41 {main}\n\"} {\"correlation_id\":\"d8709bb9-0f8c-4798-a8c7-e68e24ab6265\",\"trace_id\":\"c5bea1cc-53fe-4bda-a8e2-bec002030bc2\"}\n[2026-04-27 10:28:14] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage before starting command {\"command\":\"mailbox:batch:process\",\"memoryBeforeCommandInMb\":60.0,\"memoryPeakBeforeCommandInMb\":99.727} {\"correlation_id\":\"5fb3c8c8-3160-4964-ac56-02d6e2cb998c\",\"trace_id\":\"3a6d5e64-ecf2-4700-98cd-3edd56a1dc34\"}\n[2026-04-27 10:28:14] local.INFO: [EmailSchedule] STARTING batch process {\"host\":\"docker_lamp_1\"} {\"correlation_id\":\"5fb3c8c8-3160-4964-ac56-02d6e2cb998c\",\"trace_id\":\"3a6d5e64-ecf2-4700-98cd-3edd56a1dc34\"}\n[2026-04-27 10:28:14] local.INFO: Processing email batch 98446 for inbox 212 {\"correlation_id\":\"5fb3c8c8-3160-4964-ac56-02d6e2cb998c\",\"trace_id\":\"3a6d5e64-ecf2-4700-98cd-3edd56a1dc34\"}\n[2026-04-27 10:28:14] local.INFO: [SocialAccountService] Fetching token {\"socialAccountId\":1500,\"provider\":\"salesforce\"} {\"correlation_id\":\"5fb3c8c8-3160-4964-ac56-02d6e2cb998c\",\"trace_id\":\"3a6d5e64-ecf2-4700-98cd-3edd56a1dc34\"}\n[2026-04-27 10:28:14] local.INFO: [SocialAccountService] Token retrieved {\"socialAccountId\":1500,\"provider\":\"salesforce\"} {\"correlation_id\":\"5fb3c8c8-3160-4964-ac56-02d6e2cb998c\",\"trace_id\":\"3a6d5e64-ecf2-4700-98cd-3edd56a1dc34\"}\n[2026-04-27 10:28:14] local.INFO: [EncryptedTokenManager] Generating access token. {\"mode\":\"legacy\"} {\"correlation_id\":\"5fb3c8c8-3160-4964-ac56-02d6e2cb998c\",\"trace_id\":\"3a6d5e64-ecf2-4700-98cd-3edd56a1dc34\"}\n[2026-04-27 10:28:14] local.INFO: [CrmOwnerResolver] Integration owner matched as CRM Owner {\"crm_provider\":\"salesforce\",\"crm_owner\":143,\"team_id\":1} {\"correlation_id\":\"5fb3c8c8-3160-4964-ac56-02d6e2cb998c\",\"trace_id\":\"3a6d5e64-ecf2-4700-98cd-3edd56a1dc34\"}\n[2026-04-27 10:28:14] local.INFO: [SocialAccountService] Fetching token {\"socialAccountId\":1354,\"provider\":\"google\"} {\"correlation_id\":\"5fb3c8c8-3160-4964-ac56-02d6e2cb998c\",\"trace_id\":\"3a6d5e64-ecf2-4700-98cd-3edd56a1dc34\"}\n[2026-04-27 10:28:14] local.INFO: [SocialAccountService] Token retrieved {\"socialAccountId\":1354,\"provider\":\"google\"} {\"correlation_id\":\"5fb3c8c8-3160-4964-ac56-02d6e2cb998c\",\"trace_id\":\"3a6d5e64-ecf2-4700-98cd-3edd56a1dc34\"}\n[2026-04-27 10:28:14] local.INFO: [EncryptedTokenManager] Generating access token. {\"mode\":\"legacy\"} {\"correlation_id\":\"5fb3c8c8-3160-4964-ac56-02d6e2cb998c\",\"trace_id\":\"3a6d5e64-ecf2-4700-98cd-3edd56a1dc34\"}\n[2026-04-27 10:28:15] local.INFO: Processing an email from inbox batch {\"batch\":98446,\"inbox_id\":212,\"email\":\"lukas.kovalik@jiminny.com\",\"email_id\":\"19dce788ac964bbf\",\"from\":\"Nikolay Yankov <notifications@github.com>\",\"to\":\"\\\"jiminny/app\\\" <app@noreply.github.com>\",\"cc\":\"Push <push@noreply.github.com>\"} {\"correlation_id\":\"5fb3c8c8-3160-4964-ac56-02d6e2cb998c\",\"trace_id\":\"3a6d5e64-ecf2-4700-98cd-3edd56a1dc34\"}\n[2026-04-27 10:28:15] local.INFO: [SocialAccountService] Fetching token {\"socialAccountId\":1500,\"provider\":\"salesforce\"} {\"correlation_id\":\"5fb3c8c8-3160-4964-ac56-02d6e2cb998c\",\"trace_id\":\"3a6d5e64-ecf2-4700-98cd-3edd56a1dc34\"}\n[2026-04-27 10:28:15] local.INFO: [SocialAccountService] Token retrieved {\"socialAccountId\":1500,\"provider\":\"salesforce\"} {\"correlation_id\":\"5fb3c8c8-3160-4964-ac56-02d6e2cb998c\",\"trace_id\":\"3a6d5e64-ecf2-4700-98cd-3edd56a1dc34\"}\n[2026-04-27 10:28:15] local.INFO: [EncryptedTokenManager] Generating access token. {\"mode\":\"legacy\"} {\"correlation_id\":\"5fb3c8c8-3160-4964-ac56-02d6e2cb998c\",\"trace_id\":\"3a6d5e64-ecf2-4700-98cd-3edd56a1dc34\"}\n[2026-04-27 10:28:15] local.INFO: [CrmOwnerResolver] Integration owner matched as CRM Owner {\"crm_provider\":\"salesforce\",\"crm_owner\":143,\"team_id\":1} {\"correlation_id\":\"5fb3c8c8-3160-4964-ac56-02d6e2cb998c\",\"trace_id\":\"3a6d5e64-ecf2-4700-98cd-3edd56a1dc34\"}\n[2026-04-27 10:28:15] local.INFO: [EmailImport\\ParticipantsResolver] The sender email is blacklisted, skipping {\"email\":\"notifications@github.com\",\"inbox_id\":212,\"message_provider_id\":\"19dce788ac964bbf\",\"team_id\":1} {\"correlation_id\":\"5fb3c8c8-3160-4964-ac56-02d6e2cb998c\",\"trace_id\":\"3a6d5e64-ecf2-4700-98cd-3edd56a1dc34\"}\n[2026-04-27 10:28:15] local.INFO: [EmailImport\\ParticipantsValidator] Email participants are less than 2 {\"inbox_id\":212,\"message_provider_id\":\"19dce788ac964bbf\",\"message_id\":\"<jiminny/app/pull/11998/before/2b8a97d6be8b053441d5a845b72dd04803cbb53c/after/8a68a94d39cc76ae546e19f354e8b0dabd577314@github.com>\"} {\"correlation_id\":\"5fb3c8c8-3160-4964-ac56-02d6e2cb998c\",\"trace_id\":\"3a6d5e64-ecf2-4700-98cd-3edd56a1dc34\"}\n[2026-04-27 10:28:15] local.INFO: Processing an email from inbox batch {\"batch\":98446,\"inbox_id\":212,\"email\":\"lukas.kovalik@jiminny.com\",\"email_id\":\"19dce7883f2fea19\",\"from\":\"The Jiminny Team <no-reply@dev.jiminny.com>\",\"to\":\"lukas.kovalik@jiminny.com\",\"cc\":null} {\"correlation_id\":\"5fb3c8c8-3160-4964-ac56-02d6e2cb998c\",\"trace_id\":\"3a6d5e64-ecf2-4700-98cd-3edd56a1dc34\"}\n[2026-04-27 10:28:15] local.INFO: [SocialAccountService] Fetching token {\"socialAccountId\":1500,\"provider\":\"salesforce\"} {\"correlation_id\":\"5fb3c8c8-3160-4964-ac56-02d6e2cb998c\",\"trace_id\":\"3a6d5e64-ecf2-4700-98cd-3edd56a1dc34\"}\n[2026-04-27 10:28:15] local.INFO: [SocialAccountService] Token retrieved {\"socialAccountId\":1500,\"provider\":\"salesforce\"} {\"correlation_id\":\"5fb3c8c8-3160-4964-ac56-02d6e2cb998c\",\"trace_id\":\"3a6d5e64-ecf2-4700-98cd-3edd56a1dc34\"}\n[2026-04-27 10:28:15] local.INFO: [EncryptedTokenManager] Generating access token. {\"mode\":\"legacy\"} {\"correlation_id\":\"5fb3c8c8-3160-4964-ac56-02d6e2cb998c\",\"trace_id\":\"3a6d5e64-ecf2-4700-98cd-3edd56a1dc34\"}\n[2026-04-27 10:28:15] local.INFO: [CrmOwnerResolver] Integration owner matched as CRM Owner {\"crm_provider\":\"salesforce\",\"crm_owner\":143,\"team_id\":1} {\"correlation_id\":\"5fb3c8c8-3160-4964-ac56-02d6e2cb998c\",\"trace_id\":\"3a6d5e64-ecf2-4700-98cd-3edd56a1dc34\"}\n[2026-04-27 10:28:15] local.INFO: [EmailImport\\ParticipantsResolver] The sender email is blacklisted, skipping {\"email\":\"no-reply@dev.jiminny.com\",\"inbox_id\":212,\"message_provider_id\":\"19dce7883f2fea19\",\"team_id\":1} {\"correlation_id\":\"5fb3c8c8-3160-4964-ac56-02d6e2cb998c\",\"trace_id\":\"3a6d5e64-ecf2-4700-98cd-3edd56a1dc34\"}\n[2026-04-27 10:28:15] local.INFO: [EmailImport\\ParticipantsValidator] Email participants are less than 2 {\"inbox_id\":212,\"message_provider_id\":\"19dce7883f2fea19\",\"message_id\":\"<d70689e8-7b14-42ac-a3bf-5f1e81da943e@mtasv.net>\"} {\"correlation_id\":\"5fb3c8c8-3160-4964-ac56-02d6e2cb998c\",\"trace_id\":\"3a6d5e64-ecf2-4700-98cd-3edd56a1dc34\"}\n[2026-04-27 10:28:15] local.INFO: Processing an email from inbox batch {\"batch\":98446,\"inbox_id\":212,\"email\":\"lukas.kovalik@jiminny.com\",\"email_id\":\"19dce761cb049fba\",\"from\":\"The Jiminny Team <no-reply@dev.jiminny.com>\",\"to\":\"lukas.kovalik@jiminny.com\",\"cc\":null} {\"correlation_id\":\"5fb3c8c8-3160-4964-ac56-02d6e2cb998c\",\"trace_id\":\"3a6d5e64-ecf2-4700-98cd-3edd56a1dc34\"}\n[2026-04-27 10:28:15] local.INFO: [SocialAccountService] Fetching token {\"socialAccountId\":1500,\"provider\":\"salesforce\"} {\"correlation_id\":\"5fb3c8c8-3160-4964-ac56-02d6e2cb998c\",\"trace_id\":\"3a6d5e64-ecf2-4700-98cd-3edd56a1dc34\"}\n[2026-04-27 10:28:15] local.INFO: [SocialAccountService] Token retrieved {\"socialAccountId\":1500,\"provider\":\"salesforce\"} {\"correlation_id\":\"5fb3c8c8-3160-4964-ac56-02d6e2cb998c\",\"trace_id\":\"3a6d5e64-ecf2-4700-98cd-3edd56a1dc34\"}\n[2026-04-27 10:28:15] local.INFO: [EncryptedTokenManager] Generating access token. {\"mode\":\"legacy\"} {\"correlation_id\":\"5fb3c8c8-3160-4964-ac56-02d6e2cb998c\",\"trace_id\":\"3a6d5e64-ecf2-4700-98cd-3edd56a1dc34\"}\n[2026-04-27 10:28:15] local.INFO: [CrmOwnerResolver] Integration owner matched as CRM Owner {\"crm_provider\":\"salesforce\",\"crm_owner\":143,\"team_id\":1} {\"correlation_id\":\"5fb3c8c8-3160-4964-ac56-02d6e2cb998c\",\"trace_id\":\"3a6d5e64-ecf2-4700-98cd-3edd56a1dc34\"}\n[2026-04-27 10:28:15] local.INFO: [EmailImport\\ParticipantsResolver] The sender email is blacklisted, skipping {\"email\":\"no-reply@dev.jiminny.com\",\"inbox_id\":212,\"message_provider_id\":\"19dce761cb049fba\",\"team_id\":1} {\"correlation_id\":\"5fb3c8c8-3160-4964-ac56-02d6e2cb998c\",\"trace_id\":\"3a6d5e64-ecf2-4700-98cd-3edd56a1dc34\"}\n[2026-04-27 10:28:15] local.INFO: [EmailImport\\ParticipantsValidator] Email participants are less than 2 {\"inbox_id\":212,\"message_provider_id\":\"19dce761cb049fba\",\"message_id\":\"<017f0e66-9989-446e-9788-7ae83a99137e@mtasv.net>\"} {\"correlation_id\":\"5fb3c8c8-3160-4964-ac56-02d6e2cb998c\",\"trace_id\":\"3a6d5e64-ecf2-4700-98cd-3edd56a1dc34\"}\n[2026-04-27 10:28:15] local.INFO: Processing an email from inbox batch {\"batch\":98446,\"inbox_id\":212,\"email\":\"lukas.kovalik@jiminny.com\",\"email_id\":\"19dce74ed398d2c1\",\"from\":\"\\\"sonarqubecloud[bot]\\\" <notifications@github.com>\",\"to\":\"\\\"jiminny/app\\\" <app@noreply.github.com>\",\"cc\":\"Lukas Kovalik <kovaliklukas@gmail.com>, Author <author@noreply.github.com>\"} {\"correlation_id\":\"5fb3c8c8-3160-4964-ac56-02d6e2cb998c\",\"trace_id\":\"3a6d5e64-ecf2-4700-98cd-3edd56a1dc34\"}\n[2026-04-27 10:28:15] local.INFO: [SocialAccountService] Fetching token {\"socialAccountId\":1500,\"provider\":\"salesforce\"} {\"correlation_id\":\"5fb3c8c8-3160-4964-ac56-02d6e2cb998c\",\"trace_id\":\"3a6d5e64-ecf2-4700-98cd-3edd56a1dc34\"}\n[2026-04-27 10:28:15] local.INFO: [SocialAccountService] Token retrieved {\"socialAccountId\":1500,\"provider\":\"salesforce\"} {\"correlation_id\":\"5fb3c8c8-3160-4964-ac56-02d6e2cb998c\",\"trace_id\":\"3a6d5e64-ecf2-4700-98cd-3edd56a1dc34\"}\n[2026-04-27 10:28:15] local.INFO: [EncryptedTokenManager] Generating access token. {\"mode\":\"legacy\"} {\"correlation_id\":\"5fb3c8c8-3160-4964-ac56-02d6e2cb998c\",\"trace_id\":\"3a6d5e64-ecf2-4700-98cd-3edd56a1dc34\"}\n[2026-04-27 10:28:15] local.INFO: [CrmOwnerResolver] Integration owner matched as CRM Owner {\"crm_provider\":\"salesforce\",\"crm_owner\":143,\"team_id\":1} {\"correlation_id\":\"5fb3c8c8-3160-4964-ac56-02d6e2cb998c\",\"trace_id\":\"3a6d5e64-ecf2-4700-98cd-3edd56a1dc34\"}\n[2026-04-27 10:28:15] local.INFO: [EmailImport\\ParticipantsResolver] The sender email is blacklisted, skipping {\"email\":\"notifications@github.com\",\"inbox_id\":212,\"message_provider_id\":\"19dce74ed398d2c1\",\"team_id\":1} {\"correlation_id\":\"5fb3c8c8-3160-4964-ac56-02d6e2cb998c\",\"trace_id\":\"3a6d5e64-ecf2-4700-98cd-3edd56a1dc34\"}\n[2026-04-27 10:28:15] local.INFO: [EmailImport\\ParticipantsValidator] Email participants are less than 2 {\"inbox_id\":212,\"message_provider_id\":\"19dce74ed398d2c1\",\"message_id\":\"<jiminny/app/pull/12016/c4326142661@github.com>\"} {\"correlation_id\":\"5fb3c8c8-3160-4964-ac56-02d6e2cb998c\",\"trace_id\":\"3a6d5e64-ecf2-4700-98cd-3edd56a1dc34\"}\n[2026-04-27 10:28:15] local.INFO: Processing an email from inbox batch {\"batch\":98446,\"inbox_id\":212,\"email\":\"lukas.kovalik@jiminny.com\",\"email_id\":\"19dce713dbecf41f\",\"from\":\"\\\"sonarqubecloud[bot]\\\" <notifications@github.com>\",\"to\":\"\\\"jiminny/infrastructure\\\" <infrastructure@noreply.github.com>\",\"cc\":\"Subscribed <subscribed@noreply.github.com>\"} {\"correlation_id\":\"5fb3c8c8-3160-4964-ac56-02d6e2cb998c\",\"trace_id\":\"3a6d5e64-ecf2-4700-98cd-3edd56a1dc34\"}\n[2026-04-27 10:28:15] local.INFO: [SocialAccountService] Fetching token {\"socialAccountId\":1500,\"provider\":\"salesforce\"} {\"correlation_id\":\"5fb3c8c8-3160-4964-ac56-02d6e2cb998c\",\"trace_id\":\"3a6d5e64-ecf2-4700-98cd-3edd56a1dc34\"}\n[2026-04-27 10:28:15] local.INFO: [SocialAccountService] Token retrieved {\"socialAccountId\":1500,\"provider\":\"salesforce\"} {\"correlation_id\":\"5fb3c8c8-3160-4964-ac56-02d6e2cb998c\",\"trace_id\":\"3a6d5e64-ecf2-4700-98cd-3edd56a1dc34\"}\n[2026-04-27 10:28:15] local.INFO: [EncryptedTokenManager] Generating access token. {\"mode\":\"legacy\"} {\"correlation_id\":\"5fb3c8c8-3160-4964-ac56-02d6e2cb998c\",\"trace_id\":\"3a6d5e64-ecf2-4700-98cd-3edd56a1dc34\"}\n[2026-04-27 10:28:15] local.INFO: [CrmOwnerResolver] Integration owner matched as CRM Owner {\"crm_provider\":\"salesforce\",\"crm_owner\":143,\"team_id\":1} {\"correlation_id\":\"5fb3c8c8-3160-4964-ac56-02d6e2cb998c\",\"trace_id\":\"3a6d5e64-ecf2-4700-98cd-3edd56a1dc34\"}\n[2026-04-27 10:28:15] local.INFO: [EmailImport\\ParticipantsResolver] The sender email is blacklisted, skipping {\"email\":\"notifications@github.com\",\"inbox_id\":212,\"message_provider_id\":\"19dce713dbecf41f\",\"team_id\":1} {\"correlation_id\":\"5fb3c8c8-3160-4964-ac56-02d6e2cb998c\",\"trace_id\":\"3a6d5e64-ecf2-4700-98cd-3edd56a1dc34\"}\n[2026-04-27 10:28:15] local.INFO: [EmailImport\\ParticipantsValidator] Email participants are less than 2 {\"inbox_id\":212,\"message_provider_id\":\"19dce713dbecf41f\",\"message_id\":\"<jiminny/infrastructure/pull/725/c4326108250@github.com>\"} {\"correlation_id\":\"5fb3c8c8-3160-4964-ac56-02d6e2cb998c\",\"trace_id\":\"3a6d5e64-ecf2-4700-98cd-3edd56a1dc34\"}\n[2026-04-27 10:28:15] local.INFO: Processing an email from inbox batch {\"batch\":98446,\"inbox_id\":212,\"email\":\"lukas.kovalik@jiminny.com\",\"email_id\":\"19dce708ed8b5acb\",\"from\":\"Veselin Kulov <notifications@github.com>\",\"to\":\"\\\"jiminny/infrastructure\\\" <infrastructure@noreply.github.com>\",\"cc\":\"Push <push@noreply.github.com>\"} {\"correlation_id\":\"5fb3c8c8-3160-4964-ac56-02d6e2cb998c\",\"trace_id\":\"3a6d5e64-ecf2-4700-98cd-3edd56a1dc34\"}\n[2026-04-27 10:28:15] local.INFO: [SocialAccountService] Fetching token {\"socialAccountId\":1500,\"provider\":\"salesforce\"} {\"correlation_id\":\"5fb3c8c8-3160-4964-ac56-02d6e2cb998c\",\"trace_id\":\"3a6d5e64-ecf2-4700-98cd-3edd56a1dc34\"}\n[2026-04-27 10:28:15] local.INFO: [SocialAccountService] Token retrieved {\"socialAccountId\":1500,\"provider\":\"salesforce\"} {\"correlation_id\":\"5fb3c8c8-3160-4964-ac56-02d6e2cb998c\",\"trace_id\":\"3a6d5e64-ecf2-4700-98cd-3edd56a1dc34\"}\n[2026-04-27 10:28:15] local.INFO: [EncryptedTokenManager] Generating access token. {\"mode\":\"legacy\"} {\"correlation_id\":\"5fb3c8c8-3160-4964-ac56-02d6e2cb998c\",\"trace_id\":\"3a6d5e64-ecf2-4700-98cd-3edd56a1dc34\"}\n[2026-04-27 10:28:15] local.INFO: [CrmOwnerResolver] Integration owner matched as CRM Owner {\"crm_provider\":\"salesforce\",\"crm_owner\":143,\"team_id\":1} {\"correlation_id\":\"5fb3c8c8-3160-4964-ac56-02d6e2cb998c\",\"trace_id\":\"3a6d5e64-ecf2-4700-98cd-3edd56a1dc34\"}\n[2026-04-27 10:28:15] local.INFO: [EmailImport\\ParticipantsResolver] The sender email is blacklisted, skipping {\"email\":\"notifications@github.com\",\"inbox_id\":212,\"message_provider_id\":\"19dce708ed8b5acb\",\"team_id\":1} {\"correlation_id\":\"5fb3c8c8-3160-4964-ac56-02d6e2cb998c\",\"trace_id\":\"3a6d5e64-ecf2-4700-98cd-3edd56a1dc34\"}\n[2026-04-27 10:28:15] local.INFO: [EmailImport\\ParticipantsValidator] Email participants are less than 2 {\"inbox_id\":212,\"message_provider_id\":\"19dce708ed8b5acb\",\"message_id\":\"<jiminny/infrastructure/pull/725/before/186952cc584428fcca17aa364f6cc535d65e804a/after/078246a06b25fb93ad61c7f14472ecb755483f4d@github.com>\"} {\"correlation_id\":\"5fb3c8c8-3160-4964-ac56-02d6e2cb998c\",\"trace_id\":\"3a6d5e64-ecf2-4700-98cd-3edd56a1dc34\"}\n[2026-04-27 10:28:15] local.INFO: Processing an email from inbox batch {\"batch\":98446,\"inbox_id\":212,\"email\":\"lukas.kovalik@jiminny.com\",\"email_id\":\"19dce6eea657f2fa\",\"from\":\"\\\"sonarqubecloud[bot]\\\" <notifications@github.com>\",\"to\":\"\\\"jiminny/app\\\" <app@noreply.github.com>\",\"cc\":\"Subscribed <subscribed@noreply.github.com>\"} {\"correlation_id\":\"5fb3c8c8-3160-4964-ac56-02d6e2cb998c\",\"trace_id\":\"3a6d5e64-ecf2-4700-98cd-3edd56a1dc34\"}\n[2026-04-27 10:28:15] local.INFO: [SocialAccountService] Fetching token {\"socialAccountId\":1500,\"provider\":\"salesforce\"} {\"correlation_id\":\"5fb3c8c8-3160-4964-ac56-02d6e2cb998c\",\"trace_id\":\"3a6d5e64-ecf2-4700-98cd-3edd56a1dc34\"}\n[2026-04-27 10:28:15] local.INFO: [SocialAccountService] Token retrieved {\"socialAccountId\":1500,\"provider\":\"salesforce\"} {\"correlation_id\":\"5fb3c8c8-3160-4964-ac56-02d6e2cb998c\",\"trace_id\":\"3a6d5e64-ecf2-4700-98cd-3edd56a1dc34\"}\n[2026-04-27 10:28:15] local.INFO: [EncryptedTokenManager] Generating access token. {\"mode\":\"legacy\"} {\"correlation_id\":\"5fb3c8c8-3160-4964-ac56-02d6e2cb998c\",\"trace_id\":\"3a6d5e64-ecf2-4700-98cd-3edd56a1dc34\"}\n[2026-04-27 10:28:15] local.INFO: [CrmOwnerResolver] Integration owner matched as CRM Owner {\"crm_provider\":\"salesforce\",\"crm_owner\":143,\"team_id\":1} {\"correlation_id\":\"5fb3c8c8-3160-4964-ac56-02d6e2cb998c\",\"trace_id\":\"3a6d5e64-ecf2-4700-98cd-3edd56a1dc34\"}\n[2026-04-27 10:28:15] local.INFO: [EmailImport\\ParticipantsResolver] The sender email is blacklisted, skipping {\"email\":\"notifications@github.com\",\"inbox_id\":212,\"message_provider_id\":\"19dce6eea657f2fa\",\"team_id\":1} {\"correlation_id\":\"5fb3c8c8-3160-4964-ac56-02d6e2cb998c\",\"trace_id\":\"3a6d5e64-ecf2-4700-98cd-3edd56a1dc34\"}\n[2026-04-27 10:28:15] local.INFO: [EmailImport\\ParticipantsValidator] Email participants are less than 2 {\"inbox_id\":212,\"message_provider_id\":\"19dce6eea657f2fa\",\"message_id\":\"<jiminny/app/pull/11998/c4326088534@github.com>\"} {\"correlation_id\":\"5fb3c8c8-3160-4964-ac56-02d6e2cb998c\",\"trace_id\":\"3a6d5e64-ecf2-4700-98cd-3edd56a1dc34\"}\n[2026-04-27 10:28:15] local.INFO: Deleting successfully processed batch 98446 for inbox 212 {\"correlation_id\":\"5fb3c8c8-3160-4964-ac56-02d6e2cb998c\",\"trace_id\":\"3a6d5e64-ecf2-4700-98cd-3edd56a1dc34\"}\n[2026-04-27 10:28:15] local.INFO: [EmailSchedule] FINISHED batch process {\"host\":\"docker_lamp_1\",\"processed\":1} {\"correlation_id\":\"5fb3c8c8-3160-4964-ac56-02d6e2cb998c\",\"trace_id\":\"3a6d5e64-ecf2-4700-98cd-3edd56a1dc34\"}\n[2026-04-27 10:28:15] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage for command {\"command\":\"mailbox:batch:process\",\"memoryBeforeCommandInMb\":60.0,\"memoryAfterCommandInMB\":62.0,\"memoryPeakBeforeCommandInMb\":99.727,\"memoryPeakAfterCommandInMB\":99.727} {\"correlation_id\":\"5fb3c8c8-3160-4964-ac56-02d6e2cb998c\",\"trace_id\":\"3a6d5e64-ecf2-4700-98cd-3edd56a1dc34\"}\n[2026-04-27 10:28:16] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage before starting command {\"command\":\"conference:monitor:count\",\"memoryBeforeCommandInMb\":60.0,\"memoryPeakBeforeCommandInMb\":99.727} {\"correlation_id\":\"ef5b3e1a-a06a-4527-9120-b2adfe4e1846\",\"trace_id\":\"d932d64a-3f63-4aca-8abf-bb13e0892cba\"}\n[2026-04-27 10:28:16] local.INFO: Running conference:monitor:count command for activities in (2026-04-27 10:26:00, 2026-04-27 10:28:00] {\"correlation_id\":\"ef5b3e1a-a06a-4527-9120-b2adfe4e1846\",\"trace_id\":\"d932d64a-3f63-4aca-8abf-bb13e0892cba\"}\n[2026-04-27 10:28:16] local.INFO: [conference:monitor:count] No activities found in (2026-04-27 10:26:00, 2026-04-27 10:28:00] {\"correlation_id\":\"ef5b3e1a-a06a-4527-9120-b2adfe4e1846\",\"trace_id\":\"d932d64a-3f63-4aca-8abf-bb13e0892cba\"}\n[2026-04-27 10:28:16] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage for command {\"command\":\"conference:monitor:count\",\"memoryBeforeCommandInMb\":60.0,\"memoryAfterCommandInMB\":62.0,\"memoryPeakBeforeCommandInMb\":99.727,\"memoryPeakAfterCommandInMB\":99.727} {\"correlation_id\":\"ef5b3e1a-a06a-4527-9120-b2adfe4e1846\",\"trace_id\":\"d932d64a-3f63-4aca-8abf-bb13e0892cba\"}\n[2026-04-27 10:28:17] local.ERROR: Target class [Jiminny\\Repositories\\AjReportsRepository] does not exist. {\"exception\":\"[object] (Illuminate\\\\Contracts\\\\Container\\\\BindingResolutionException(code: 0): Target class [Jiminny\\\\Repositories\\\\AjReportsRepository] does not exist. at /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php:1124)\n[stacktrace]\n#0 /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php(933): Illuminate\\\\Container\\\\Container->build('Jiminny\\\\\\\\Reposit...')\n#1 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Application.php(1078): Illuminate\\\\Container\\\\Container->resolve('Jiminny\\\\\\\\Reposit...', Array, true)\n#2 /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php(864): Illuminate\\\\Foundation\\\\Application->resolve('Jiminny\\\\\\\\Reposit...', Array)\n#3 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Application.php(1058): Illuminate\\\\Container\\\\Container->make('Jiminny\\\\\\\\Reposit...', Array)\n#4 /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php(1336): Illuminate\\\\Foundation\\\\Application->make('Jiminny\\\\\\\\Reposit...')\n#5 /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php(1237): Illuminate\\\\Container\\\\Container->resolveClass(Object(ReflectionParameter))\n#6 /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php(1162): Illuminate\\\\Container\\\\Container->resolveDependencies(Array)\n#7 /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php(933): Illuminate\\\\Container\\\\Container->build('Jiminny\\\\\\\\Http\\\\\\\\Co...')\n#8 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Application.php(1078): Illuminate\\\\Container\\\\Container->resolve('Jiminny\\\\\\\\Http\\\\\\\\Co...', Array, true)\n#9 /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php(864): Illuminate\\\\Foundation\\\\Application->resolve('Jiminny\\\\\\\\Http\\\\\\\\Co...', Array)\n#10 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Application.php(1058): Illuminate\\\\Container\\\\Container->make('Jiminny\\\\\\\\Http\\\\\\\\Co...', Array)\n#11 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Route.php(286): Illuminate\\\\Foundation\\\\Application->make('Jiminny\\\\\\\\Http\\\\\\\\Co...')\n#12 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Route.php(1133): Illuminate\\\\Routing\\\\Route->getController()\n#13 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Route.php(1062): Illuminate\\\\Routing\\\\Route->controllerMiddleware()\n#14 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Router.php(834): Illuminate\\\\Routing\\\\Route->gatherMiddleware()\n#15 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Router.php(816): Illuminate\\\\Routing\\\\Router->gatherRouteMiddleware(Object(Illuminate\\\\Routing\\\\Route))\n#16 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Router.php(800): Illuminate\\\\Routing\\\\Router->runRouteWithinStack(Object(Illuminate\\\\Routing\\\\Route), Object(Illuminate\\\\Http\\\\Request))\n#17 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Router.php(764): Illuminate\\\\Routing\\\\Router->runRoute(Object(Illuminate\\\\Http\\\\Request), Object(Illuminate\\\\Routing\\\\Route))\n#18 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Router.php(753): Illuminate\\\\Routing\\\\Router->dispatchToRoute(Object(Illuminate\\\\Http\\\\Request))\n#19 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Http/Kernel.php(200): Illuminate\\\\Routing\\\\Router->dispatch(Object(Illuminate\\\\Http\\\\Request))\n#20 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(180): Illuminate\\\\Foundation\\\\Http\\\\Kernel->Illuminate\\\\Foundation\\\\Http\\\\{closure}(Object(Illuminate\\\\Http\\\\Request))\n#21 /home/jiminny/vendor/barryvdh/laravel-debugbar/src/Middleware/InjectDebugbar.php(59): Illuminate\\\\Pipeline\\\\Pipeline->Illuminate\\\\Pipeline\\\\{closure}(Object(Illuminate\\\\Http\\\\Request))\n#22 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(219): Barryvdh\\\\Debugbar\\\\Middleware\\\\InjectDebugbar->handle(Object(Illuminate\\\\Http\\\\Request), Object(Closure))\n#23 /home/jiminny/vendor/laravel/framework/src/Illuminate/Http/Middleware/HandleCors.php(74): Illuminate\\\\Pipeline\\\\Pipeline->Illuminate\\\\Pipeline\\\\{closure}(Object(Illuminate\\\\Http\\\\Request))\n#24 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(219): Illuminate\\\\Http\\\\Middleware\\\\HandleCors->handle(Object(Illuminate\\\\Http\\\\Request), Object(Closure))\n#25 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Http/Middleware/TransformsRequest.php(21): Illuminate\\\\Pipeline\\\\Pipeline->Illuminate\\\\Pipeline\\\\{closure}(Object(Illuminate\\\\Http\\\\Request))\n#26 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Http/Middleware/TrimStrings.php(51): Illuminate\\\\Foundation\\\\Http\\\\Middleware\\\\TransformsRequest->handle(Object(Illuminate\\\\Http\\\\Request), Object(Closure))\n#27 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(219): Illuminate\\\\Foundation\\\\Http\\\\Middleware\\\\TrimStrings->handle(Object(Illuminate\\\\Http\\\\Request), Object(Closure))\n#28 /home/jiminny/vendor/bepsvpt/secure-headers/src/SecureHeadersMiddleware.php(18): Illuminate\\\\Pipeline\\\\Pipeline->Illuminate\\\\Pipeline\\\\{closure}(Object(Illuminate\\\\Http\\\\Request))\n#29 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(219): Bepsvpt\\\\SecureHeaders\\\\SecureHeadersMiddleware->handle(Object(Illuminate\\\\Http\\\\Request), Object(Closure))\n#30 /home/jiminny/app/Http/Middleware/SentryContext.php(60): Illuminate\\\\Pipeline\\\\Pipeline->Illuminate\\\\Pipeline\\\\{closure}(Object(Illuminate\\\\Http\\\\Request))\n#31 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(219): Jiminny\\\\Http\\\\Middleware\\\\SentryContext->handle(Object(Illuminate\\\\Http\\\\Request), Object(Closure))\n#32 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Http/Middleware/PreventRequestsDuringMaintenance.php(109): Illuminate\\\\Pipeline\\\\Pipeline->Illuminate\\\\Pipeline\\\\{closure}(Object(Illuminate\\\\Http\\\\Request))\n#33 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(219): Illuminate\\\\Foundation\\\\Http\\\\Middleware\\\\PreventRequestsDuringMaintenance->handle(Object(Illuminate\\\\Http\\\\Request), Object(Closure))\n#34 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Http/Middleware/InvokeDeferredCallbacks.php(22): Illuminate\\\\Pipeline\\\\Pipeline->Illuminate\\\\Pipeline\\\\{closure}(Object(Illuminate\\\\Http\\\\Request))\n#35 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(219): Illuminate\\\\Foundation\\\\Http\\\\Middleware\\\\InvokeDeferredCallbacks->handle(Object(Illuminate\\\\Http\\\\Request), Object(Closure))\n#36 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(137): Illuminate\\\\Pipeline\\\\Pipeline->Illuminate\\\\Pipeline\\\\{closure}(Object(Illuminate\\\\Http\\\\Request))\n#37 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Http/Kernel.php(175): Illuminate\\\\Pipeline\\\\Pipeline->then(Object(Closure))\n#38 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Http/Kernel.php(144): Illuminate\\\\Foundation\\\\Http\\\\Kernel->sendRequestThroughRouter(Object(Illuminate\\\\Http\\\\Request))\n#39 /home/jiminny/public/index.php(51): Illuminate\\\\Foundation\\\\Http\\\\Kernel->handle(Object(Illuminate\\\\Http\\\\Request))\n#40 {main}\n\n[previous exception] [object] (ReflectionException(code: -1): Class \\\"Jiminny\\\\Repositories\\\\AjReportsRepository\\\" does not exist at /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php:1122)\n[stacktrace]\n#0 /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php(1122): ReflectionClass->__construct('Jiminny\\\\\\\\Reposit...')\n#1 /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php(933): Illuminate\\\\Container\\\\Container->build('Jiminny\\\\\\\\Reposit...')\n#2 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Application.php(1078): Illuminate\\\\Container\\\\Container->resolve('Jiminny\\\\\\\\Reposit...', Array, true)\n#3 /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php(864): Illuminate\\\\Foundation\\\\Application->resolve('Jiminny\\\\\\\\Reposit...', Array)\n#4 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Application.php(1058): Illuminate\\\\Container\\\\Container->make('Jiminny\\\\\\\\Reposit...', Array)\n#5 /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php(1336): Illuminate\\\\Foundation\\\\Application->make('Jiminny\\\\\\\\Reposit...')\n#6 /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php(1237): Illuminate\\\\Container\\\\Container->resolveClass(Object(ReflectionParameter))\n#7 /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php(1162): Illuminate\\\\Container\\\\Container->resolveDependencies(Array)\n#8 /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php(933): Illuminate\\\\Container\\\\Container->build('Jiminny\\\\\\\\Http\\\\\\\\Co...')\n#9 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Application.php(1078): Illuminate\\\\Container\\\\Container->resolve('Jiminny\\\\\\\\Http\\\\\\\\Co...', Array, true)\n#10 /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php(864): Illuminate\\\\Foundation\\\\Application->resolve('Jiminny\\\\\\\\Http\\\\\\\\Co...', Array)\n#11 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Application.php(1058): Illuminate\\\\Container\\\\Container->make('Jiminny\\\\\\\\Http\\\\\\\\Co...', Array)\n#12 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Route.php(286): Illuminate\\\\Foundation\\\\Application->make('Jiminny\\\\\\\\Http\\\\\\\\Co...')\n#13 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Route.php(1133): Illuminate\\\\Routing\\\\Route->getController()\n#14 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Route.php(1062): Illuminate\\\\Routing\\\\Route->controllerMiddleware()\n#15 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Router.php(834): Illuminate\\\\Routing\\\\Route->gatherMiddleware()\n#16 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Router.php(816): Illuminate\\\\Routing\\\\Router->gatherRouteMiddleware(Object(Illuminate\\\\Routing\\\\Route))\n#17 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Router.php(800): Illuminate\\\\Routing\\\\Router->runRouteWithinStack(Object(Illuminate\\\\Routing\\\\Route), Object(Illuminate\\\\Http\\\\Request))\n#18 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Router.php(764): Illuminate\\\\Routing\\\\Router->runRoute(Object(Illuminate\\\\Http\\\\Request), Object(Illuminate\\\\Routing\\\\Route))\n#19 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Router.php(753): Illuminate\\\\Routing\\\\Router->dispatchToRoute(Object(Illuminate\\\\Http\\\\Request))\n#20 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Http/Kernel.php(200): Illuminate\\\\Routing\\\\Router->dispatch(Object(Illuminate\\\\Http\\\\Request))\n#21 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(180): Illuminate\\\\Foundation\\\\Http\\\\Kernel->Illuminate\\\\Foundation\\\\Http\\\\{closure}(Object(Illuminate\\\\Http\\\\Request))\n#22 /home/jiminny/vendor/barryvdh/laravel-debugbar/src/Middleware/InjectDebugbar.php(59): Illuminate\\\\Pipeline\\\\Pipeline->Illuminate\\\\Pipeline\\\\{closure}(Object(Illuminate\\\\Http\\\\Request))\n#23 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(219): Barryvdh\\\\Debugbar\\\\Middleware\\\\InjectDebugbar->handle(Object(Illuminate\\\\Http\\\\Request), Object(Closure))\n#24 /home/jiminny/vendor/laravel/framework/src/Illuminate/Http/Middleware/HandleCors.php(74): Illuminate\\\\Pipeline\\\\Pipeline->Illuminate\\\\Pipeline\\\\{closure}(Object(Illuminate\\\\Http\\\\Request))\n#25 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(219): Illuminate\\\\Http\\\\Middleware\\\\HandleCors->handle(Object(Illuminate\\\\Http\\\\Request), Object(Closure))\n#26 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Http/Middleware/TransformsRequest.php(21): Illuminate\\\\Pipeline\\\\Pipeline->Illuminate\\\\Pipeline\\\\{closure}(Object(Illuminate\\\\Http\\\\Request))\n#27 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Http/Middleware/TrimStrings.php(51): Illuminate\\\\Foundation\\\\Http\\\\Middleware\\\\TransformsRequest->handle(Object(Illuminate\\\\Http\\\\Request), Object(Closure))\n#28 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(219): Illuminate\\\\Foundation\\\\Http\\\\Middleware\\\\TrimStrings->handle(Object(Illuminate\\\\Http\\\\Request), Object(Closure))\n#29 /home/jiminny/vendor/bepsvpt/secure-headers/src/SecureHeadersMiddleware.php(18): Illuminate\\\\Pipeline\\\\Pipeline->Illuminate\\\\Pipeline\\\\{closure}(Object(Illuminate\\\\Http\\\\Request))\n#30 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(219): Bepsvpt\\\\SecureHeaders\\\\SecureHeadersMiddleware->handle(Object(Illuminate\\\\Http\\\\Request), Object(Closure))\n#31 /home/jiminny/app/Http/Middleware/SentryContext.php(60): Illuminate\\\\Pipeline\\\\Pipeline->Illuminate\\\\Pipeline\\\\{closure}(Object(Illuminate\\\\Http\\\\Request))\n#32 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(219): Jiminny\\\\Http\\\\Middleware\\\\SentryContext->handle(Object(Illuminate\\\\Http\\\\Request), Object(Closure))\n#33 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Http/Middleware/PreventRequestsDuringMaintenance.php(109): Illuminate\\\\Pipeline\\\\Pipeline->Illuminate\\\\Pipeline\\\\{closure}(Object(Illuminate\\\\Http\\\\Request))\n#34 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(219): Illuminate\\\\Foundation\\\\Http\\\\Middleware\\\\PreventRequestsDuringMaintenance->handle(Object(Illuminate\\\\Http\\\\Request), Object(Closure))\n#35 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Http/Middleware/InvokeDeferredCallbacks.php(22): Illuminate\\\\Pipeline\\\\Pipeline->Illuminate\\\\Pipeline\\\\{closure}(Object(Illuminate\\\\Http\\\\Request))\n#36 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(219): Illuminate\\\\Foundation\\\\Http\\\\Middleware\\\\InvokeDeferredCallbacks->handle(Object(Illuminate\\\\Http\\\\Request), Object(Closure))\n#37 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(137): Illuminate\\\\Pipeline\\\\Pipeline->Illuminate\\\\Pipeline\\\\{closure}(Object(Illuminate\\\\Http\\\\Request))\n#38 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Http/Kernel.php(175): Illuminate\\\\Pipeline\\\\Pipeline->then(Object(Closure))\n#39 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Http/Kernel.php(144): Illuminate\\\\Foundation\\\\Http\\\\Kernel->sendRequestThroughRouter(Object(Illuminate\\\\Http\\\\Request))\n#40 /home/jiminny/public/index.php(51): Illuminate\\\\Foundation\\\\Http\\\\Kernel->handle(Object(Illuminate\\\\Http\\\\Request))\n#41 {main}\n\"} {\"correlation_id\":\"46b082fc-5a28-4cc4-8bd1-c670f8662e7f\",\"trace_id\":\"8ae7dc26-61c7-42e4-8617-0b85f53e54c1\"}\n[2026-04-27 10:28:19] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage before starting command {\"command\":\"calendar:sync\",\"memoryBeforeCommandInMb\":60.0,\"memoryPeakBeforeCommandInMb\":99.727} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:19] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage before starting command {\"command\":\"mailbox:batch:retry-failed\",\"memoryBeforeCommandInMb\":60.0,\"memoryPeakBeforeCommandInMb\":99.727} {\"correlation_id\":\"e155562a-84a1-45f5-8897-1779f96886f6\",\"trace_id\":\"12141267-1032-479b-aab5-e17e133007d9\"}\n[2026-04-27 10:28:19] local.NOTICE: Calendar sync start {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:19] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage for command {\"command\":\"mailbox:batch:retry-failed\",\"memoryBeforeCommandInMb\":60.0,\"memoryAfterCommandInMB\":62.0,\"memoryPeakBeforeCommandInMb\":99.727,\"memoryPeakAfterCommandInMB\":99.727} {\"correlation_id\":\"e155562a-84a1-45f5-8897-1779f96886f6\",\"trace_id\":\"12141267-1032-479b-aab5-e17e133007d9\"}\n[2026-04-27 10:28:19] local.INFO: [SocialAccountService] Fetching token {\"socialAccountId\":1393,\"provider\":\"google\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:19] local.INFO: [SocialAccountService] Token needs refreshing {\"socialAccountId\":1393,\"provider\":\"google\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:19] local.INFO: [EncryptedTokenManager] Generating access token. {\"mode\":\"legacy\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:19] local.INFO: [SocialAccountService] Refreshing token from provider {\"socialAccountId\":1393,\"provider\":\"google\",\"refreshToken\":\"5aa7e2d96b53201cd16fca5d2e4ef3ad03320971fc064781d18aee3ae7b99fbf\",\"state\":\"full-refresh\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:20] local.ERROR: [SocialAccountService] Failed to refresh token {\"socialAccountId\":1393,\"provider\":\"google\",\"responseBody\":{\"error\":\"invalid_grant\",\"error_description\":\"Account has been deleted\"}} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:20] local.INFO: [SocialAccountObserver] Saving model {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:20] local.ERROR: [SocialAccountService] Failed to refresh token {\"socialAccountId\":1393,\"provider\":\"google\",\"reason\":\"Flow refresh required.\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:20] local.INFO: [SocialAccountService] Fetching token {\"socialAccountId\":1387,\"provider\":\"google\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:20] local.INFO: [SocialAccountService] Token needs refreshing {\"socialAccountId\":1387,\"provider\":\"google\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:20] local.INFO: [EncryptedTokenManager] Generating access token. {\"mode\":\"legacy\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:20] local.INFO: [SocialAccountService] Refreshing token from provider {\"socialAccountId\":1387,\"provider\":\"google\",\"refreshToken\":\"8157ac6de94842937194009e9c50e459253600f799dacf6a40755ffdbeb5bba6\",\"state\":\"full-refresh\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:21] local.ERROR: [SocialAccountService] Failed to refresh token {\"socialAccountId\":1387,\"provider\":\"google\",\"responseBody\":{\"error\":\"invalid_grant\",\"error_description\":\"Account has been deleted\"}} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:21] local.INFO: [SocialAccountObserver] Saving model {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:21] local.ERROR: [SocialAccountService] Failed to refresh token {\"socialAccountId\":1387,\"provider\":\"google\",\"reason\":\"Flow refresh required.\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:21] local.INFO: [SocialAccountService] Fetching token {\"socialAccountId\":1348,\"provider\":\"google\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:21] local.INFO: [SocialAccountService] Token needs refreshing {\"socialAccountId\":1348,\"provider\":\"google\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:21] local.INFO: [EncryptedTokenManager] Generating access token. {\"mode\":\"legacy\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:21] local.INFO: [SocialAccountService] Refreshing token from provider {\"socialAccountId\":1348,\"provider\":\"google\",\"refreshToken\":\"9e7d13d3032d0cb1b79d8e95aef01383e8e91eb52ff8ee960c8a0b6b95cd8c73\",\"state\":\"full-refresh\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:21] local.ERROR: [SocialAccountService] Failed to refresh token {\"socialAccountId\":1348,\"provider\":\"google\",\"responseBody\":{\"error\":\"invalid_grant\",\"error_description\":\"Bad Request\"}} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:21] local.INFO: [SocialAccountObserver] Saving model {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:21] local.ERROR: [SocialAccountService] Failed to refresh token {\"socialAccountId\":1348,\"provider\":\"google\",\"reason\":\"Flow refresh required.\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:21] local.INFO: [SocialAccountService] Fetching token {\"socialAccountId\":1361,\"provider\":\"google\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:21] local.INFO: [SocialAccountService] Token needs refreshing {\"socialAccountId\":1361,\"provider\":\"google\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:21] local.INFO: [EncryptedTokenManager] Generating access token. {\"mode\":\"legacy\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:21] local.INFO: [SocialAccountService] Refreshing token from provider {\"socialAccountId\":1361,\"provider\":\"google\",\"refreshToken\":\"6c843da199c2b9907445329304fcc4ec5057a4ee748d8299641764395c08e1fd\",\"state\":\"full-refresh\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:22] local.ERROR: [SocialAccountService] Failed to refresh token {\"socialAccountId\":1361,\"provider\":\"google\",\"responseBody\":{\"error\":\"invalid_grant\",\"error_description\":\"Account has been deleted\"}} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:22] local.INFO: [SocialAccountObserver] Saving model {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:22] local.ERROR: [SocialAccountService] Failed to refresh token {\"socialAccountId\":1361,\"provider\":\"google\",\"reason\":\"Flow refresh required.\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:22] local.INFO: [SocialAccountService] Fetching token {\"socialAccountId\":1310,\"provider\":\"google\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:22] local.INFO: [SocialAccountService] Token needs refreshing {\"socialAccountId\":1310,\"provider\":\"google\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:22] local.INFO: [EncryptedTokenManager] Generating access token. {\"mode\":\"legacy\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:22] local.INFO: [SocialAccountService] Refreshing token from provider {\"socialAccountId\":1310,\"provider\":\"google\",\"refreshToken\":\"e34818922c2830a660813a63f6169a4a9a992ae2cccd7dc8dd7796cfdb470ef1\",\"state\":\"full-refresh\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:23] local.ERROR: [SocialAccountService] Failed to refresh token {\"socialAccountId\":1310,\"provider\":\"google\",\"responseBody\":{\"error\":\"invalid_grant\",\"error_description\":\"Bad Request\"}} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:23] local.INFO: [SocialAccountObserver] Saving model {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:23] local.ERROR: [SocialAccountService] Failed to refresh token {\"socialAccountId\":1310,\"provider\":\"google\",\"reason\":\"Flow refresh required.\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:23] local.INFO: [SocialAccountService] Fetching token {\"socialAccountId\":1333,\"provider\":\"google\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:23] local.INFO: [SocialAccountService] Token needs refreshing {\"socialAccountId\":1333,\"provider\":\"google\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:23] local.INFO: [EncryptedTokenManager] Generating access token. {\"mode\":\"legacy\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:23] local.INFO: [SocialAccountService] Refreshing token from provider {\"socialAccountId\":1333,\"provider\":\"google\",\"refreshToken\":\"6c902986546d8e8da1dc539b046cdc1d458f519acc972e5b5f1d6a1a295165e0\",\"state\":\"full-refresh\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:23] local.ERROR: [SocialAccountService] Failed to refresh token {\"socialAccountId\":1333,\"provider\":\"google\",\"responseBody\":{\"error\":\"unauthorized_client\",\"error_description\":\"Unauthorized\"}} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:23] local.INFO: [SocialAccountObserver] Saving model {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:23] local.ERROR: [SocialAccountService] Failed to refresh token {\"socialAccountId\":1333,\"provider\":\"google\",\"reason\":\"Flow refresh required.\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:24] local.INFO: [SocialAccountService] Fetching token {\"socialAccountId\":1368,\"provider\":\"google\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:24] local.INFO: [SocialAccountService] Token needs refreshing {\"socialAccountId\":1368,\"provider\":\"google\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:24] local.INFO: [EncryptedTokenManager] Generating access token. {\"mode\":\"legacy\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:24] local.INFO: [SocialAccountService] Refreshing token from provider {\"socialAccountId\":1368,\"provider\":\"google\",\"refreshToken\":\"d2f128898ff8543bd16b69cfae37896ab85119b0f5ed2b431d739593bb600333\",\"state\":\"full-refresh\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:24] local.ERROR: [SocialAccountService] Failed to refresh token {\"socialAccountId\":1368,\"provider\":\"google\",\"responseBody\":{\"error\":\"invalid_grant\",\"error_description\":\"Bad Request\"}} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:24] local.INFO: [SocialAccountObserver] Saving model {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:24] local.ERROR: [SocialAccountService] Failed to refresh token {\"socialAccountId\":1368,\"provider\":\"google\",\"reason\":\"Flow refresh required.\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:24] local.INFO: [SocialAccountService] Fetching token {\"socialAccountId\":1365,\"provider\":\"google\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:24] local.INFO: [SocialAccountService] Token needs refreshing {\"socialAccountId\":1365,\"provider\":\"google\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:24] local.INFO: [EncryptedTokenManager] Generating access token. {\"mode\":\"legacy\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:24] local.INFO: [SocialAccountService] Refreshing token from provider {\"socialAccountId\":1365,\"provider\":\"google\",\"refreshToken\":\"7676e4a9afcd082b413248ab5ec6e487021fec6a9bdf315860a59cefad9caad8\",\"state\":\"full-refresh\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:25] local.ERROR: [SocialAccountService] Failed to refresh token {\"socialAccountId\":1365,\"provider\":\"google\",\"responseBody\":{\"error\":\"unauthorized_client\",\"error_description\":\"Unauthorized\"}} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:25] local.INFO: [SocialAccountObserver] Saving model {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:25] local.ERROR: [SocialAccountService] Failed to refresh token {\"socialAccountId\":1365,\"provider\":\"google\",\"reason\":\"Flow refresh required.\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:25] local.INFO: [SocialAccountService] Fetching token {\"socialAccountId\":1364,\"provider\":\"google\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:25] local.INFO: [SocialAccountService] Token needs refreshing {\"socialAccountId\":1364,\"provider\":\"google\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:25] local.INFO: [EncryptedTokenManager] Generating access token. {\"mode\":\"legacy\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:25] local.INFO: [SocialAccountService] Refreshing token from provider {\"socialAccountId\":1364,\"provider\":\"google\",\"refreshToken\":\"dd5882ebce76e645292ce33ae74238abbb77c0a4ecc6a2bfe723cad82e72ba8e\",\"state\":\"full-refresh\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:25] local.ERROR: [SocialAccountService] Failed to refresh token {\"socialAccountId\":1364,\"provider\":\"google\",\"responseBody\":{\"error\":\"unauthorized_client\",\"error_description\":\"Unauthorized\"}} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:25] local.INFO: [SocialAccountObserver] Saving model {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:25] local.ERROR: [SocialAccountService] Failed to refresh token {\"socialAccountId\":1364,\"provider\":\"google\",\"reason\":\"Flow refresh required.\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:25] local.INFO: [SocialAccountService] Fetching token {\"socialAccountId\":1370,\"provider\":\"office\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:25] local.INFO: [SocialAccountService] Token needs refreshing {\"socialAccountId\":1370,\"provider\":\"office\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:25] local.INFO: [EncryptedTokenManager] Generating access token. {\"mode\":\"legacy\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:25] local.INFO: [SocialAccountService] Refreshing token from provider {\"socialAccountId\":1370,\"provider\":\"office\",\"refreshToken\":\"b7ee8035306d0043cea6e00e7c4fe14f745e44074a1194db62a31cdf8b70af3e\",\"state\":\"full-refresh\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:26] local.ERROR: [SocialAccountService] Failed to refresh token {\"socialAccountId\":1370,\"provider\":\"office\",\"responseBody\":\"{\\\"error\\\":\\\"invalid_client\\\",\\\"error_description\\\":\\\"AADSTS7000215: Invalid client secret provided. Ensure the secret being sent in the request is the client secret value, not the client secret ID, for a secret added to app 'bbcbb2ef-6200-4fae-82bd-d81f5dd738da'. Trace ID: 2be54cfa-1691-4388-a284-754fdf414400 Correlation ID: 9a934049-899b-45c5-99a5-15c9c470d8a2 Timestamp: 2026-04-27 10:28:26Z\\\",\\\"error_codes\\\":[7000215],\\\"timestamp\\\":\\\"2026-04-27 10:28:26Z\\\",\\\"trace_id\\\":\\\"2be54cfa-1691-4388-a284-754fdf414400\\\",\\\"correlation_id\\\":\\\"9a934049-899b-45c5-99a5-15c9c470d8a2\\\",\\\"error_uri\\\":\\\"https://login.microsoftonline.com/error?code=7000215\\\"}\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:26] local.INFO: [SocialAccountObserver] Saving model {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:26] local.ERROR: [SocialAccountService] Failed to refresh token {\"socialAccountId\":1370,\"provider\":\"office\",\"reason\":\"Flow refresh required.\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:26] local.INFO: [SocialAccountService] Fetching token {\"socialAccountId\":1202,\"provider\":\"office\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:26] local.INFO: [SocialAccountService] Token needs refreshing {\"socialAccountId\":1202,\"provider\":\"office\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:26] local.INFO: [EncryptedTokenManager] Generating access token. {\"mode\":\"legacy\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:26] local.INFO: [SocialAccountService] Refreshing token from provider {\"socialAccountId\":1202,\"provider\":\"office\",\"refreshToken\":\"b458799ccc29b21a6e2eb5260fdb63e49ccba21bf942a3973fb63799bd7f0afe\",\"state\":\"full-refresh\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:26] local.ERROR: [SocialAccountService] Failed to refresh token {\"socialAccountId\":1202,\"provider\":\"office\",\"responseBody\":\"{\\\"error\\\":\\\"invalid_client\\\",\\\"error_description\\\":\\\"AADSTS7000215: Invalid client secret provided. Ensure the secret being sent in the request is the client secret value, not the client secret ID, for a secret added to app 'bbcbb2ef-6200-4fae-82bd-d81f5dd738da'. Trace ID: 27729e9a-0943-41bd-9752-4ec9a8444500 Correlation ID: 824a9761-9387-4c53-ae31-5c242b1d4298 Timestamp: 2026-04-27 10:28:26Z\\\",\\\"error_codes\\\":[7000215],\\\"timestamp\\\":\\\"2026-04-27 10:28:26Z\\\",\\\"trace_id\\\":\\\"27729e9a-0943-41bd-9752-4ec9a8444500\\\",\\\"correlation_id\\\":\\\"824a9761-9387-4c53-ae31-5c242b1d4298\\\",\\\"error_uri\\\":\\\"https://login.microsoftonline.com/error?code=7000215\\\"}\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:26] local.INFO: [SocialAccountObserver] Saving model {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:26] local.ERROR: [SocialAccountService] Failed to refresh token {\"socialAccountId\":1202,\"provider\":\"office\",\"reason\":\"Flow refresh required.\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:26] local.INFO: [SocialAccountService] Fetching token {\"socialAccountId\":1502,\"provider\":\"google\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:26] local.INFO: [SocialAccountService] Token needs refreshing {\"socialAccountId\":1502,\"provider\":\"google\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:26] local.INFO: [EncryptedTokenManager] Generating access token. {\"mode\":\"legacy\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:26] local.INFO: [SocialAccountService] Refreshing token from provider {\"socialAccountId\":1502,\"provider\":\"google\",\"refreshToken\":\"d417c92ebaa137295a04675f715d0511ae8acac9d779b102eac50d6300116d3e\",\"state\":\"connected\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:27] local.INFO: [SocialAccountObserver] Saving model {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:27] local.INFO: [SocialAccountObserver] Access token was modified, encrypting {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:27] local.INFO: [SocialAccountService] Token refreshed {\"socialAccountId\":1502,\"provider\":\"google\",\"state\":\"connected\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:27] local.INFO: Calendar sync job dispatched {\"calendar_id\":501} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:27] local.INFO: [SocialAccountService] Fetching token {\"socialAccountId\":1300,\"provider\":\"google\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:27] local.INFO: [SocialAccountService] Token needs refreshing {\"socialAccountId\":1300,\"provider\":\"google\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:27] local.INFO: [EncryptedTokenManager] Generating access token. {\"mode\":\"legacy\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:27] local.INFO: [SocialAccountService] Refreshing token from provider {\"socialAccountId\":1300,\"provider\":\"google\",\"refreshToken\":\"4b811db0725fd9602a95943519a7da935e2a5065da7d9ebfcb170752e3e1ddb8\",\"state\":\"full-refresh\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:27] local.ERROR: [SocialAccountService] Failed to refresh token {\"socialAccountId\":1300,\"provider\":\"google\",\"responseBody\":{\"error\":\"invalid_grant\",\"error_description\":\"Account has been deleted\"}} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:27] local.INFO: [SocialAccountObserver] Saving model {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:27] local.ERROR: [SocialAccountService] Failed to refresh token {\"socialAccountId\":1300,\"provider\":\"google\",\"reason\":\"Flow refresh required.\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:27] local.INFO: [SocialAccountService] Fetching token {\"socialAccountId\":1409,\"provider\":\"google\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:27] local.INFO: [SocialAccountService] Token needs refreshing {\"socialAccountId\":1409,\"provider\":\"google\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:27] local.INFO: [EncryptedTokenManager] Generating access token. {\"mode\":\"legacy\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:27] local.INFO: [SocialAccountService] Refreshing token from provider {\"socialAccountId\":1409,\"provider\":\"google\",\"refreshToken\":\"e2a3f2d06894894eed1ee87d9db1ace77d4d42ee6e1288a8940ad2c10333b0c4\",\"state\":\"full-refresh\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:28] local.ERROR: [SocialAccountService] Failed to refresh token {\"socialAccountId\":1409,\"provider\":\"google\",\"responseBody\":{\"error\":\"invalid_grant\",\"error_description\":\"Bad Request\"}} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:28] local.INFO: [SocialAccountObserver] Saving model {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:28] local.ERROR: [SocialAccountService] Failed to refresh token {\"socialAccountId\":1409,\"provider\":\"google\",\"reason\":\"Flow refresh required.\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:28] local.INFO: [SocialAccountService] Fetching token {\"socialAccountId\":1352,\"provider\":\"google\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:28] local.INFO: [SocialAccountService] Token needs refreshing {\"socialAccountId\":1352,\"provider\":\"google\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:28] local.INFO: [EncryptedTokenManager] Generating access token. {\"mode\":\"legacy\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:28] local.INFO: [SocialAccountService] Refreshing token from provider {\"socialAccountId\":1352,\"provider\":\"google\",\"refreshToken\":\"dd4b16b00fdc1216da6b717c02338c073636e29162826b2de6db3f064fc029eb\",\"state\":\"full-refresh\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:28] local.ERROR: [SocialAccountService] Failed to refresh token {\"socialAccountId\":1352,\"provider\":\"google\",\"responseBody\":{\"error\":\"unauthorized_client\",\"error_description\":\"Unauthorized\"}} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:28] local.INFO: [SocialAccountObserver] Saving model {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:28] local.ERROR: [SocialAccountService] Failed to refresh token {\"socialAccountId\":1352,\"provider\":\"google\",\"reason\":\"Flow refresh required.\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:28] local.INFO: [SocialAccountService] Fetching token {\"socialAccountId\":1296,\"provider\":\"office\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:28] local.INFO: [SocialAccountService] Token needs refreshing {\"socialAccountId\":1296,\"provider\":\"office\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:28] local.INFO: [EncryptedTokenManager] Generating access token. {\"mode\":\"legacy\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:28] local.INFO: [SocialAccountService] Refreshing token from provider {\"socialAccountId\":1296,\"provider\":\"office\",\"refreshToken\":\"011ae723c9d800c674e0b4be76f49fc046dac7d501b66c59ef0d9549cfa56ae5\",\"state\":\"full-refresh\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:28] local.ERROR: [SocialAccountService] Failed to refresh token {\"socialAccountId\":1296,\"provider\":\"office\",\"responseBody\":\"{\\\"error\\\":\\\"invalid_client\\\",\\\"error_description\\\":\\\"AADSTS7000215: Invalid client secret provided. Ensure the secret being sent in the request is the client secret value, not the client secret ID, for a secret added to app 'bbcbb2ef-6200-4fae-82bd-d81f5dd738da'. Trace ID: 73b326f0-9fcd-4ff5-ad61-edd6a0504500 Correlation ID: 6bd8fef3-b24c-46d8-8bba-a9b2ab41c108 Timestamp: 2026-04-27 10:28:28Z\\\",\\\"error_codes\\\":[7000215],\\\"timestamp\\\":\\\"2026-04-27 10:28:28Z\\\",\\\"trace_id\\\":\\\"73b326f0-9fcd-4ff5-ad61-edd6a0504500\\\",\\\"correlation_id\\\":\\\"6bd8fef3-b24c-46d8-8bba-a9b2ab41c108\\\",\\\"error_uri\\\":\\\"https://login.microsoftonline.com/error?code=7000215\\\"}\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:28] local.INFO: [SocialAccountObserver] Saving model {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:28] local.ERROR: [SocialAccountService] Failed to refresh token {\"socialAccountId\":1296,\"provider\":\"office\",\"reason\":\"Flow refresh required.\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:28] local.INFO: [SocialAccountService] Fetching token {\"socialAccountId\":391,\"provider\":\"office\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:28] local.INFO: [SocialAccountService] Token needs refreshing {\"socialAccountId\":391,\"provider\":\"office\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:28] local.INFO: [EncryptedTokenManager] Generating access token. {\"mode\":\"legacy\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:28] local.INFO: [SocialAccountService] Refreshing token from provider {\"socialAccountId\":391,\"provider\":\"office\",\"refreshToken\":\"00045eebae0f39b34887c6d53f92ae78064f7145e1f4b67754aebd03cfb2d881\",\"state\":\"full-refresh\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:28] local.INFO: [SocialAccountService] Fetching token {\"socialAccountId\":1502,\"provider\":\"google\"} {\"correlation_id\":\"8a9488be-81de-40f5-8554-2503eec42566\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:28] local.INFO: [SocialAccountService] Token retrieved {\"socialAccountId\":1502,\"provider\":\"google\"} {\"correlation_id\":\"8a9488be-81de-40f5-8554-2503eec42566\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:28] local.INFO: [EncryptedTokenManager] Generating access token. {\"mode\":\"legacy\"} {\"correlation_id\":\"8a9488be-81de-40f5-8554-2503eec42566\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:29] local.INFO: [Calendar] Processing sync {\"calendarId\":\"a33076c1-8d97-431a-99f0-85c9524e118b\",\"from\":null,\"to\":null,\"delta\":\"CIiFh8TP44kDEIiFh8TP44kDGAUgkZvkzgIokZvkzgI=\",\"last_sync\":\"2024-12-09 07:12:53\",\"dateMode\":\"daily\"} {\"correlation_id\":\"8a9488be-81de-40f5-8554-2503eec42566\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:29] local.INFO: [CrmOwnerResolver] Integration owner matched as CRM Owner {\"crm_provider\":\"integration-app\",\"crm_owner\":1695,\"team_id\":3143} {\"correlation_id\":\"8a9488be-81de-40f5-8554-2503eec42566\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:29] local.INFO: [SocialAccountService] Fetching token {\"socialAccountId\":1502,\"provider\":\"google\"} {\"correlation_id\":\"8a9488be-81de-40f5-8554-2503eec42566\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:29] local.INFO: [SocialAccountService] Token retrieved {\"socialAccountId\":1502,\"provider\":\"google\"} {\"correlation_id\":\"8a9488be-81de-40f5-8554-2503eec42566\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:29] local.INFO: [EncryptedTokenManager] Generating access token. {\"mode\":\"legacy\"} {\"correlation_id\":\"8a9488be-81de-40f5-8554-2503eec42566\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:29] local.ERROR: [SocialAccountService] Failed to refresh token {\"socialAccountId\":391,\"provider\":\"office\",\"responseBody\":\"{\\\"error\\\":\\\"invalid_client\\\",\\\"error_description\\\":\\\"AADSTS7000215: Invalid client secret provided. Ensure the secret being sent in the request is the client secret value, not the client secret ID, for a secret added to app 'bbcbb2ef-6200-4fae-82bd-d81f5dd738da'. Trace ID: c5f1db6f-22fb-41cc-bd89-c46497634200 Correlation ID: 7b4df0cc-bf50-4017-9b1a-e25ffb6fe9bf Timestamp: 2026-04-27 10:28:29Z\\\",\\\"error_codes\\\":[7000215],\\\"timestamp\\\":\\\"2026-04-27 10:28:29Z\\\",\\\"trace_id\\\":\\\"c5f1db6f-22fb-41cc-bd89-c46497634200\\\",\\\"correlation_id\\\":\\\"7b4df0cc-bf50-4017-9b1a-e25ffb6fe9bf\\\",\\\"error_uri\\\":\\\"https://login.microsoftonline.com/error?code=7000215\\\"}\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:29] local.INFO: [SocialAccountObserver] Saving model {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:29] local.ERROR: [SocialAccountService] Failed to refresh token {\"socialAccountId\":391,\"provider\":\"office\",\"reason\":\"Flow refresh required.\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:29] local.INFO: [SocialAccountService] Fetching token {\"socialAccountId\":1271,\"provider\":\"office\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:29] local.INFO: [SocialAccountService] Token needs refreshing {\"socialAccountId\":1271,\"provider\":\"office\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:29] local.INFO: [EncryptedTokenManager] Generating access token. {\"mode\":\"legacy\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:29] local.INFO: [SocialAccountService] Refreshing token from provider {\"socialAccountId\":1271,\"provider\":\"office\",\"refreshToken\":\"118cde2c06993147b07ccaec4cbcd5026a819dea6c71081166a492933e392afb\",\"state\":\"full-refresh\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:29] local.INFO: [Google Calendar] Failed to watch channel for calendar {\"calendarId\":\"a33076c1-8d97-431a-99f0-85c9524e118b\",\"code\":400,\"reason\":\"{\n \\\"error\\\": {\n \\\"errors\\\": [\n {\n \\\"domain\\\": \\\"global\\\",\n \\\"reason\\\": \\\"push.webhookUrlNotHttps\\\",\n \\\"message\\\": \\\"WebHook callback must be HTTPS: /webhook/calendar/google?resourceType=event\\\"\n }\n ],\n \\\"code\\\": 400,\n \\\"message\\\": \\\"WebHook callback must be HTTPS: /webhook/calendar/google?resourceType=event\\\"\n }\n}\"} {\"correlation_id\":\"8a9488be-81de-40f5-8554-2503eec42566\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:29] local.WARNING: [Calendar] Sync failed {\"calendarId\":\"a33076c1-8d97-431a-99f0-85c9524e118b\",\"code\":400,\"reason\":\"{\n \\\"error\\\": {\n \\\"errors\\\": [\n {\n \\\"domain\\\": \\\"global\\\",\n \\\"reason\\\": \\\"push.webhookUrlNotHttps\\\",\n \\\"message\\\": \\\"WebHook callback must be HTTPS: /webhook/calendar/google?resourceType=event\\\"\n }\n ],\n \\\"code\\\": 400,\n \\\"message\\\": \\\"WebHook callback must be HTTPS: /webhook/calendar/google?resourceType=event\\\"\n }\n}\"} {\"correlation_id\":\"8a9488be-81de-40f5-8554-2503eec42566\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:29] local.ERROR: [SocialAccountService] Failed to refresh token {\"socialAccountId\":1271,\"provider\":\"office\",\"responseBody\":\"{\\\"error\\\":\\\"invalid_client\\\",\\\"error_description\\\":\\\"AADSTS7000215: Invalid client secret provided. Ensure the secret being sent in the request is the client secret value, not the client secret ID, for a secret added to app 'bbcbb2ef-6200-4fae-82bd-d81f5dd738da'. Trace ID: 8d6e82ba-88c3-4ec9-95ee-a4a905803900 Correlation ID: 6ede51d5-704d-4e24-9fb7-88f69f3da5db Timestamp: 2026-04-27 10:28:29Z\\\",\\\"error_codes\\\":[7000215],\\\"timestamp\\\":\\\"2026-04-27 10:28:29Z\\\",\\\"trace_id\\\":\\\"8d6e82ba-88c3-4ec9-95ee-a4a905803900\\\",\\\"correlation_id\\\":\\\"6ede51d5-704d-4e24-9fb7-88f69f3da5db\\\",\\\"error_uri\\\":\\\"https://login.microsoftonline.com/error?code=7000215\\\"}\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:30] local.INFO: [SocialAccountObserver] Saving model {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:30] local.ERROR: [SocialAccountService] Failed to refresh token {\"socialAccountId\":1271,\"provider\":\"office\",\"reason\":\"Flow refresh required.\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:30] local.INFO: [SocialAccountService] Fetching token {\"socialAccountId\":1351,\"provider\":\"google\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:30] local.INFO: [SocialAccountService] Token needs refreshing {\"socialAccountId\":1351,\"provider\":\"google\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:30] local.INFO: [EncryptedTokenManager] Generating access token. {\"mode\":\"legacy\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:30] local.INFO: [SocialAccountService] Refreshing token from provider {\"socialAccountId\":1351,\"provider\":\"google\",\"refreshToken\":\"4271d15b9e60a606439caddc68337f783e472c85b03dacff14d1b6dfded9051c\",\"state\":\"full-refresh\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:30] local.ERROR: [SocialAccountService] Failed to refresh token {\"socialAccountId\":1351,\"provider\":\"google\",\"responseBody\":{\"error\":\"invalid_grant\",\"error_description\":\"Token has been expired or revoked.\"}} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:30] local.INFO: [SocialAccountObserver] Saving model {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:30] local.ERROR: [SocialAccountService] Failed to refresh token {\"socialAccountId\":1351,\"provider\":\"google\",\"reason\":\"Flow refresh required.\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:30] local.INFO: [SocialAccountService] Fetching token {\"socialAccountId\":1366,\"provider\":\"google\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:30] local.INFO: [SocialAccountService] Token needs refreshing {\"socialAccountId\":1366,\"provider\":\"google\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:30] local.INFO: [EncryptedTokenManager] Generating access token. {\"mode\":\"legacy\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:30] local.INFO: [SocialAccountService] Refreshing token from provider {\"socialAccountId\":1366,\"provider\":\"google\",\"refreshToken\":\"ae21385059b2eebfd43f68aecd56eccd702a1aabb6598f1f7ab594ed8af491b4\",\"state\":\"full-refresh\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:30] local.ERROR: [SocialAccountService] Failed to refresh token {\"socialAccountId\":1366,\"provider\":\"google\",\"responseBody\":{\"error\":\"invalid_grant\",\"error_description\":\"Bad Request\"}} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:30] local.INFO: [SocialAccountObserver] Saving model {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:30] local.ERROR: [SocialAccountService] Failed to refresh token {\"socialAccountId\":1366,\"provider\":\"google\",\"reason\":\"Flow refresh required.\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:30] local.INFO: [SocialAccountService] Fetching token {\"socialAccountId\":1115,\"provider\":\"google\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:30] local.INFO: [SocialAccountService] Token needs refreshing {\"socialAccountId\":1115,\"provider\":\"google\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:30] local.INFO: [EncryptedTokenManager] Generating access token. {\"mode\":\"legacy\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:30] local.INFO: [SocialAccountService] Refreshing token from provider {\"socialAccountId\":1115,\"provider\":\"google\",\"refreshToken\":\"356b60f12e262a5e24d3042386ef47d6a6cfe3074c242f4426edcec8646192b1\",\"state\":\"connected\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:30] local.INFO: [SocialAccountObserver] Saving model {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:30] local.INFO: [SocialAccountObserver] Access token was modified, encrypting {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:31] local.INFO: [SocialAccountService] Token refreshed {\"socialAccountId\":1115,\"provider\":\"google\",\"state\":\"connected\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:31] local.INFO: Calendar sync job dispatched {\"calendar_id\":378} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:31] local.INFO: [SocialAccountService] Fetching token {\"socialAccountId\":1421,\"provider\":\"office\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:31] local.INFO: [SocialAccountService] Token needs refreshing {\"socialAccountId\":1421,\"provider\":\"office\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:31] local.INFO: [EncryptedTokenManager] Generating access token. {\"mode\":\"legacy\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:31] local.INFO: [SocialAccountService] Refreshing token from provider {\"socialAccountId\":1421,\"provider\":\"office\",\"refreshToken\":\"9e3e287aa0cc900726e3d4eead2600fb64aa34b437790f6a1fd8048306cb2db9\",\"state\":\"connected\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:31] local.INFO: [SocialAccountService] Fetching token {\"socialAccountId\":1115,\"provider\":\"google\"} {\"correlation_id\":\"8fdf411d-8a4f-46c1-a46a-cc84f3c2f2b1\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:31] local.INFO: [SocialAccountService] Token retrieved {\"socialAccountId\":1115,\"provider\":\"google\"} {\"correlation_id\":\"8fdf411d-8a4f-46c1-a46a-cc84f3c2f2b1\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:31] local.INFO: [EncryptedTokenManager] Generating access token. {\"mode\":\"legacy\"} {\"correlation_id\":\"8fdf411d-8a4f-46c1-a46a-cc84f3c2f2b1\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:31] local.INFO: [Calendar] Processing sync {\"calendarId\":\"2676cb6d-f86c-427e-bf78-591e388e3c1e\",\"from\":null,\"to\":null,\"delta\":\"CJ_x49O3jpIDEJ_x49O3jpIDGAUgw67KlwMow67KlwM=\",\"last_sync\":\"2026-01-19 07:48:40\",\"dateMode\":\"daily\"} {\"correlation_id\":\"8fdf411d-8a4f-46c1-a46a-cc84f3c2f2b1\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:31] local.WARNING: [Pipedrive] Account not connected for user {\"userId\":\"e6538737-e7b4-455f-a37a-3e79b665a220\",\"account\":{\"Jiminny\\\\Models\\\\SocialAccount\":{\"id\":1116,\"sociable_id\":241,\"provider_user_id\":\"19555731\",\"expires\":1775683749,\"refresh_token_expires\":null,\"provider\":\"pipedrive\",\"state\":\"full-refresh\",\"auth_scope\":\"base,deals:full,activities:full,contacts:full,search:read\",\"retry_after\":null,\"created_at\":\"2023-09-08 09:44:29\",\"updated_at\":\"2026-04-08 22:58:34\"}}} {\"correlation_id\":\"8fdf411d-8a4f-46c1-a46a-cc84f3c2f2b1\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:31] local.INFO: [CrmOwnerResolver] Integration owner is not connected, attempting team members {\"crm_provider\":\"pipedrive\",\"crm_owner\":241,\"team_id\":19} {\"correlation_id\":\"8fdf411d-8a4f-46c1-a46a-cc84f3c2f2b1\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:31] local.INFO: [CrmOwnerResolver] No team members found with active crm connection {\"crm_provider\":\"pipedrive\",\"team_id\":19} {\"correlation_id\":\"8fdf411d-8a4f-46c1-a46a-cc84f3c2f2b1\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:31] local.INFO: [CrmOwnerResolver] No team member found with active crm connection {\"crm_provider\":\"pipedrive\",\"team_id\":19} {\"correlation_id\":\"8fdf411d-8a4f-46c1-a46a-cc84f3c2f2b1\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:31] local.WARNING: [Calendar] CRM disconnected for user so events will not be matched {\"provider\":\"pipedrive\",\"user_id\":241,\"message\":\"Your Pipedrive account has become disconnected. Please login to Jiminny to reconnect.\"} {\"correlation_id\":\"8fdf411d-8a4f-46c1-a46a-cc84f3c2f2b1\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:31] local.INFO: [SocialAccountService] Fetching token {\"socialAccountId\":1115,\"provider\":\"google\"} {\"correlation_id\":\"8fdf411d-8a4f-46c1-a46a-cc84f3c2f2b1\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:31] local.INFO: [SocialAccountService] Token retrieved {\"socialAccountId\":1115,\"provider\":\"google\"} {\"correlation_id\":\"8fdf411d-8a4f-46c1-a46a-cc84f3c2f2b1\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:31] local.INFO: [EncryptedTokenManager] Generating access token. {\"mode\":\"legacy\"} {\"correlation_id\":\"8fdf411d-8a4f-46c1-a46a-cc84f3c2f2b1\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:31] local.INFO: [Google Calendar] Failed to watch channel for calendar {\"calendarId\":\"2676cb6d-f86c-427e-bf78-591e388e3c1e\",\"code\":400,\"reason\":\"{\n \\\"error\\\": {\n \\\"errors\\\": [\n {\n \\\"domain\\\": \\\"global\\\",\n \\\"reason\\\": \\\"push.webhookUrlNotHttps\\\",\n \\\"message\\\": \\\"WebHook callback must be HTTPS: /webhook/calendar/google?resourceType=event\\\"\n }\n ],\n \\\"code\\\": 400,\n \\\"message\\\": \\\"WebHook callback must be HTTPS: /webhook/calendar/google?resourceType=event\\\"\n }\n}\"} {\"correlation_id\":\"8fdf411d-8a4f-46c1-a46a-cc84f3c2f2b1\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:31] local.WARNING: [Calendar] Sync failed {\"calendarId\":\"2676cb6d-f86c-427e-bf78-591e388e3c1e\",\"code\":400,\"reason\":\"{\n \\\"error\\\": {\n \\\"errors\\\": [\n {\n \\\"domain\\\": \\\"global\\\",\n \\\"reason\\\": \\\"push.webhookUrlNotHttps\\\",\n \\\"message\\\": \\\"WebHook callback must be HTTPS: /webhook/calendar/google?resourceType=event\\\"\n }\n ],\n \\\"code\\\": 400,\n \\\"message\\\": \\\"WebHook callback must be HTTPS: /webhook/calendar/google?resourceType=event\\\"\n }\n}\"} {\"correlation_id\":\"8fdf411d-8a4f-46c1-a46a-cc84f3c2f2b1\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:32] local.INFO: [SocialAccountObserver] Saving model {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:32] local.INFO: [SocialAccountObserver] Access token was modified, encrypting {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:32] local.INFO: [SocialAccountObserver] Refresh token was modified, encrypting {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:32] local.INFO: [SocialAccountService] Token refreshed {\"socialAccountId\":1421,\"provider\":\"office\",\"state\":\"connected\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:32] local.INFO: Calendar sync job dispatched {\"calendar_id\":504} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:32] local.NOTICE: Calendar sync end {\"retrieved_calendars\":31,\"processed_calendars\":3} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:32] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage for command {\"command\":\"calendar:sync\",\"memoryBeforeCommandInMb\":60.0,\"memoryAfterCommandInMB\":62.0,\"memoryPeakBeforeCommandInMb\":99.727,\"memoryPeakAfterCommandInMB\":99.727} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:33] local.INFO: [SocialAccountService] Fetching token {\"socialAccountId\":1421,\"provider\":\"office\"} {\"correlation_id\":\"52b2ff86-82c7-4ed0-b528-35d7d00d69f0\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:33] local.INFO: [SocialAccountService] Token retrieved {\"socialAccountId\":1421,\"provider\":\"office\"} {\"correlation_id\":\"52b2ff86-82c7-4ed0-b528-35d7d00d69f0\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:33] local.INFO: [EncryptedTokenManager] Generating access token. {\"mode\":\"legacy\"} {\"correlation_id\":\"52b2ff86-82c7-4ed0-b528-35d7d00d69f0\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:34] local.INFO: [Calendar] Processing sync {\"calendarId\":\"9e8b1a2c-1a8f-42bd-b161-810fc0baf540\",\"from\":null,\"to\":null,\"delta\":\"R0usmcdvmMuZCBYV0hguCMx0nGVsTQEodM41jSlu-5eY2JUTYHZXE-bDro2tnepp8q_Vq3ITNS8CkncjGjcBfaHkza05dCTIlaXcSIoPNGOl9vNckYy_ZhVgE1SWvgRcCeVT-SyogdB9hP-oaWbH9r51OQrrzAwmalK5aCksyUVh9jG_A-mGVzRPnUB4SKa77o5P659lpx1egON48YU2tQ.D2oErKma-mQrBdXxAKdH7beUkp6HS2PXUWYLCgvbP2c\",\"last_sync\":\"2026-04-27 06:28:41\",\"dateMode\":\"daily\"} {\"correlation_id\":\"52b2ff86-82c7-4ed0-b528-35d7d00d69f0\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:34] local.INFO: [SocialAccountService] Fetching token {\"socialAccountId\":1499,\"provider\":\"hubspot\"} {\"correlation_id\":\"52b2ff86-82c7-4ed0-b528-35d7d00d69f0\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:34] local.INFO: [SocialAccountService] Token retrieved {\"socialAccountId\":1499,\"provider\":\"hubspot\"} {\"correlation_id\":\"52b2ff86-82c7-4ed0-b528-35d7d00d69f0\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:34] local.INFO: [EncryptedTokenManager] Generating access token. {\"mode\":\"legacy\"} {\"correlation_id\":\"52b2ff86-82c7-4ed0-b528-35d7d00d69f0\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:34] local.INFO: [CrmOwnerResolver] Integration owner matched as CRM Owner {\"crm_provider\":\"hubspot\",\"crm_owner\":89,\"team_id\":2} {\"correlation_id\":\"52b2ff86-82c7-4ed0-b528-35d7d00d69f0\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:34] local.INFO: [MS Office Calendar] Skipping delta sync for daily mode {\"calendarId\":\"9e8b1a2c-1a8f-42bd-b161-810fc0baf540\"} {\"correlation_id\":\"52b2ff86-82c7-4ed0-b528-35d7d00d69f0\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:29:06] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage before starting command {\"command\":\"meeting-bot:schedule-bot\",\"memoryBeforeCommandInMb\":60.0,\"memoryPeakBeforeCommandInMb\":99.727} {\"correlation_id\":\"7e95c05b-50c8-4e0b-a01c-efeed6af544c\",\"trace_id\":\"07db4d4f-d6e7-44d9-8e54-4f038ea69afb\"}\n[2026-04-27 10:29:06] local.INFO: [ScheduleBotCommand] Number of activities to be captured: 0 {\"correlation_id\":\"7e95c05b-50c8-4e0b-a01c-efeed6af544c\",\"trace_id\":\"07db4d4f-d6e7-44d9-8e54-4f038ea69afb\"}\n[2026-04-27 10:29:06] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage for command {\"command\":\"meeting-bot:schedule-bot\",\"memoryBeforeCommandInMb\":60.0,\"memoryAfterCommandInMB\":62.0,\"memoryPeakBeforeCommandInMb\":99.727,\"memoryPeakAfterCommandInMB\":99.727} {\"correlation_id\":\"7e95c05b-50c8-4e0b-a01c-efeed6af544c\",\"trace_id\":\"07db4d4f-d6e7-44d9-8e54-4f038ea69afb\"}\n[2026-04-27 10:29:09] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage before starting command {\"command\":\"dialers:monitor-activities\",\"memoryBeforeCommandInMb\":60.0,\"memoryPeakBeforeCommandInMb\":99.727} {\"correlation_id\":\"e6e7fbf8-6e80-4529-858e-a4e95d0d3805\",\"trace_id\":\"f91a115a-9fdc-46ce-b9f2-13a1e6d0e325\"}\n[2026-04-27 10:29:09] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage for command {\"command\":\"dialers:monitor-activities\",\"memoryBeforeCommandInMb\":60.0,\"memoryAfterCommandInMB\":62.0,\"memoryPeakBeforeCommandInMb\":99.727,\"memoryPeakAfterCommandInMB\":99.727} {\"correlation_id\":\"e6e7fbf8-6e80-4529-858e-a4e95d0d3805\",\"trace_id\":\"f91a115a-9fdc-46ce-b9f2-13a1e6d0e325\"}\n[2026-04-27 10:29:10] local.NOTICE: Monitoring start {\"correlation_id\":\"211e5e36-1c6c-4198-b928-d2f1525ab0ea\",\"trace_id\":\"c26e980d-6e32-426d-9667-f1d902432b8d\"}\n[2026-04-27 10:29:10] local.NOTICE: Monitoring end {\"correlation_id\":\"211e5e36-1c6c-4198-b928-d2f1525ab0ea\",\"trace_id\":\"c26e980d-6e32-426d-9667-f1d902432b8d\"}\n[2026-04-27 10:29:12] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage before starting command {\"command\":\"mailbox:skip-lists:refresh\",\"memoryBeforeCommandInMb\":60.0,\"memoryPeakBeforeCommandInMb\":99.727} {\"correlation_id\":\"684bdf68-76ee-42dd-9d97-62f414a33b1b\",\"trace_id\":\"284c98ef-5b7f-4572-996c-72c5bc2995f9\"}\n[2026-04-27 10:29:13] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage for command {\"command\":\"mailbox:skip-lists:refresh\",\"memoryBeforeCommandInMb\":60.0,\"memoryAfterCommandInMB\":62.0,\"memoryPeakBeforeCommandInMb\":99.727,\"memoryPeakAfterCommandInMB\":99.727} {\"correlation_id\":\"684bdf68-76ee-42dd-9d97-62f414a33b1b\",\"trace_id\":\"284c98ef-5b7f-4572-996c-72c5bc2995f9\"}\n[2026-04-27 10:29:18] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage before starting command {\"command\":\"mailbox:batch:process\",\"memoryBeforeCommandInMb\":60.0,\"memoryPeakBeforeCommandInMb\":99.727} {\"correlation_id\":\"6e74b148-ae54-42ff-ab72-2ded18d10ff4\",\"trace_id\":\"d05bc9a0-450c-45b2-be3b-225a551ed909\"}\n[2026-04-27 10:29:18] local.INFO: [EmailSchedule] STARTING batch process {\"host\":\"docker_lamp_1\"} {\"correlation_id\":\"6e74b148-ae54-42ff-ab72-2ded18d10ff4\",\"trace_id\":\"d05bc9a0-450c-45b2-be3b-225a551ed909\"}\n[2026-04-27 10:29:18] local.INFO: [EmailSchedule] FINISHED batch process {\"host\":\"docker_lamp_1\",\"processed\":0} {\"correlation_id\":\"6e74b148-ae54-42ff-ab72-2ded18d10ff4\",\"trace_id\":\"d05bc9a0-450c-45b2-be3b-225a551ed909\"}\n[2026-04-27 10:29:18] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage for command {\"command\":\"mailbox:batch:process\",\"memoryBeforeCommandInMb\":60.0,\"memoryAfterCommandInMB\":62.0,\"memoryPeakBeforeCommandInMb\":99.727,\"memoryPeakAfterCommandInMB\":99.727} {\"correlation_id\":\"6e74b148-ae54-42ff-ab72-2ded18d10ff4\",\"trace_id\":\"d05bc9a0-450c-45b2-be3b-225a551ed909\"}\n[2026-04-27 10:30:08] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage before starting command {\"command\":\"meeting-bot:schedule-bot\",\"memoryBeforeCommandInMb\":60.0,\"memoryPeakBeforeCommandInMb\":99.727} {\"correlation_id\":\"24b807e0-b413-4ecc-a89b-365280be88ac\",\"trace_id\":\"542ac70c-6756-4409-8224-0844a7576d9c\"}\n[2026-04-27 10:30:08] local.INFO: [ScheduleBotCommand] Number of activities to be captured: 0 {\"correlation_id\":\"24b807e0-b413-4ecc-a89b-365280be88ac\",\"trace_id\":\"542ac70c-6756-4409-8224-0844a7576d9c\"}\n[2026-04-27 10:30:08] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage for command {\"command\":\"meeting-bot:schedule-bot\",\"memoryBeforeCommandInMb\":60.0,\"memoryAfterCommandInMB\":62.0,\"memoryPeakBeforeCommandInMb\":99.727,\"memoryPeakAfterCommandInMB\":99.727} {\"correlation_id\":\"24b807e0-b413-4ecc-a89b-365280be88ac\",\"trace_id\":\"542ac70c-6756-4409-8224-0844a7576d9c\"}\n[2026-04-27 10:30:10] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage before starting command {\"command\":\"dialers:monitor-activities\",\"memoryBeforeCommandInMb\":60.0,\"memoryPeakBeforeCommandInMb\":99.727} {\"correlation_id\":\"2711ca04-b4a7-4f3a-b0b2-5826fea4da3b\",\"trace_id\":\"2613ffca-f181-4d91-ad55-ec1bdd97ee7e\"}\n[2026-04-27 10:30:10] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage for command {\"command\":\"dialers:monitor-activities\",\"memoryBeforeCommandInMb\":60.0,\"memoryAfterCommandInMB\":62.0,\"memoryPeakBeforeCommandInMb\":99.727,\"memoryPeakAfterCommandInMB\":99.727} {\"correlation_id\":\"2711ca04-b4a7-4f3a-b0b2-5826fea4da3b\",\"trace_id\":\"2613ffca-f181-4d91-ad55-ec1bdd97ee7e\"}\n[2026-04-27 10:30:17] local.NOTICE: Monitoring start {\"correlation_id\":\"e1537475-8481-4144-b9f5-596ea8034b8a\",\"trace_id\":\"f5561d0a-0cb7-4c72-8bde-cce617183529\"}\n[2026-04-27 10:30:17] local.NOTICE: Monitoring end {\"correlation_id\":\"e1537475-8481-4144-b9f5-596ea8034b8a\",\"trace_id\":\"f5561d0a-0cb7-4c72-8bde-cce617183529\"}\n[2026-04-27 10:30:21] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage before starting command {\"command\":\"mailbox:skip-lists:refresh\",\"memoryBeforeCommandInMb\":60.0,\"memoryPeakBeforeCommandInMb\":99.727} {\"correlation_id\":\"8a911b60-16a0-416e-934e-3b7e0280e452\",\"trace_id\":\"3c2bbdd6-985d-4624-b42b-fd9cc2b4756f\"}\n[2026-04-27 10:30:21] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage for command {\"command\":\"mailbox:skip-lists:refresh\",\"memoryBeforeCommandInMb\":60.0,\"memoryAfterCommandInMB\":62.0,\"memoryPeakBeforeCommandInMb\":99.727,\"memoryPeakAfterCommandInMB\":99.727} {\"correlation_id\":\"8a911b60-16a0-416e-934e-3b7e0280e452\",\"trace_id\":\"3c2bbdd6-985d-4624-b42b-fd9cc2b4756f\"}\n[2026-04-27 10:30:25] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage before starting command {\"command\":\"mailbox:batch:process\",\"memoryBeforeCommandInMb\":60.0,\"memoryPeakBeforeCommandInMb\":99.727} {\"correlation_id\":\"f62efaa6-201f-43b2-bf45-9619982dffb1\",\"trace_id\":\"f1ad3d60-af4e-4427-9d77-6aa71cfed172\"}\n[2026-04-27 10:30:25] local.INFO: [EmailSchedule] STARTING batch process {\"host\":\"docker_lamp_1\"} {\"correlation_id\":\"f62efaa6-201f-43b2-bf45-9619982dffb1\",\"trace_id\":\"f1ad3d60-af4e-4427-9d77-6aa71cfed172\"}\n[2026-04-27 10:30:25] local.INFO: [EmailSchedule] FINISHED batch process {\"host\":\"docker_lamp_1\",\"processed\":0} {\"correlation_id\":\"f62efaa6-201f-43b2-bf45-9619982dffb1\",\"trace_id\":\"f1ad3d60-af4e-4427-9d77-6aa71cfed172\"}\n[2026-04-27 10:30:25] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage for command {\"command\":\"mailbox:batch:process\",\"memoryBeforeCommandInMb\":60.0,\"memoryAfterCommandInMB\":62.0,\"memoryPeakBeforeCommandInMb\":99.727,\"memoryPeakAfterCommandInMB\":99.727} {\"correlation_id\":\"f62efaa6-201f-43b2-bf45-9619982dffb1\",\"trace_id\":\"f1ad3d60-af4e-4427-9d77-6aa71cfed172\"}\n[2026-04-27 10:30:30] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage before starting command {\"command\":\"conference:monitor:count\",\"memoryBeforeCommandInMb\":60.0,\"memoryPeakBeforeCommandInMb\":99.727} {\"correlation_id\":\"cf6dc132-ecb8-4efc-bead-06bf1c96cae6\",\"trace_id\":\"9e2b073b-940d-498a-babe-115f633dd954\"}\n[2026-04-27 10:30:30] local.INFO: Running conference:monitor:count command for activities in (2026-04-27 10:28:00, 2026-04-27 10:30:00] {\"correlation_id\":\"cf6dc132-ecb8-4efc-bead-06bf1c96cae6\",\"trace_id\":\"9e2b073b-940d-498a-babe-115f633dd954\"}\n[2026-04-27 10:30:30] local.INFO: [conference:monitor:count] No activities found in (2026-04-27 10:28:00, 2026-04-27 10:30:00] {\"correlation_id\":\"cf6dc132-ecb8-4efc-bead-06bf1c96cae6\",\"trace_id\":\"9e2b073b-940d-498a-babe-115f633dd954\"}\n[2026-04-27 10:30:30] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage for command {\"command\":\"conference:monitor:count\",\"memoryBeforeCommandInMb\":60.0,\"memoryAfterCommandInMB\":62.0,\"memoryPeakBeforeCommandInMb\":99.727,\"memoryPeakAfterCommandInMB\":99.727} {\"correlation_id\":\"cf6dc132-ecb8-4efc-bead-06bf1c96cae6\",\"trace_id\":\"9e2b073b-940d-498a-babe-115f633dd954\"}\n[2026-04-27 10:30:32] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage before starting command {\"command\":\"activity:purge-stale\",\"memoryBeforeCommandInMb\":60.0,\"memoryPeakBeforeCommandInMb\":99.727} {\"correlation_id\":\"f455260d-d1c1-456f-8937-d4bb026d9078\",\"trace_id\":\"f8c2e585-0137-4103-9e16-1b4ce05df2ef\"}\n[2026-04-27 10:30:32] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage for command {\"command\":\"activity:purge-stale\",\"memoryBeforeCommandInMb\":60.0,\"memoryAfterCommandInMB\":62.0,\"memoryPeakBeforeCommandInMb\":99.727,\"memoryPeakAfterCommandInMB\":99.727} {\"correlation_id\":\"f455260d-d1c1-456f-8937-d4bb026d9078\",\"trace_id\":\"f8c2e585-0137-4103-9e16-1b4ce05df2ef\"}\n[2026-04-27 10:30:35] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage before starting command {\"command\":\"mailbox:text-relay:sync\",\"memoryBeforeCommandInMb\":60.0,\"memoryPeakBeforeCommandInMb\":99.727} {\"correlation_id\":\"be7d3c6a-664a-42e8-a4b3-cbd021296404\",\"trace_id\":\"0b250827-4c02-4237-8b34-e5ddd004eba1\"}\n[2026-04-27 10:30:35] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage for command {\"command\":\"mailbox:text-relay:sync\",\"memoryBeforeCommandInMb\":60.0,\"memoryAfterCommandInMB\":62.0,\"memoryPeakBeforeCommandInMb\":99.727,\"memoryPeakAfterCommandInMB\":99.727} {\"correlation_id\":\"be7d3c6a-664a-42e8-a4b3-cbd021296404\",\"trace_id\":\"0b250827-4c02-4237-8b34-e5ddd004eba1\"}\n[2026-04-27 10:30:37] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage before starting command {\"command\":\"conference:pre-meeting-notification\",\"memoryBeforeCommandInMb\":60.0,\"memoryPeakBeforeCommandInMb\":99.727} {\"correlation_id\":\"5d8fbdf6-2737-4d73-babe-699c88279e1f\",\"trace_id\":\"0882a963-b02f-4049-b670-12b92f80829a\"}\n[2026-04-27 10:30:37] local.INFO: Running pre-meeting notification command {\"correlation_id\":\"5d8fbdf6-2737-4d73-babe-699c88279e1f\",\"trace_id\":\"0882a963-b02f-4049-b670-12b92f80829a\"}\n[2026-04-27 10:30:37] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage for command {\"command\":\"conference:pre-meeting-notification\",\"memoryBeforeCommandInMb\":60.0,\"memoryAfterCommandInMB\":62.0,\"memoryPeakBeforeCommandInMb\":99.727,\"memoryPeakAfterCommandInMB\":99.727} {\"correlation_id\":\"5d8fbdf6-2737-4d73-babe-699c88279e1f\",\"trace_id\":\"0882a963-b02f-4049-b670-12b92f80829a\"}\n[2026-04-27 10:30:39] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage before starting command {\"command\":\"conference:monitor:start\",\"memoryBeforeCommandInMb\":60.0,\"memoryPeakBeforeCommandInMb\":99.727} {\"correlation_id\":\"e8968e47-9acd-4bd4-9e7d-3c415f46df05\",\"trace_id\":\"287fba85-f395-41ab-813d-49cb5d0ea06d\"}\n[2026-04-27 10:30:39] local.INFO: Running conference:monitor:start command for activities in (2026-04-27 10:20:00, 2026-04-27 10:25:00] {\"correlation_id\":\"e8968e47-9acd-4bd4-9e7d-3c415f46df05\",\"trace_id\":\"287fba85-f395-41ab-813d-49cb5d0ea06d\"}\n[2026-04-27 10:30:39] local.INFO: [conference:monitor:start] No activities found in (2026-04-27 10:20:00, 2026-04-27 10:25:00] {\"correlation_id\":\"e8968e47-9acd-4bd4-9e7d-3c415f46df05\",\"trace_id\":\"287fba85-f395-41ab-813d-49cb5d0ea06d\"}\n[2026-04-27 10:30:39] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage for command {\"command\":\"conference:monitor:start\",\"memoryBeforeCommandInMb\":60.0,\"memoryAfterCommandInMB\":62.0,\"memoryPeakBeforeCommandInMb\":99.727,\"memoryPeakAfterCommandInMB\":99.727} {\"correlation_id\":\"e8968e47-9acd-4bd4-9e7d-3c415f46df05\",\"trace_id\":\"287fba85-f395-41ab-813d-49cb5d0ea06d\"}\n[2026-04-27 10:30:43] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage before starting command {\"command\":\"conference:monitor:end\",\"memoryBeforeCommandInMb\":60.0,\"memoryPeakBeforeCommandInMb\":99.727} {\"correlation_id\":\"b87eb011-7108-46bd-aa90-975ebe41f483\",\"trace_id\":\"1f54c2a6-fad1-4804-87d8-4972a378e91b\"}\n[2026-04-27 10:30:43] local.INFO: conference:monitor:end:Jiminny\\Console\\Commands\\Activities\\MonitorMeetingEndCommand::logActivitiesEnded {\"from\":\"10:25\",\"to\":\"10:30\"} {\"correlation_id\":\"b87eb011-7108-46bd-aa90-975ebe41f483\",\"trace_id\":\"1f54c2a6-fad1-4804-87d8-4972a378e91b\"}\n[2026-04-27 10:30:44] local.INFO: conference:monitor:end:Jiminny\\Console\\Commands\\Activities\\MonitorMeetingEndCommand::logActivitiesWithUnfinishedSession {\"from\":\"00:20\",\"to\":\"00:25\"} {\"correlation_id\":\"b87eb011-7108-46bd-aa90-975ebe41f483\",\"trace_id\":\"1f54c2a6-fad1-4804-87d8-4972a378e91b\"}\n[2026-04-27 10:30:44] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage for command {\"command\":\"conference:monitor:end\",\"memoryBeforeCommandInMb\":60.0,\"memoryAfterCommandInMB\":62.0,\"memoryPeakBeforeCommandInMb\":99.727,\"memoryPeakAfterCommandInMB\":99.727} {\"correlation_id\":\"b87eb011-7108-46bd-aa90-975ebe41f483\",\"trace_id\":\"1f54c2a6-fad1-4804-87d8-4972a378e91b\"}\n[2026-04-27 10:30:51] local.NOTICE: Repairing HubSpot tokens start {\"correlation_id\":\"2e6dfa3d-f07d-4eee-8232-6138133e2fbb\",\"trace_id\":\"41dea86e-0ecc-414e-8303-e60d8ae09976\"}\n[2026-04-27 10:30:51] local.INFO: Trying to refresh HubSpot token {\"account_id\":59,\"updated_at\":\"2025-10-03 09:32:05\"} {\"correlation_id\":\"2e6dfa3d-f07d-4eee-8232-6138133e2fbb\",\"trace_id\":\"41dea86e-0ecc-414e-8303-e60d8ae09976\"}\n[2026-04-27 10:30:51] local.INFO: [EncryptedTokenManager] Generating access token. {\"mode\":\"legacy\"} {\"correlation_id\":\"2e6dfa3d-f07d-4eee-8232-6138133e2fbb\",\"trace_id\":\"41dea86e-0ecc-414e-8303-e60d8ae09976\"}\n[2026-04-27 10:30:51] local.INFO: [SocialAccountService] Refreshing token from provider {\"socialAccountId\":59,\"provider\":\"hubspot\",\"refreshToken\":\"97b78f6e2cc49965c00c2492b602b02708b1392551e6b3f113fbaa48992af90b\",\"state\":\"full-refresh\"} {\"correlation_id\":\"2e6dfa3d-f07d-4eee-8232-6138133e2fbb\",\"trace_id\":\"41dea86e-0ecc-414e-8303-e60d8ae09976\"}\n[2026-04-27 10:30:52] local.ERROR: Failed to refresh HubSpot token {\"account_id\":59,\"updated_at\":\"2025-10-03 09:32:05\",\"reason\":\"missing or invalid refresh token\",\"previous\":\"\"} {\"correlation_id\":\"2e6dfa3d-f07d-4eee-8232-6138133e2fbb\",\"trace_id\":\"41dea86e-0ecc-414e-8303-e60d8ae09976\"}\n[2026-04-27 10:30:52] local.INFO: Trying to refresh HubSpot token {\"account_id\":306,\"updated_at\":\"2023-11-27 09:30:03\"} {\"correlation_id\":\"2e6dfa3d-f07d-4eee-8232-6138133e2fbb\",\"trace_id\":\"41dea86e-0ecc-414e-8303-e60d8ae09976\"}\n[2026-04-27 10:30:52] local.INFO: [EncryptedTokenManager] Generating access token. {\"mode\":\"legacy\"} {\"correlation_id\":\"2e6dfa3d-f07d-4eee-8232-6138133e2fbb\",\"trace_id\":\"41dea86e-0ecc-414e-8303-e60d8ae09976\"}\n[2026-04-27 10:30:52] local.INFO: [SocialAccountService] Refreshing token from provider {\"socialAccountId\":306,\"provider\":\"hubspot\",\"refreshToken\":\"6fa6aa8cc641d131231acc3470f5c03cb3b07b2e580fb18f8acb3b1dbb72549b\",\"state\":\"full-refresh\"} {\"correlation_id\":\"2e6dfa3d-f07d-4eee-8232-6138133e2fbb\",\"trace_id\":\"41dea86e-0ecc-414e-8303-e60d8ae09976\"}\n[2026-04-27 10:30:52] local.ERROR: Failed to refresh HubSpot token {\"account_id\":306,\"updated_at\":\"2023-11-27 09:30:03\",\"reason\":\"missing or invalid refresh token\",\"previous\":\"\"} {\"correlation_id\":\"2e6dfa3d-f07d-4eee-8232-6138133e2fbb\",\"trace_id\":\"41dea86e-0ecc-414e-8303-e60d8ae09976\"}\n[2026-04-27 10:30:52] local.INFO: Trying to refresh HubSpot token {\"account_id\":1372,\"updated_at\":\"2025-10-02 14:47:06\"} {\"correlation_id\":\"2e6dfa3d-f07d-4eee-8232-6138133e2fbb\",\"trace_id\":\"41dea86e-0ecc-414e-8303-e60d8ae09976\"}\n[2026-04-27 10:30:52] local.INFO: [EncryptedTokenManager] Generating access token. {\"mode\":\"legacy\"} {\"correlation_id\":\"2e6dfa3d-f07d-4eee-8232-6138133e2fbb\",\"trace_id\":\"41dea86e-0ecc-414e-8303-e60d8ae09976\"}\n[2026-04-27 10:30:52] local.INFO: [SocialAccountService] Refreshing token from provider {\"socialAccountId\":1372,\"provider\":\"hubspot\",\"refreshToken\":\"9aa73948c761da29dce46c177cf9aee1fde483a44169ca38723f9f0597d7a8c4\",\"state\":\"full-refresh\"} {\"correlation_id\":\"2e6dfa3d-f07d-4eee-8232-6138133e2fbb\",\"trace_id\":\"41dea86e-0ecc-414e-8303-e60d8ae09976\"}\n[2026-04-27 10:30:52] local.ERROR: Failed to refresh HubSpot token {\"account_id\":1372,\"updated_at\":\"2025-10-02 14:47:06\",\"reason\":\"missing or invalid refresh token\",\"previous\":\"\"} {\"correlation_id\":\"2e6dfa3d-f07d-4eee-8232-6138133e2fbb\",\"trace_id\":\"41dea86e-0ecc-414e-8303-e60d8ae09976\"}\n[2026-04-27 10:30:52] local.NOTICE: Repairing HubSpot tokens end {\"total\":3,\"fixed\":0,\"failed\":3} {\"correlation_id\":\"2e6dfa3d-f07d-4eee-8232-6138133e2fbb\",\"trace_id\":\"41dea86e-0ecc-414e-8303-e60d8ae09976\"}\n[2026-04-27 10:30:56] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage before starting command {\"command\":\"jiminny:transcription:retry-failed\",\"memoryBeforeCommandInMb\":60.0,\"memoryPeakBeforeCommandInMb\":99.727} {\"correlation_id\":\"a6655a64-38ad-4c65-96f1-f5a294dbb343\",\"trace_id\":\"52cdca32-c0c8-4215-81a2-4263bbde2158\"}\n[2026-04-27 10:30:56] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage before starting command {\"command\":\"conference:pre-meeting-reminder\",\"memoryBeforeCommandInMb\":60.0,\"memoryPeakBeforeCommandInMb\":99.727} {\"correlation_id\":\"ec2d49dd-ab36-4f4e-8a60-7bf5ea384cac\",\"trace_id\":\"b3e89960-9a27-4bf7-8b55-95f5704f9a10\"}\n[2026-04-27 10:30:57] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage for command {\"command\":\"jiminny:transcription:retry-failed\",\"memoryBeforeCommandInMb\":60.0,\"memoryAfterCommandInMB\":62.0,\"memoryPeakBeforeCommandInMb\":99.727,\"memoryPeakAfterCommandInMB\":99.727} {\"correlation_id\":\"a6655a64-38ad-4c65-96f1-f5a294dbb343\",\"trace_id\":\"52cdca32-c0c8-4215-81a2-4263bbde2158\"}\n[2026-04-27 10:30:57] local.INFO: [HubSpot Journal Polling] Getting offset from database {\"offset\":\"\",\"jiminny_team_id\":1} {\"correlation_id\":\"1f928e93-b17e-4518-a1b9-a8280d1d95e8\",\"trace_id\":\"ba203ed6-fe98-4fd7-aa62-8de9fb8ffeca\"}\n[2026-04-27 10:30:57] local.INFO: [HubSpot Journal Command] Starting polling service {\"correlation_id\":\"1f928e93-b17e-4518-a1b9-a8280d1d95e8\",\"trace_id\":\"ba203ed6-fe98-4fd7-aa62-8de9fb8ffeca\"}\n[2026-04-27 10:30:57] local.INFO: [HubSpot Journal Polling] Service starting {\"memory_limit\":\"256M\",\"max_execution_time\":\"0\",\"initial_memory_mb\":62.0} {\"correlation_id\":\"1f928e93-b17e-4518-a1b9-a8280d1d95e8\",\"trace_id\":\"ba203ed6-fe98-4fd7-aa62-8de9fb8ffeca\"}\n[2026-04-27 10:30:57] local.INFO: [HubSpot Journal Polling] Acquired polling lock {\"expires_at\":\"2026-04-27T10:32:57.492763Z\"} {\"correlation_id\":\"1f928e93-b17e-4518-a1b9-a8280d1d95e8\",\"trace_id\":\"ba203ed6-fe98-4fd7-aa62-8de9fb8ffeca\"}\n[2026-04-27 10:30:57] local.INFO: [HubSpot Journal Polling] Getting offset from database {\"offset\":\"\",\"jiminny_team_id\":1} {\"correlation_id\":\"1f928e93-b17e-4518-a1b9-a8280d1d95e8\",\"trace_id\":\"ba203ed6-fe98-4fd7-aa62-8de9fb8ffeca\"}\n[2026-04-27 10:30:57] local.INFO: [HubSpot Journal API] Fetching latest journal entry {\"url\":\"https://api.hubapi.com/webhooks/v4/journal/latest\"} {\"correlation_id\":\"1f928e93-b17e-4518-a1b9-a8280d1d95e8\",\"trace_id\":\"ba203ed6-fe98-4fd7-aa62-8de9fb8ffeca\"}\n[2026-04-27 10:30:57] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage for command {\"command\":\"conference:pre-meeting-reminder\",\"memoryBeforeCommandInMb\":60.0,\"memoryAfterCommandInMB\":62.0,\"memoryPeakBeforeCommandInMb\":99.727,\"memoryPeakAfterCommandInMB\":99.727} {\"correlation_id\":\"ec2d49dd-ab36-4f4e-8a60-7bf5ea384cac\",\"trace_id\":\"b3e89960-9a27-4bf7-8b55-95f5704f9a10\"}\n[2026-04-27 10:30:58] local.INFO: [HubSpot Journal Polling] No data {\"correlation_id\":\"1f928e93-b17e-4518-a1b9-a8280d1d95e8\",\"trace_id\":\"ba203ed6-fe98-4fd7-aa62-8de9fb8ffeca\"}\n[2026-04-27 10:31:02] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage before starting command {\"command\":\"crm:reset-governor\",\"memoryBeforeCommandInMb\":60.0,\"memoryPeakBeforeCommandInMb\":99.727} {\"correlation_id\":\"f261cd75-c916-4f21-b4f4-856fe0ed315c\",\"trace_id\":\"6db8819d-ffaa-4fe8-8ccd-0dcc326b8cdc\"}\n[2026-04-27 10:31:02] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage for command {\"command\":\"crm:reset-governor\",\"memoryBeforeCommandInMb\":60.0,\"memoryAfterCommandInMB\":62.0,\"memoryPeakBeforeCommandInMb\":99.727,\"memoryPeakAfterCommandInMB\":99.727} {\"correlation_id\":\"f261cd75-c916-4f21-b4f4-856fe0ed315c\",\"trace_id\":\"6db8819d-ffaa-4fe8-8ccd-0dcc326b8cdc\"}\n[2026-04-27 10:31:03] local.INFO: [HubSpot Journal Polling] Getting offset from database {\"offset\":\"\",\"jiminny_team_id\":1} {\"correlation_id\":\"1f928e93-b17e-4518-a1b9-a8280d1d95e8\",\"trace_id\":\"ba203ed6-fe98-4fd7-aa62-8de9fb8ffeca\"}\n[2026-04-27 10:31:03] local.INFO: [HubSpot Journal API] Fetching latest journal entry {\"url\":\"https://api.hubapi.com/webhooks/v4/journal/latest\"} {\"correlation_id\":\"1f928e93-b17e-4518-a1b9-a8280d1d95e8\",\"trace_id\":\"ba203ed6-fe98-4fd7-aa62-8de9fb8ffeca\"}\n[2026-04-27 10:31:03] local.INFO: [HubSpot Journal Polling] No data {\"correlation_id\":\"1f928e93-b17e-4518-a1b9-a8280d1d95e8\",\"trace_id\":\"ba203ed6-fe98-4fd7-aa62-8de9fb8ffeca\"}\n[2026-04-27 10:31:06] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage before starting command {\"command\":\"activity:sync\",\"memoryBeforeCommandInMb\":60.0,\"memoryPeakBeforeCommandInMb\":99.727} {\"correlation_id\":\"f9151ed9-60aa-4b2b-b962-9eab5f75405b\",\"trace_id\":\"dba432b8-cb4b-4e76-8684-01d96f91e453\"}\n[2026-04-27 10:31:07] local.INFO: Dispatching activity sync job {\"import_id\":811739,\"provider\":\"twilio-flex\",\"team\":\"jiminny\"} {\"correlation_id\":\"f9151ed9-60aa-4b2b-b962-9eab5f75405b\",\"trace_id\":\"dba432b8-cb4b-4e76-8684-01d96f91e453\"}\n[2026-04-27 10:31:07] local.INFO: Dispatching activity sync job {\"import_id\":811740,\"provider\":\"xant\",\"team\":\"jiminny\"} {\"correlation_id\":\"f9151ed9-60aa-4b2b-b962-9eab5f75405b\",\"trace_id\":\"dba432b8-cb4b-4e76-8684-01d96f91e453\"}\n[2026-04-27 10:31:07] local.INFO: Dispatching activity sync job {\"import_id\":811741,\"provider\":\"apollo\",\"team\":\"jiminny\"} {\"correlation_id\":\"f9151ed9-60aa-4b2b-b962-9eab5f75405b\",\"trace_id\":\"dba432b8-cb4b-4e76-8684-01d96f91e453\"}\n[2026-04-27 10:31:07] local.INFO: Dispatching activity sync job {\"import_id\":811742,\"provider\":\"groove\",\"team\":\"jiminny\"} {\"correlation_id\":\"f9151ed9-60aa-4b2b-b962-9eab5f75405b\",\"trace_id\":\"dba432b8-cb4b-4e76-8684-01d96f91e453\"}\n[2026-04-27 10:31:07] local.INFO: Dispatching activity sync job {\"import_id\":811743,\"provider\":\"twilio-video\",\"team\":\"jiminny\"} {\"correlation_id\":\"f9151ed9-60aa-4b2b-b962-9eab5f75405b\",\"trace_id\":\"dba432b8-cb4b-4e76-8684-01d96f91e453\"}\n[2026-04-27 10:31:07] local.INFO: Dispatching activity sync job {\"import_id\":811744,\"provider\":\"hubspot\",\"team\":\"hubspot\"} {\"correlation_id\":\"f9151ed9-60aa-4b2b-b962-9eab5f75405b\",\"trace_id\":\"dba432b8-cb4b-4e76-8684-01d96f91e453\"}\n[2026-04-27 10:31:07] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage for command {\"command\":\"activity:sync\",\"memoryBeforeCommandInMb\":60.0,\"memoryAfterCommandInMB\":62.0,\"memoryPeakBeforeCommandInMb\":99.727,\"memoryPeakAfterCommandInMB\":99.727} {\"correlation_id\":\"f9151ed9-60aa-4b2b-b962-9eab5f75405b\",\"trace_id\":\"dba432b8-cb4b-4e76-8684-01d96f91e453\"}\n[2026-04-27 10:31:07] local.WARNING: [Salesforce] Account not connected for user {\"userId\":\"cdf8b554-d951-4758-bc2b-c1b85d1cd0b9\",\"account\":null} {\"correlation_id\":\"e7a521c5-fad4-48c2-b880-d572b5d237f4\",\"trace_id\":\"dba432b8-cb4b-4e76-8684-01d96f91e453\"}\n[2026-04-27 10:31:07] local.INFO: [CrmOwnerResolver] Integration owner is not connected, attempting team members {\"crm_provider\":\"salesforce\",\"crm_owner\":3,\"team_id\":1} {\"correlation_id\":\"e7a521c5-fad4-48c2-b880-d572b5d237f4\",\"trace_id\":\"dba432b8-cb4b-4e76-8684-01d96f91e453\"}\n[2026-04-27 10:31:07] local.INFO: [SocialAccountService] Fetching token {\"socialAccountId\":1500,\"provider\":\"salesforce\"} {\"correlation_id\":\"e7a521c5-fad4-48c2-b880-d572b5d237f4\",\"trace_id\":\"dba432b8-cb4b-4e76-8684-01d96f91e453\"}\n[2026-04-27 10:31:07] local.INFO: [SocialAccountService] Token retrieved {\"socialAccountId\":1500,\"provider\":\"salesforce\"} {\"correlation_id\":\"e7a521c5-fad4-48c2-b880-d572b5d237f4\",\"trace_id\":\"dba432b8-cb4b-4e76-8684-01d96f91e453\"}\n[2026-04-27 10:31:07] local.INFO: [EncryptedTokenManager] Generating access token. {\"mode\":\"legacy\"} {\"correlation_id\":\"e7a521c5-fad4-48c2-b880-d572b5d237f4\",\"trace_id\":\"dba432b8-cb4b-4e76-8684-01d96f91e453\"}\n[2026-04-27 10:31:07] local.INFO: [CrmOwnerResolver] TeamMember found with active crm connection {\"crm_provider\":\"salesforce\",\"crm_owner\":143,\"team_id\":1} {\"correlation_id\":\"e7a521c5-fad4-48c2-b880-d572b5d237f4\",\"trace_id\":\"dba432b8-cb4b-4e76-8684-01d96f91e453\"}\n[2026-04-27 10:31:07] local.INFO: [SocialAccountService] Fetching token {\"socialAccountId\":1194,\"provider\":\"twilio-flex\"} {\"correlation_id\":\"e7a521c5-fad4-48c2-b880-d572b5d237f4\",\"trace_id\":\"dba432b8-cb4b-4e76-8684-01d96f91e453\"}\n[2026-04-27 10:31:07] local.INFO: [SocialAccountService] Token retrieved {\"socialAccountId\":1194,\"provider\":\"twilio-flex\"} {\"correlation_id\":\"e7a521c5-fad4-48c2-b880-d572b5d237f4\",\"trace_id\":\"dba432b8-cb4b-4e76-8684-01d96f91e453\"}\n[2026-04-27 10:31:07] local.INFO: [EncryptedTokenManager] Generating access token. {\"mode\":\"legacy\"} {\"correlation_id\":\"e7a521c5-fad4-48c2-b880-d572b5d237f4\",\"trace_id\":\"dba432b8-cb4b-4e76-8684-01d96f91e453\"}\n[2026-04-27 10:31:07] local.INFO: [SyncActivity] Start {\"import_id\":811739,\"provider\":\"twilio-flex\",\"provider_id\":317,\"team\":\"jiminny\",\"team_id\":1} {\"correlation_id\":\"e7a521c5-fad4-48c2-b880-d572b5d237f4\",\"trace_id\":\"dba432b8-cb4b-4e76-8684-01d96f91e453\"}\n[2026-04-27 10:31:07] local.NOTICE: [TwilioFlex] Calls import start {\"correlation_id\":\"e7a521c5-fad4-48c2-b880-d572b5d237f4\",\"trace_id\":\"dba432b8-cb4b-4e76-8684-01d96f91e453\"}\n[2026-04-27 10:31:08] local.ALERT: [SyncActivity] Failed {\"import_id\":811739,\"provider\":\"twilio-flex\",\"provider_id\":317,\"team\":\"jiminny\",\"team_id\":1,\"reason\":\"[HTTP 401] Unable to fetch page: Authenticate\",\"file\":\"/home/jiminny/vendor/twilio/sdk/src/Twilio/Page.php\",\"line\":60} {\"correlation_id\":\"e7a521c5-fad4-48c2-b880-d572b5d237f4\",\"trace_id\":\"dba432b8-cb4b-4e76-8684-01d96f91e453\"}\n[2026-04-27 10:31:08] local.INFO: [HubSpot Journal Polling] Getting offset from database {\"offset\":\"\",\"jiminny_team_id\":1} {\"correlation_id\":\"1f928e93-b17e-4518-a1b9-a8280d1d95e8\",\"trace_id\":\"ba203ed6-fe98-4fd7-aa62-8de9fb8ffeca\"}\n[2026-04-27 10:31:08] local.INFO: [HubSpot Journal API] Fetching latest journal entry {\"url\":\"https://api.hubapi.com/webhooks/v4/journal/latest\"} {\"correlation_id\":\"1f928e93-b17e-4518-a1b9-a8280d1d95e8\",\"trace_id\":\"ba203ed6-fe98-4fd7-aa62-8de9fb8ffeca\"}\n[2026-04-27 10:31:09] local.INFO: [SocialAccountService] Fetching token {\"socialAccountId\":1500,\"provider\":\"salesforce\"} {\"correlation_id\":\"73848601-79bb-4161-91ae-37acf4385d24\",\"trace_id\":\"dba432b8-cb4b-4e76-8684-01d96f91e453\"}\n[2026-04-27 10:31:09] local.INFO: [SocialAccountService] Token retrieved {\"socialAccountId\":1500,\"provider\":\"salesforce\"} {\"correlation_id\":\"73848601-79bb-4161-91ae-37acf4385d24\",\"trace_id\":\"dba432b8-cb4b-4e76-8684-01d96f91e453\"}\n[2026-04-27 10:31:09] local.INFO: [EncryptedTokenManager] Generating access token. {\"mode\":\"legacy\"} {\"correlation_id\":\"73848601-79bb-4161-91ae-37acf4385d24\",\"trace_id\":\"dba432b8-cb4b-4e76-8684-01d96f91e453\"}\n[2026-04-27 10:31:09] local.INFO: [CrmOwnerResolver] Integration owner matched as CRM Owner {\"crm_provider\":\"salesforce\",\"crm_owner\":143,\"team_id\":1} {\"correlation_id\":\"73848601-79bb-4161-91ae-37acf4385d24\",\"trace_id\":\"dba432b8-cb4b-4e76-8684-01d96f91e453\"}\n[2026-04-27 10:31:09] local.INFO: [SocialAccountService] Fetching token {\"socialAccountId\":1500,\"provider\":\"salesforce\"} {\"correlation_id\":\"73848601-79bb-4161-91ae-37acf4385d24\",\"trace_id\":\"dba432b8-cb4b-4e76-8684-01d96f91e453\"}\n[2026-04-27 10:31:09] local.INFO: [HubSpot Journal Polling] No data {\"correlation_id\":\"1f928e93-b17e-4518-a1b9-a8280d1d95e8\",\"trace_id\":\"ba203ed6-fe98-4fd7-aa62-8de9fb8ffeca\"}\n[2026-04-27 10:31:09] local.INFO: [SocialAccountService] Token retrieved {\"socialAccountId\":1500,\"provider\":\"salesforce\"} {\"correlation_id\":\"73848601-79bb-4161-91ae-37acf4385d24\",\"trace_id\":\"dba432b8-cb4b-4e76-8684-01d96f91e453\"}\n[2026-04-27 10:31:09] local.INFO: [EncryptedTokenManager] Generating access token. {\"mode\":\"legacy\"} {\"correlation_id\":\"73848601-79bb-4161-91ae-37acf4385d24\",\"trace_id\":\"dba432b8-cb4b-4e76-8684-01d96f91e453\"}\n[2026-04-27 10:31:09] local.INFO: [SyncActivity] Start {\"import_id\":811740,\"provider\":\"xant\",\"provider_id\":161,\"team\":\"jiminny\",\"team_id\":1} {\"correlation_id\":\"73848601-79bb-4161-91ae-37acf4385d24\",\"trace_id\":\"dba432b8-cb4b-4e76-8684-01d96f91e453\"}\n[2026-04-27 10:31:09] local.INFO: [Salesforce] Performing query {\"query\":\"\n SELECT Playbooks_Call_Date__c,Playbooks_Call_Recording__c,CreatedDate,TaskSubtype,CallType,CallDurationInSeconds,Id,OwnerId,WhoId,WhatId,Priority,ActivityDate,Subject,Description,Status,Type\n FROM Task\n WHERE IsDeleted = false\n AND LastModifiedDate >= :from\n AND LastModifiedDate <= :to\n ORDER BY LastModifiedDate ASC\n LIMIT :limit\",\"params\":{\"from\":\"2026-04-27T10:14:00Z\",\"to\":\"2026-04-27T10:30:00Z\",\"ownerId\":null,\"subType\":null,\"limit\":5000}} {\"correlation_id\":\"73848601-79bb-4161-91ae-37acf4385d24\",\"trace_id\":\"dba432b8-cb4b-4e76-8684-01d96f91e453\"}\n[2026-04-27 10:31:09] local.INFO: [Salesforce] Sending request {\"endpoint\":\"https://jiminny--stagingenv.sandbox.my.salesforce.com/services/data/v50.0/query/?q=%0A++++++++++++SELECT+Playbooks_Call_Date__c%2CPlaybooks_Call_Recording__c%2CCreatedDate%2CTaskSubtype%2CCallType%2CCallDurationInSeconds%2CId%2COwnerId%2CWhoId%2CWhatId%2CPriority%2CActivityDate%2CSubject%2CDescription%2CStatus%2CType%0A++++++++++++++FROM+Task%0A+++++++++++++WHERE+IsDeleted+%3D+false%0A+++++++++++++++AND+LastModifiedDate+%3E%3D+2026-04-27T10%3A14%3A00Z%0A+++++++++++++++AND+LastModifiedDate+%3C%3D+2026-04-27T10%3A30%3A00Z%0A++++++++++ORDER+BY+LastModifiedDate+ASC%0A+++++++++++++LIMIT+5000 GET\",\"team_id\":1} {\"correlation_id\":\"73848601-79bb-4161-91ae-37acf4385d24\",\"trace_id\":\"dba432b8-cb4b-4e76-8684-01d96f91e453\"}\n[2026-04-27 10:31:09] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage before starting command {\"command\":\"mailbox:batch:fail-stalled\",\"memoryBeforeCommandInMb\":60.0,\"memoryPeakBeforeCommandInMb\":99.727} {\"correlation_id\":\"0d30446d-c469-4ade-80dc-f57f997ea610\",\"trace_id\":\"61eea326-3f40-4a50-a5e6-bddd7fa25d27\"}\n[2026-04-27 10:31:09] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage for command {\"command\":\"mailbox:batch:fail-stalled\",\"memoryBeforeCommandInMb\":60.0,\"memoryAfterCommandInMB\":62.0,\"memoryPeakBeforeCommandInMb\":99.727,\"memoryPeakAfterCommandInMB\":99.727} {\"correlation_id\":\"0d30446d-c469-4ade-80dc-f57f997ea610\",\"trace_id\":\"61eea326-3f40-4a50-a5e6-bddd7fa25d27\"}\n[2026-04-27 10:31:09] local.INFO: [Xant (InsideSales)] No calls found. {\"correlation_id\":\"73848601-79bb-4161-91ae-37acf4385d24\",\"trace_id\":\"dba432b8-cb4b-4e76-8684-01d96f91e453\"}\n[2026-04-27 10:31:09] local.INFO: [SyncActivity] End {\"import_id\":811740,\"provider\":\"xant\",\"provider_id\":161,\"team\":\"jiminny\",\"team_id\":1} {\"correlation_id\":\"73848601-79bb-4161-91ae-37acf4385d24\",\"trace_id\":\"dba432b8-cb4b-4e76-8684-01d96f91e453\"}\n[2026-04-27 10:31:09] local.INFO: [SyncActivity] Memory usage {\"import_id\":811740,\"provider\":\"xant\",\"provider_id\":161,\"team\":\"jiminny\",\"team_id\":1,\"memory_usage\":25149472,\"memory_real_usage\":65011712,\"pid\":13616} {\"correlation_id\":\"73848601-79bb-4161-91ae-37acf4385d24\",\"trace_id\":\"dba432b8-cb4b-4e76-8684-01d96f91e453\"}\n[2026-04-27 10:31:09] local.INFO: [SocialAccountService] Fetching token {\"socialAccountId\":1500,\"provider\":\"salesforce\"} {\"correlation_id\":\"11c977fb-9603-4dde-a4d7-f821d47fcdc9\",\"trace_id\":\"dba432b8-cb4b-4e76-8684-01d96f91e453\"}\n[2026-04-27 10:31:09] local.INFO: [SocialAccountService] Token retrieved {\"socialAccountId\":1500,\"provider\":\"salesforce\"} {\"correlation_id\":\"11c977fb-9603-4dde-a4d7-f821d47fcdc9\",\"trace_id\":\"dba432b8-cb4b-4e76-8684-01d96f91e453\"}\n[2026-04-27 10:31:09] local.INFO: [EncryptedTokenManager] Generating access token. {\"mode\":\"legacy\"} {\"correlation_id\":\"11c977fb-9603-4dde-a4d7-f821d47fcdc9\",\"trace_id\":\"dba432b8-cb4b-4e76-8684-01d96f91e453\"}\n[2026-04-27 10:31:09] local.INFO: [CrmOwnerResolver] Integration owner matched as CRM Owner {\"crm_provider\":\"salesforce\",\"crm_owner\":143,\"team_id\":1} {\"correlation_id\":\"11c977fb-9603-4dde-a4d7-f821d47fcdc9\",\"trace_id\":\"dba432b8-cb4b-4e76-8684-01d96f91e453\"}\n[2026-04-27 10:31:09] local.INFO: [SocialAccountService] Fetching token {\"socialAccountId\":1500,\"provider\":\"salesforce\"} {\"correlation_id\":\"11c977fb-9603-4dde-a4d7-f821d47fcdc9\",\"trace_id\":\"dba432b8-cb4b-4e76-8684-01d96f91e453\"}\n[2026-04-27 10:31:09] local.INFO: [SocialAccountService] Token retrieved {\"socialAccountId\":1500,\"provider\":\"salesforce\"} {\"correlation_id\":\"11c977fb-9603-4dde-a4d7-f821d47fcdc9\",\"trace_id\":\"dba432b8-cb4b-4e76-8684-01d96f91e453\"}\n[2026-04-27 10:31:09] local.INFO: [EncryptedTokenManager] Generating access token. {\"mode\":\"legacy\"} {\"correlation_id\":\"11c977fb-9603-4dde-a4d7-f821d47fcdc9\",\"trace_id\":\"dba432b8-cb4b-4e76-8684-01d96f91e453\"}\n[2026-04-27 10:31:09] local.INFO: [SyncActivity] Start {\"import_id\":811741,\"provider\":\"apollo\",\"provider_id\":441,\"team\":\"jiminny\",\"team_id\":1} {\"correlation_id\":\"11c977fb-9603-4dde-a4d7-f821d47fcdc9\",\"trace_id\":\"dba432b8-cb4b-4e76-8684-01d96f91e453\"}\n[2026-04-27 10:31:09] local.INFO: [Salesforce] Performing query {\"query\":\"\n SELECT AccountId,CreatedDate,TaskSubtype,CallType,Id,OwnerId,WhoId,WhatId,Priority,ActivityDate,Subject,Description,Status,Type\n FROM Task\n WHERE IsDeleted = false\n AND LastModifiedDate >= :from\n AND LastModifiedDate <= :to\n ORDER BY LastModifiedDate ASC\n LIMIT :limit\",\"params\":{\"from\":\"2026-04-27T10:14:00Z\",\"to\":\"2026-04-27T10:30:00Z\",\"ownerId\":null,\"subType\":null,\"limit\":5000}} {\"correlation_id\":\"11c977fb-9603-4dde-a4d7-f821d47fcdc9\",\"trace_id\":\"dba432b8-cb4b-4e76-8684-01d96f91e453\"}\n[2026-04-27 10:31:09] local.INFO: [Salesforce] Sending request {\"endpoint\":\"https://jiminny--stagingenv.sandbox.my.salesforce.com/services/data/v50.0/query/?q=%0A++++++++++++SELECT+AccountId%2CCreatedDate%2CTaskSubtype%2CCallType%2CId%2COwnerId%2CWhoId%2CWhatId%2CPriority%2CActivityDate%2CSubject%2CDescription%2CStatus%2CType%0A++++++++++++++FROM+Task%0A+++++++++++++WHERE+IsDeleted+%3D+false%0A+++++++++++++++AND+LastModifiedDate+%3E%3D+2026-04-27T10%3A14%3A00Z%0A+++++++++++++++AND+LastModifiedDate+%3C%3D+2026-04-27T10%3A30%3A00Z%0A++++++++++ORDER+BY+LastModifiedDate+ASC%0A+++++++++++++LIMIT+5000 GET\",\"team_id\":1} {\"correlation_id\":\"11c977fb-9603-4dde-a4d7-f821d47fcdc9\",\"trace_id\":\"dba432b8-cb4b-4e76-8684-01d96f91e453\"}\n[2026-04-27 10:31:10] local.INFO: [Apollo] No calls found. {\"correlation_id\":\"11c977fb-9603-4dde-a4d7-f821d47fcdc9\",\"trace_id\":\"dba432b8-cb4b-4e76-8684-01d96f91e453\"}\n[2026-04-27 10:31:10] local.INFO: [SyncActivity] End {\"import_id\":811741,\"provider\":\"apollo\",\"provider_id\":441,\"team\":\"jiminny\",\"team_id\":1} {\"correlation_id\":\"11c977fb-9603-4dde-a4d7-f821d47fcdc9\",\"trace_id\":\"dba432b8-cb4b-4e76-8684-01d96f91e453\"}\n[2026-04-27 10:31:10] local.INFO: [SyncActivity] Memory usage {\"import_id\":811741,\"provider\":\"apollo\",\"provider_id\":441,\"team\":\"jiminny\",\"team_id\":1,\"memory_usage\":25527752,\"memory_real_usage\":65011712,\"pid\":13616} {\"correlation_id\":\"11c977fb-9603-4dde-a4d7-f821d47fcdc9\",\"trace_id\":\"dba432b8-cb4b-4e76-8684-01d96f91e453\"}\n[2026-04-27 10:31:10] local.INFO: [SocialAccountService] Fetching token {\"socialAccountId\":1500,\"provider\":\"salesforce\"} {\"correlation_id\":\"856f8092-8c5e-409d-8aa6-ffb8efd8bfa1\",\"trace_id\":\"dba432b8-cb4b-4e76-8684-01d96f91e453\"}\n[2026-04-27 10:31:10] local.INFO: [SocialAccountService] Token retrieved {\"socialAccountId\":1500,\"provider\":\"salesforce\"} {\"correlation_id\":\"856f8092-8c5e-409d-8aa6-ffb8efd8bfa1\",\"trace_id\":\"dba432b8-cb4b-4e76-8684-01d96f91e453\"}\n[2026-04-27 10:31:10] local.INFO: [EncryptedTokenManager] Generating access token. {\"mode\":\"legacy\"} {\"correlation_id\":\"856f8092-8c5e-409d-8aa6-ffb8efd8bfa1\",\"trace_id\":\"dba432b8-cb4b-4e76-8684-01d96f91e453\"}\n[2026-04-27 10:31:10] local.INFO: [CrmOwnerResolver] Integration owner matched as CRM Owner {\"crm_provider\":\"salesforce\",\"crm_owner\":143,\"team_id\":1} {\"correlation_id\":\"856f8092-8c5e-409d-8aa6-ffb8efd8bfa1\",\"trace_id\":\"dba432b8-cb4b-4e76-8684-01d96f91e453\"}\n[2026-04-27 10:31:10] local.INFO: [SocialAccountService] Fetching token {\"socialAccountId\":1500,\"provider\":\"salesforce\"} {\"correlation_id\":\"856f8092-8c5e-409d-8aa6-ffb8efd8bfa1\",\"trace_id\":\"dba432b8-cb4b-4e76-8684-01d96f91e453\"}\n[2026-04-27 10:31:10] local.INFO: [SocialAccountService] Token retrieved {\"socialAccountId\":1500,\"provider\":\"salesforce\"} {\"correlation_id\":\"856f8092-8c5e-409d-8aa6-ffb8efd8bfa1\",\"trace_id\":\"dba432b8-cb4b-4e76-8684-01d96f91e453\"}\n[2026-04-27 10:31:10] local.INFO: [EncryptedTokenManager] Generating access token. {\"mode\":\"legacy\"} {\"correlation_id\":\"856f8092-8c5e-409d-8aa6-ffb8efd8bfa1\",\"trace_id\":\"dba432b8-cb4b-4e76-8684-01d96f91e453\"}\n[2026-04-27 10:31:10] local.INFO: [SyncActivity] Start {\"import_id\":811742,\"provider\":\"groove\",\"provider_id\":228,\"team\":\"jiminny\",\"team_id\":1} {\"correlation_id\":\"856f8092-8c5e-409d-8aa6-ffb8efd8bfa1\",\"trace_id\":\"dba432b8-cb4b-4e76-8684-01d96f91e453\"}\n[2026-04-27 10:31:10] local.INFO: [Salesforce] Performing query {\"query\":\"\n SELECT call_recording_url__c,TaskSubtype,CreatedDate,CallType,CallDurationInSeconds,Id,OwnerId,WhoId,WhatId,Priority,ActivityDate,Subject,Description,Status,Type\n FROM Task\n WHERE IsDeleted = false\n AND LastModifiedDate >= :from\n AND LastModifiedDate <= :to\n ORDER BY LastModifiedDate ASC\n LIMIT :limit\",\"params\":{\"from\":\"2026-04-27T10:14:00Z\",\"to\":\"2026-04-27T10:30:00Z\",\"ownerId\":null,\"subType\":null,\"limit\":5000}} {\"correlation_id\":\"856f8092-8c5e-409d-8aa6-ffb8efd8bfa1\",\"trace_id\":\"dba432b8-cb4b-4e76-8684-01d96f91e453\"}\n[2026-04-27 10:31:10] local.INFO: [Salesforce] Sending request {\"endpoint\":\"https://jiminny--stagingenv.sandbox.my.salesforce.com/services/data/v50.0/query/?q=%0A++++++++++++SELECT+call_recording_url__c%2CTaskSubtype%2CCreatedDate%2CCallType%2CCallDurationInSeconds%2CId%2COwnerId%2CWhoId%2CWhatId%2CPriority%2CActivityDate%2CSubject%2CDescription%2CStatus%2CType%0A++++++++++++++FROM+Task%0A+++++++++++++WHERE+IsDeleted+%3D+false%0A+++++++++++++++AND+LastModifiedDate+%3E%3D+2026-04-27T10%3A14%3A00Z%0A+++++++++++++++AND+LastModifiedDate+%3C%3D+2026-04-27T10%3A30%3A00Z%0A++++++++++ORDER+BY+LastModifiedDate+ASC%0A+++++++++++++LIMIT+5000 GET\",\"team_id\":1} {\"correlation_id\":\"856f8092-8c5e-409d-8aa6-ffb8efd8bfa1\",\"trace_id\":\"dba432b8-cb4b-4e76-8684-01d96f91e453\"}\n[2026-04-27 10:31:10] local.ERROR: [Salesforce] Request exception [400] \nSELECT call_recording_url__c,TaskSubtype\n ^\nERROR at Row:1:Column:8\nNo such column 'call_recording_url__c' on entity 'Task'. If you are attempting to use a custom field, be sure to append the '__c' after the custom field name. Please reference your WSDL or the describe call for the appropriate names. {\"url\":\"https://jiminny--stagingenv.sandbox.my.salesforce.com/services/data/v50.0/query/?q=%0A++++++++++++SELECT+call_recording_url__c%2CTaskSubtype%2CCreatedDate%2CCallType%2CCallDurationInSeconds%2CId%2COwnerId%2CWhoId%2CWhatId%2CPriority%2CActivityDate%2CSubject%2CDescription%2CStatus%2CType%0A++++++++++++++FROM+Task%0A+++++++++++++WHERE+IsDeleted+%3D+false%0A+++++++++++++++AND+LastModifiedDate+%3E%3D+2026-04-27T10%3A14%3A00Z%0A+++++++++++++++AND+LastModifiedDate+%3C%3D+2026-04-27T10%3A30%3A00Z%0A++++++++++ORDER+BY+LastModifiedDate+ASC%0A+++++++++++++LIMIT+5000\",\"data\":{\"headers\":{\"Authorization\":\"Bearer 00D2g0000008hH4!AQEAQC41CLa_xP1bAbi9E2OUbAOpW..Wx961OtqTwJ3oWpOKs6yPdm47B4yKRBHbRmmJ.Lvd34sjr0gnAkV8fLb8IZWzIW93\"}},\"response\":{\"GuzzleHttp\\\\Psr7\\\\Stream\":\"[{\\\"message\\\":\\\"\\\\nSELECT call_recording_url__c,TaskSubtype\\\\n ^\\\\nERROR at Row:1:Column:8\\\\nNo such column 'call_recording_url__c' on entity 'Task'. If you are attempting to use a custom field, be sure to append the '__c' after the custom field name. Please reference your WSDL or the describe call for the appropriate names.\\\",\\\"errorCode\\\":\\\"INVALID_FIELD\\\"}]\"},\"fields\":[]} {\"correlation_id\":\"856f8092-8c5e-409d-8aa6-ffb8efd8bfa1\",\"trace_id\":\"dba432b8-cb4b-4e76-8684-01d96f91e453\"}\n[2026-04-27 10:31:10] local.ALERT: [SyncActivity] Failed {\"import_id\":811742,\"provider\":\"groove\",\"provider_id\":228,\"team\":\"jiminny\",\"team_id\":1,\"reason\":\"\nSELECT call_recording_url__c,TaskSubtype\n ^\nERROR at Row:1:Column:8\nNo such column 'call_recording_url__c' on entity 'Task'. If you are attempting to use a custom field, be sure to append the '__c' after the custom field name. Please reference your WSDL or the describe call for the appropriate names.\",\"file\":\"/home/jiminny/app/Services/Crm/Salesforce/Client.php\",\"line\":564} {\"correlation_id\":\"856f8092-8c5e-409d-8aa6-ffb8efd8bfa1\",\"trace_id\":\"dba432b8-cb4b-4e76-8684-01d96f91e453\"}\n[2026-04-27 10:31:10] local.INFO: [SocialAccountService] Fetching token {\"socialAccountId\":1500,\"provider\":\"salesforce\"} {\"correlation_id\":\"0ec6aef6-345c-4602-b6a1-646b95e481c0\",\"trace_id\":\"dba432b8-cb4b-4e76-8684-01d96f91e453\"}\n[2026-04-27 10:31:10] local.INFO: [SocialAccountService] Token retrieved {\"socialAccountId\":1500,\"provider\":\"salesforce\"} {\"correlation_id\":\"0ec6aef6-345c-4602-b6a1-646b95e481c0\",\"trace_id\":\"dba432b8-cb4b-4e76-8684-01d96f91e453\"}\n[2026-04-27 10:31:10] local.INFO: [EncryptedTokenManager] Generating access token. {\"mode\":\"legacy\"} {\"correlation_id\":\"0ec6aef6-345c-4602-b6a1-646b95e481c0\",\"trace_id\":\"dba432b8-cb4b-4e76-8684-01d96f91e453\"}\n[2026-04-27 10:31:10] local.INFO: [CrmOwnerResolver] Integration owner matched as CRM Owner {\"crm_provider\":\"salesforce\",\"crm_owner\":143,\"team_id\":1} {\"correlation_id\":\"0ec6aef6-345c-4602-b6a1-646b95e481c0\",\"trace_id\":\"dba432b8-cb4b-4e76-8684-01d96f91e453\"}\n[2026-04-27 10:31:10] local.INFO: [SocialAccountService] Fetching token {\"socialAccountId\":1500,\"provider\":\"salesforce\"} {\"correlation_id\":\"0ec6aef6-345c-4602-b6a1-646b95e481c0\",\"trace_id\":\"dba432b8-cb4b-4e76-8684-01d96f91e453\"}\n[2026-04-27 10:31:10] local.INFO: [SocialAccountService] Token retrieved {\"socialAccountId\":1500,\"provider\":\"salesforce\"} {\"correlation_id\":\"0ec6aef6-345c-4602-b6a1-646b95e481c0\",\"trace_id\":\"dba432b8-cb4b-4e76-8684-01d96f91e453\"}\n[2026-04-27 10:31:10] local.INFO: [EncryptedTokenManager] Generating access token. {\"mode\":\"legacy\"} {\"correlation_id\":\"0ec6aef6-345c-4602-b6a1-646b95e481c0\",\"trace_id\":\"dba432b8-cb4b-4e76-8684-01d96f91e453\"}\n[2026-04-27 10:31:10] local.INFO: [SyncActivity] Start {\"import_id\":811743,\"provider\":\"twilio-video\",\"provider_id\":243,\"team\":\"jiminny\",\"team_id\":1} {\"correlation_id\":\"0ec6aef6-345c-4602-b6a1-646b95e481c0\",\"trace_id\":\"dba432b8-cb4b-4e76-8684-01d96f91e453\"}\n[2026-04-27 10:31:10] local.INFO: [Salesforce] Performing query {\"query\":\"SELECT Id,OwnerId,WhoId,WhatId,Priority,ActivityDate,Subject,Description,Status,Type,twilio_call_sid__c,Lead_UUID__c,Opportunity__c\n FROM Task\n WHERE Type = 'Video'\n AND isClosed = true\n AND IsDeleted = false\n AND LastModifiedDate >= :from\n AND twilio_call_sid__c != NULL AND LastModifiedDate <= :to ORDER BY LastModifiedDate ASC\n LIMIT :limit\",\"params\":{\"from\":\"2026-04-27T10:14:00Z\",\"to\":\"2026-04-27T10:30:00Z\",\"ownerId\":null,\"subType\":null,\"limit\":5000}} {\"correlation_id\":\"0ec6aef6-345c-4602-b6a1-646b95e481c0\",\"trace_id\":\"dba432b8-cb4b-4e76-8684-01d96f91e453\"}\n[2026-04-27 10:31:10] local.INFO: [Salesforce] Sending request {\"endpoint\":\"https://jiminny--stagingenv.sandbox.my.salesforce.com/services/data/v50.0/query/?q=SELECT+Id%2COwnerId%2CWhoId%2CWhatId%2CPriority%2CActivityDate%2CSubject%2CDescription%2CStatus%2CType%2Ctwilio_call_sid__c%2CLead_UUID__c%2COpportunity__c%0A++++++++++++++FROM+Task%0A++++++++++++WHERE+Type+%3D+%27Video%27%0A++++++++++++++AND+isClosed+%3D+true%0A++++++++++++++AND+IsDeleted+%3D+false%0A++++++++++++++AND+LastModifiedDate+%3E%3D+2026-04-27T10%3A14%3A00Z%0A++++++++++++++AND+twilio_call_sid__c+%21%3D+NULL+AND+LastModifiedDate+%3C%3D+2026-04-27T10%3A30%3A00Z+ORDER+BY+LastModifiedDate+ASC%0A+++++++++++++LIMIT+5000 GET\",\"team_id\":1} {\"correlation_id\":\"0ec6aef6-345c-4602-b6a1-646b95e481c0\",\"trace_id\":\"dba432b8-cb4b-4e76-8684-01d96f91e453\"}\n[2026-04-27 10:31:11] local.INFO: [Twilio Video] No calls found. {\"correlation_id\":\"0ec6aef6-345c-4602-b6a1-646b95e481c0\",\"trace_id\":\"dba432b8-cb4b-4e76-8684-01d96f91e453\"}\n[2026-04-27 10:31:11] local.INFO: [SyncActivity] End {\"import_id\":811743,\"provider\":\"twilio-video\",\"provider_id\":243,\"team\":\"jiminny\",\"team_id\":1} {\"correlation_id\":\"0ec6aef6-345c-4602-b6a1-646b95e481c0\",\"trace_id\":\"dba432b8-cb4b-4e76-8684-01d96f91e453\"}\n[2026-04-27 10:31:11] local.INFO: [SyncActivity] Memory usage {\"import_id\":811743,\"provider\":\"twilio-video\",\"provider_id\":243,\"team\":\"jiminny\",\"team_id\":1,\"memory_usage\":25679680,\"memory_real_usage\":65011712,\"pid\":13616} {\"correlation_id\":\"0ec6aef6-345c-4602-b6a1-646b95e481c0\",\"trace_id\":\"dba432b8-cb4b-4e76-8684-01d96f91e453\"}\n[2026-04-27 10:31:11] local.INFO: [SocialAccountService] Fetching token {\"socialAccountId\":1499,\"provider\":\"hubspot\"} {\"correlation_id\":\"3b9d7e85-b8c2-4ae2-863e-c343c277afdc\",\"trace_id\":\"dba432b8-cb4b-4e76-8684-01d96f91e453\"}\n[2026-04-27 10:31:11] local.INFO: [SocialAccountService] Token retrieved {\"socialAccountId\":1499,\"provider\":\"hubspot\"} {\"correlation_id\":\"3b9d7e85-b8c2-4ae2-863e-c343c277afdc\",\"trace_id\":\"dba432b8-cb4b-4e76-8684-01d96f91e453\"}\n[2026-04-27 10:31:11] local.INFO: [EncryptedTokenManager] Generating access token. {\"mode\":\"legacy\"} {\"correlation_id\":\"3b9d7e85-b8c2-4ae2-863e-c343c277afdc\",\"trace_id\":\"dba432b8-cb4b-4e76-8684-01d96f91e453\"}\n[2026-04-27 10:31:11] local.INFO: [CrmOwnerResolver] Integration owner matched as CRM Owner {\"crm_provider\":\"hubspot\",\"crm_owner\":89,\"team_id\":2} {\"correlation_id\":\"3b9d7e85-b8c2-4ae2-863e-c343c277afdc\",\"trace_id\":\"dba432b8-cb4b-4e76-8684-01d96f91e453\"}\n[2026-04-27 10:31:11] local.INFO: [SocialAccountService] Fetching token {\"socialAccountId\":408,\"provider\":\"hubspot\"} {\"correlation_id\":\"3b9d7e85-b8c2-4ae2-863e-c343c277afdc\",\"trace_id\":\"dba432b8-cb4b-4e76-8684-01d96f91e453\"}\n[2026-04-27 10:31:11] local.INFO: [SocialAccountService] Token needs refreshing {\"socialAccountId\":408,\"provider\":\"hubspot\"} {\"correlation_id\":\"3b9d7e85-b8c2-4ae2-863e-c343c277afdc\",\"trace_id\":\"dba432b8-cb4b-4e76-8684-01d96f91e453\"}\n[2026-04-27 10:31:11] local.INFO: [EncryptedTokenManager] Generating access token. {\"mode\":\"legacy\"} {\"correlation_id\":\"3b9d7e85-b8c2-4ae2-863e-c343c277afdc\",\"trace_id\":\"dba432b8-cb4b-4e76-8684-01d96f91e453\"}\n[2026-04-27 10:31:11] local.INFO: [SocialAccountService] Refreshing token from provider {\"socialAccountId\":408,\"provider\":\"hubspot\",\"refreshToken\":\"de4e47eb985578f4218833e763e31059e88b562e87e10749b3389be2328f0aa7\",\"state\":\"connected\"} {\"correlation_id\":\"3b9d7e85-b8c2-4ae2-863e-c343c277afdc\",\"trace_id\":\"dba432b8-cb4b-4e76-8684-01d96f91e453\"}\n[2026-04-27 10:31:11] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage before starting command {\"command\":\"crm:bullhorn:ping\",\"memoryBeforeCommandInMb\":60.0,\"memoryPeakBeforeCommandInMb\":99.727} {\"correlation_id\":\"a2085168-092a-44b0-8dd2-c8845bc36f86\",\"trace_id\":\"9445425b-6aaf-4a23-9323-ecf85939f967\"}\n[2026-04-27 10:31:11] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage for command {\"command\":\"crm:bullhorn:ping\",\"memoryBeforeCommandInMb\":60.0,\"memoryAfterCommandInMB\":62.0,\"memoryPeakBeforeCommandInMb\":99.727,\"memoryPeakAfterCommandInMB\":99.727} {\"correlation_id\":\"a2085168-092a-44b0-8dd2-c8845bc36f86\",\"trace_id\":\"9445425b-6aaf-4a23-9323-ecf85939f967\"}\n[2026-04-27 10:31:12] local.INFO: [SocialAccountObserver] Saving model {\"correlation_id\":\"3b9d7e85-b8c2-4ae2-863e-c343c277afdc\",\"trace_id\":\"dba432b8-cb4b-4e76-8684-01d96f91e453\"}","role_description":"text entry area","is_enabled":true,"is_focused":true,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Sync Changes","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.018055556,"height":0.026666667},"on_screen":false,"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Hide This Notification","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.018055556,"height":0.026666667},"on_screen":false,"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Code changed:","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.088194445,"height":0.027777778},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Hide","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.018055556,"height":0.026666667},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"2","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.016666668,"height":0.02111111},"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"60","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.021527778,"height":0.02111111},"on_screen":false,"role_description":"text"},{"role":"AXButton","text":"Previous Highlighted Error","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.015277778,"height":0.025555555},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Next Highlighted Error","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.014583333,"height":0.025555555},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXTextArea","text":"<?php\n\ndeclare(strict_types=1);\n\nnamespace Jiminny\\Services\\Crm\\Hubspot;\n\nuse HubSpot\\Client\\Crm\\Deals\\ApiException as DealApiException;\nuse HubSpot\\Client\\Crm\\Contacts\\ApiException as ContactApiException;\nuse HubSpot\\Client\\Crm\\Companies\\ApiException as CompanyApiException;\nuse HubSpot\\Client\\Crm\\Contacts\\Model\\SimplePublicObjectWithAssociations as ContactsWithAssociations;\nuse HubSpot\\Client\\Crm\\Companies\\Model\\SimplePublicObjectWithAssociations as CompaniesWithAssociations;\nuse HubSpot\\Client\\Crm\\Deals\\Model\\SimplePublicObjectWithAssociations as DealWithAssociations;\nuse HubSpot\\Client\\Crm\\Objects\\Model\\SimplePublicObjectInput;\nuse HubSpot\\Client\\Crm\\Objects\\Model\\SimplePublicObjectWithAssociations as ObjectWithAssociations;\nuse HubSpot\\Client\\Crm\\Pipelines\\Model\\Error;\nuse HubSpot\\Client\\Crm\\Pipelines\\Model\\PipelineStage;\nuse HubSpot\\Client\\Crm\\Properties\\Model\\Property;\nuse HubSpot\\Discovery\\Discovery;\nuse Jiminny\\Component\\Utility\\Service\\ProviderRateLimiter;\nuse Jiminny\\Exceptions\\CrmException;\nuse Jiminny\\Exceptions\\RateLimitException;\nuse Jiminny\\Exceptions\\SocialAccountTokenInvalidException;\nuse Jiminny\\Jobs\\Crm\\NoteObject;\nuse Jiminny\\Models\\Crm\\Field;\nuse Jiminny\\Services\\Crm\\BaseClient;\nuse Jiminny\\Services\\Crm\\Hubspot\\DTO\\Response\\Owner;\nuse Jiminny\\Services\\SocialAccountService;\nuse SevenShores\\Hubspot\\Exceptions\\BadRequest;\nuse SevenShores\\Hubspot\\Exceptions\\HubspotException;\nuse SevenShores\\Hubspot\\Factory;\nuse SevenShores\\Hubspot\\Http\\Response;\nuse Jiminny\\Services\\Crm\\Hubspot\\Pagination\\HubspotPaginationService;\nuse Throwable;\n\n/**\n * @phpstan-type CrmFieldOption array{id:string, label:string, value?:string}\n */\nclass Client extends BaseClient implements HubspotClientInterface\n{\n public const string MIN_API_VERSION = '2';\n\n public const string BASE_URL = 'https://api.hubapi.com';\n\n public const int ASSOCIATIONS_BATCH_SIZE_LIMIT = 1000;\n\n private HubspotPaginationService $paginationService;\n private HubspotTokenManager $tokenManager;\n private ProviderRateLimiter $rateLimiter;\n\n public function __construct(\n SocialAccountService $socialAccountService,\n HubspotPaginationService $paginationService,\n HubspotTokenManager $tokenManager,\n ProviderRateLimiter $rateLimiter,\n ) {\n parent::__construct($socialAccountService);\n $this->paginationService = $paginationService;\n $this->tokenManager = $tokenManager;\n $this->rateLimiter = $rateLimiter;\n\n $this->setBaseUrl(self::BASE_URL);\n $this->setVersion(self::MIN_API_VERSION);\n }\n\n /**\n * Single entry point for every HubSpot API call. Enforces the per-portal\n * rate limit configured in the rate_limits table (morphed to the current\n * Configuration) and reacts to a real 429 from HubSpot by translating it\n * into a RateLimitException carrying Retry-After.\n *\n * Wrap any outbound HubSpot call (SDK or raw HTTP) like:\n *\n * $this->executeRequest(fn () => $this->getNewInstance()->crm()->...);\n *\n * @template T\n * @param callable(): T $apiCall\n * @return T\n *\n * @throws RateLimitException\n */\n private function executeRequest(callable $apiCall)\n {\n if (! $this->rateLimiter->canMakeRequest($this->config)) {\n $retryAfter = $this->rateLimiter->requestAvailableIn($this->config);\n\n $this->log->warning('[Hubspot] Rate limit exceeded, deferring request', [\n 'team_id' => $this->config->team_id,\n 'config_id' => $this->config->getId(),\n 'retry_after' => $retryAfter,\n ]);\n\n throw new RateLimitException(\n 'Hubspot rate limit reached for configuration ' . $this->config->getId(),\n $retryAfter,\n );\n }\n\n $this->rateLimiter->incrementRequestCount($this->config);\n\n try {\n return $apiCall();\n } catch (Throwable $e) {\n if ($this->isHubspotRateLimit($e)) {\n $retryAfter = $this->parseRetryAfter($e);\n\n $this->log->warning('[Hubspot] Received 429 from API', [\n 'team_id' => $this->config->team_id,\n 'config_id' => $this->config->getId(),\n 'retry_after' => $retryAfter,\n 'reason' => $e->getMessage(),\n ]);\n\n throw new RateLimitException('Hubspot returned 429', $retryAfter, $e);\n }\n\n throw $e;\n }\n }\n\n private function isHubspotRateLimit(Throwable $e): bool\n {\n return method_exists($e, 'getCode') && (int) $e->getCode() === 429;\n }\n\n private function parseRetryAfter(Throwable $e): int\n {\n if (method_exists($e, 'getResponseHeaders')) {\n $headers = $e->getResponseHeaders() ?: [];\n $value = $headers['Retry-After'] ?? $headers['retry-after'] ?? null;\n if (is_array($value)) {\n $value = $value[0] ?? null;\n }\n if (is_numeric($value)) {\n return (int) $value;\n }\n }\n\n return 10;\n }\n\n public function getMinimumApiVersion(): string\n {\n return self::MIN_API_VERSION;\n }\n\n public function getInstance(): Factory\n {\n return new Factory([\n 'key' => $this->accessToken,\n 'oauth2' => true,\n 'base_url' => $this->baseUrl,\n ]);\n }\n\n public function getNewInstance(): Discovery\n {\n return \\HubSpot\\Factory::createWithAccessToken($this->accessToken);\n }\n\n /**\n * Secondly and daily limits for Hubspot API\n *\n * Product Tier: Free & Starter | Professional & Enterprise | API add-on (any tier)\n * Burst: 100/10 seconds | 150/10 seconds | 200/10 seconds\n * Daily: 250,000 | 500,000 | 1,000,000\n *\n * Official documentation states: The search endpoints are rate limited to five requests per second.\n * Since with 5 RPS were still hitting secondly rate limits we lowered it to 4\n */\n public function getPaginatedData(array $payload, string $type, int $offset = 0): array\n {\n $total = 0;\n $lastId = null;\n $rows = [];\n foreach ($this->getPaginatedDataGenerator($payload, $type, $offset, $total, $lastId) as $row) {\n $rows[] = $row;\n }\n\n return ['results' => $rows, 'total' => $total, 'last_record' => $lastId];\n }\n\n /**\n * @throws HubspotException\n * @throws SocialAccountTokenInvalidException\n * @throws BadRequest\n */\n public function getPaginatedDataGenerator(\n array $payload,\n string $type,\n int $offset = 0,\n int &$total = 0,\n ?string &$lastRecordId = null\n ): \\Generator {\n return $this->paginationService->getPaginatedDataGenerator(\n $this,\n $payload,\n $type,\n $offset,\n $total,\n $lastRecordId\n );\n }\n\n /**\n * @throws DealApiException\n * @throws CrmException\n */\n public function getOpportunityById(string $crmId, array $fields): array\n {\n try {\n $deal = $this->executeRequest(fn () => $this->getNewInstance()->crm()->deals()->basicApi()->getById(\n $crmId,\n implode(',', $fields),\n 'companies,contacts'\n ));\n } catch (DealApiException $e) {\n $this->log->info('[Hubspot] Failed to fetch opportunity', [\n 'crm_id' => $crmId,\n 'reason' => $e->getMessage(),\n ]);\n\n throw $e;\n }\n\n if (! $deal instanceof DealWithAssociations) {\n throw new CrmException('Deal not found');\n }\n\n return [\n 'id' => $deal->getId(),\n 'properties' => $deal->getProperties(),\n 'associations' => $deal->getAssociations(),\n ];\n }\n\n /**\n * Generic batch read method for HubSpot objects\n *\n * @param string $objectType The object type ('deals', 'companies', 'contacts')\n * @param array<string> $crmIds Array of HubSpot object IDs (max 100)\n * @param array<string> $fields Array of property names to fetch\n *\n * @return array<string, array> Array keyed by CRM ID with object data\n */\n private function batchReadObjects(string $objectType, array $crmIds, array $fields): array\n {\n if (empty($crmIds)) {\n return [];\n }\n\n $this->validateBatchSize($objectType, $crmIds);\n $this->ensureValidToken();\n\n try {\n $batchConfig = $this->createBatchConfiguration($objectType);\n $batchReadRequest = $this->prepareBatchRequest($batchConfig, $crmIds, $fields);\n $response = $batchConfig['api']->read($batchReadRequest);\n\n $this->validateApiResponse($response, $objectType);\n\n $results = $this->processApiResults($response);\n $this->logBatchResults($objectType, $crmIds, $results);\n\n return $results;\n } catch (\\Throwable $e) {\n $this->handleBatchError($e, $objectType, $crmIds);\n }\n }\n\n private function validateBatchSize(string $objectType, array $crmIds): void\n {\n if (count($crmIds) > 100) {\n throw new \\InvalidArgumentException(\"Batch size cannot exceed 100 {$objectType}\");\n }\n }\n\n private function createBatchConfiguration(string $objectType): array\n {\n $configurations = [\n 'deals' => [\n 'batchReadRequest' => new \\HubSpot\\Client\\Crm\\Deals\\Model\\BatchReadInputSimplePublicObjectId(),\n 'inputClass' => \\HubSpot\\Client\\Crm\\Deals\\Model\\SimplePublicObjectId::class,\n 'api' => $this->getNewInstance()->crm()->deals()->batchApi(),\n ],\n 'companies' => [\n 'batchReadRequest' => new \\HubSpot\\Client\\Crm\\Companies\\Model\\BatchReadInputSimplePublicObjectId(),\n 'inputClass' => \\HubSpot\\Client\\Crm\\Companies\\Model\\SimplePublicObjectId::class,\n 'api' => $this->getNewInstance()->crm()->companies()->batchApi(),\n ],\n 'contacts' => [\n 'batchReadRequest' => new \\HubSpot\\Client\\Crm\\Contacts\\Model\\BatchReadInputSimplePublicObjectId(),\n 'inputClass' => \\HubSpot\\Client\\Crm\\Contacts\\Model\\SimplePublicObjectId::class,\n 'api' => $this->getNewInstance()->crm()->contacts()->batchApi(),\n ],\n ];\n\n if (! isset($configurations[$objectType])) {\n throw new \\InvalidArgumentException(\"Unsupported object type: {$objectType}\");\n }\n\n return $configurations[$objectType];\n }\n\n private function prepareBatchRequest(array $batchConfig, array $crmIds, array $fields): object\n {\n $batchReadRequest = $batchConfig['batchReadRequest'];\n $inputClass = $batchConfig['inputClass'];\n\n $inputs = array_map(function ($crmId) use ($inputClass) {\n $input = new $inputClass();\n $input->setId($crmId);\n\n return $input;\n }, $crmIds);\n\n $batchReadRequest->setInputs($inputs);\n $batchReadRequest->setProperties($fields);\n\n return $batchReadRequest;\n }\n\n private function validateApiResponse($response, string $objectType): void\n {\n if (! $response) {\n throw new CrmException(\"HubSpot API returned null response for {$objectType} batch read\");\n }\n }\n\n private function processApiResults($response): array\n {\n $results = [];\n $responseResults = $response->getResults();\n\n if ($responseResults) {\n foreach ($responseResults as $object) {\n if ($object && $object->getId()) {\n $results[$object->getId()] = [\n 'id' => $object->getId(),\n 'properties' => $object->getProperties() ?: [],\n ];\n }\n }\n }\n\n return $results;\n }\n\n private function logBatchResults(string $objectType, array $crmIds, array $results): void\n {\n $this->log->info(\"[HubSpot] Batch fetched {$objectType}\", [\n 'requested_count' => count($crmIds),\n 'returned_count' => count($results),\n 'crm_ids' => $crmIds,\n ]);\n }\n\n private function handleBatchError(\\Throwable $e, string $objectType, array $crmIds): void\n {\n $errorMessage = $e->getMessage() ?: 'Unknown error';\n $errorTrace = $e->getTraceAsString() ?: 'No trace available';\n\n $this->log->error(\"[HubSpot] Failed to batch fetch {$objectType}\", [\n 'crm_ids' => $crmIds,\n 'error' => $errorMessage,\n 'trace' => $errorTrace,\n ]);\n\n throw new CrmException(\"Failed to batch fetch {$objectType}: \" . $errorMessage);\n }\n\n /**\n * Batch read multiple opportunities by their CRM IDs\n *\n * @param array<string> $crmIds Array of HubSpot deal IDs (max 100)\n * @param array<string> $fields Array of property names to fetch\n *\n * @return array<string, array> Array keyed by CRM ID with opportunity data\n */\n public function getOpportunitiesByIds(array $crmIds, array $fields): array\n {\n return $this->batchReadObjects('deals', $crmIds, $fields);\n }\n\n /**\n * Batch read multiple companies by their CRM IDs\n *\n * @param array<string> $crmIds Array of HubSpot company IDs (max 100)\n * @param array<string> $fields Array of property names to fetch\n *\n * @return array<string, array> Array keyed by CRM ID with company data\n */\n public function getCompaniesByIds(array $crmIds, array $fields): array\n {\n return $this->batchReadObjects('companies', $crmIds, $fields);\n }\n\n /**\n * Batch read multiple contacts by their CRM IDs\n *\n * @param array<string> $crmIds Array of HubSpot contact IDs (max 100)\n * @param array<string> $fields Array of property names to fetch\n *\n * @return array<string, array> Array keyed by CRM ID with contact data\n */\n public function getContactsByIds(array $crmIds, array $fields): array\n {\n return $this->batchReadObjects('contacts', $crmIds, $fields);\n }\n\n /**\n * @throws CompanyApiException\n * @throws CrmException\n */\n public function getAccountById(string $crmId, array $fields): array\n {\n try {\n $company = $this->getNewInstance()->crm()->companies()->basicApi()->getById(\n $crmId,\n implode(',', $fields),\n );\n } catch (CompanyApiException $e) {\n $this->log->info('[Hubspot] Failed to fetch account', [\n 'crm_id' => $crmId,\n 'reason' => $e->getMessage(),\n ]);\n\n throw $e;\n }\n\n if (! $company instanceof CompaniesWithAssociations) {\n throw new CrmException('Account not found');\n }\n\n return [\n 'id' => $company->getId(),\n 'properties' => $company->getProperties(),\n ];\n }\n\n /**\n * @throws ContactApiException\n * @throws CrmException\n */\n public function getContactById(string $crmId, array $fields): array\n {\n try {\n $contact = $this->getNewInstance()->crm()->contacts()->basicApi()->getById(\n $crmId,\n implode(',', $fields)\n );\n } catch (ContactApiException $e) {\n $this->log->info('[Hubspot] Failed to fetch contact', [\n 'crm_id' => $crmId,\n 'reason' => $e->getMessage(),\n ]);\n\n throw $e;\n }\n\n if (! $contact instanceof ContactsWithAssociations) {\n throw new CrmException('Contact not found');\n }\n\n return [\n 'id' => $contact->getId(),\n 'properties' => $contact->getProperties(),\n ];\n }\n\n /**\n * This is email search request that Hubspot offers as GET (more generous quota)\n */\n public function getContactByEmail(string $email, array $fields = []): array\n {\n try {\n $contact = $this->getNewInstance()->crm()->contacts()->basicApi()->getById(\n $email,\n implode(',', $fields),\n null,\n false,\n 'email'\n );\n\n return [\n 'id' => $contact->getId(),\n 'properties' => $contact->getProperties(),\n ];\n } catch (ContactApiException $e) {\n $this->log->info('[Hubspot] Failed to fetch contact', [\n 'email' => $email,\n 'reason' => $e->getMessage(),\n ]);\n\n return [];\n }\n }\n\n /**\n * @throws CrmException\n */\n public function fetchProperty(string $objectType, string $propertyId): Property\n {\n $result = $this->getNewInstance()->crm()->properties()->coreApi()->getByName($objectType, $propertyId);\n\n if (! $result instanceof Property) {\n $this->log->error('[Hubspot] Failed to fetch property', [\n 'object_type' => $objectType,\n 'property_id' => $propertyId,\n 'reason' => $result->getMessage(),\n ]);\n\n throw new CrmException('Failed to fetch property');\n }\n\n return $result;\n }\n\n /**\n * @return array<CrmFieldOption>\n */\n public function fetchPropertyOptions(string $objectType, string $propertyId): array\n {\n /** @var array<CrmFieldOption> */\n return $this->fetchProperty($objectType, $propertyId)->getOptions();\n }\n\n /**\n * @return array<array{id:string, label:string, deleted:bool}>\n */\n public function fetchCallDispositions(): array\n {\n /** @var Response $response */\n $response = $this->getInstance()->engagements()->getCallDispositions();\n\n /**\n * @var array<array{\n * id:string,\n * label:string,\n * deleted: bool\n * }>\n */\n return $response->toArray();\n }\n\n /**\n * @return array<CrmFieldOption>\n */\n public function fetchOpportunityPipelineStages(): array\n {\n $stages = [];\n $apiResponse = $this->getNewInstance()->crm()->pipelines()->pipelinesApi()->getAll('deals');\n\n if ($apiResponse instanceof Error) {\n $this->log->error('[Hubspot] Failed to fetch opportunity pipelines', [\n 'reason' => $apiResponse->getMessage(),\n ]);\n\n return [];\n }\n\n foreach ($apiResponse->getResults() as $pipeline) {\n $pipelineStages = array_map(\n static function (PipelineStage $stage) {\n return [\n 'id' => $stage->getId(),\n 'label' => $stage->getLabel(),\n ];\n },\n $pipeline->getStages()\n );\n\n $stages = array_merge($stages, $pipelineStages);\n }\n\n return $stages;\n }\n\n public function fetchOpportunityPipelines(): array\n {\n $pipelines = [];\n\n try {\n $apiResponse = $this->makeRequest('/crm/v3/pipelines/deals');\n } catch (\\Exception $e) {\n $this->log->info('[Hubspot] Failed to fetch opportunity pipelines', [\n 'reason' => $e->getMessage(),\n ]);\n\n return [];\n }\n\n $response = $apiResponse->toArray();\n\n foreach ($response['results'] as $pipeline) {\n $pipelines[] = [\n 'id' => $pipeline['id'],\n 'label' => $pipeline['label'],\n ];\n }\n\n return $pipelines;\n }\n\n /**\n * @return array<CrmFieldOption>\n */\n public function fetchMeetingOutcomeFieldOptions(Field $field): array\n {\n return $field->getCrmProviderId() === 'meetingOutcome'\n ? $this->fetchMeetingOutcomeTypes()\n : $this->fetchCallActivityTypes();\n }\n\n public function fetchMeetingOutcomeTypes(): array\n {\n return $this->extractMeetingTypeOptions(\n 'https://api.hubapi.com/crm/v3/properties/meeting/hs_meeting_outcome'\n );\n }\n\n public function fetchCallActivityTypes(): array\n {\n return $this->extractMeetingTypeOptions(\n 'https://api.hubapi.com/crm/v3/properties/call/hs_activity_type'\n );\n }\n\n private function extractMeetingTypeOptions(string $endpoint): array\n {\n /** @var Response $response */\n $response = $this->getInstance()\n ->getClient()\n ->request('GET', $endpoint);\n\n /**\n * @var array<array{\n * value: string,\n * label: string,\n * displayOrder: int\n * }> $optionData\n */\n $optionData = $response->toArray()['options'] ?? [];\n\n $options = [];\n foreach ($optionData as $item) {\n $options[] = [\n 'id' => $item['value'],\n 'value' => $item['value'],\n 'label' => $item['label'],\n 'display_order' => $item['displayOrder'],\n ];\n }\n\n return $options;\n }\n\n /**\n * @return array<CrmFieldOption>\n */\n public function fetchDispositionFieldOptions(): array\n {\n $options = [];\n\n $dispositions = $this->fetchCallDispositions();\n\n foreach ($dispositions as $disposition) {\n if ($disposition['deleted'] !== false) {\n continue;\n }\n\n $option['value'] = $disposition['id'];\n $option['id'] = $disposition['id'];\n $option['label'] = $disposition['label'];\n\n $options[] = $option;\n }\n\n return $options;\n }\n\n /**\n * @return array<CrmFieldOption>\n */\n public function fetchOpportunityFieldOptions(Field $field): array\n {\n if ($field->isStageField()) {\n return $this->fetchOpportunityPipelineStages();\n }\n\n if ($field->isPipelineField()) {\n return $this->fetchOpportunityPipelines();\n }\n\n return $this->fetchPropertyOptions('deals', $field->getCrmProviderId());\n }\n\n /**\n * @throws BadRequest\n * @throws HubspotException\n */\n public function makeRequest(string $endpoint, $method = 'GET', $payload = [], ?string $queryString = null)\n {\n $endpoint = self::BASE_URL . $endpoint;\n\n if ($method === 'GET') {\n $response = $this->getInstance()->getClient()?->request(\n method: $method,\n endpoint: $endpoint,\n query_string: $queryString\n );\n } else {\n $response = $this->getInstance()->getClient()->request($method, $endpoint, [\n 'json' => ($payload),\n ]);\n }\n\n $max = $response->getHeaderLine('X-HubSpot-RateLimit-Max'); // \"110\"\n $remaining = $response->getHeaderLine('X-HubSpot-RateLimit-Remaining'); // \"109\"\n $interval = $response->getHeaderLine('X-HubSpot-RateLimit-Interval-Milliseconds'); // \"10000\"\n $body = json_decode((string) $response->getBody(), true);\n\n \\Illuminate\\Support\\Facades\\Log::channel('custom_channel')->info('$max ' . PHP_EOL . print_r($max, true));\n \\Illuminate\\Support\\Facades\\Log::channel('custom_channel')->info('$remaining ' . PHP_EOL . print_r($remaining, true));\n \\Illuminate\\Support\\Facades\\Log::channel('custom_channel')->info('$interval ' . PHP_EOL . print_r($interval, true));\n \\Illuminate\\Support\\Facades\\Log::channel('custom_channel')->info('$body ' . PHP_EOL . print_r($body, true));\n\n return $response;\n }\n\n /**\n * @throws BadRequest\n * @throws HubspotException\n */\n public function createMeeting(array $payload): Response\n {\n $endpoint = '/crm/v3/objects/meetings';\n\n return $this->makeRequest($endpoint, 'POST', $payload);\n }\n\n /**\n * @throws BadRequest\n * @throws HubspotException\n */\n public function updateMeeting(string $meetingId, array $payload): Response\n {\n $endpoint = '/crm/v3/objects/meetings/' . $meetingId;\n\n return $this->makeRequest($endpoint, 'PATCH', $payload);\n }\n\n /**\n * @throws \\Exception\n */\n public function createNote(\n string $body,\n string $ownerId,\n int $timestamp,\n string $objectId,\n NoteObject $noteObject\n ): ?string {\n try {\n $noteInput = new SimplePublicObjectInput([\n 'properties' => [\n 'hs_note_body' => $body,\n 'hubspot_owner_id' => $ownerId,\n 'hs_timestamp' => $timestamp,\n ],\n ]);\n\n // Create note\n $note = $this->getNewInstance()->crm()->objects()->basicApi()->create('note', $noteInput);\n\n $this->getNewInstance()->crm()->objects()->associationsApi()->create(\n 'note',\n $note->getId(),\n $this->getNoteObject($noteObject),\n $objectId,\n $this->getNoteAssociationType($noteObject),\n );\n\n return $note->getId();\n } catch (\\Exception $e) {\n $this->log->error('[Hubspot] Failed to create note', [\n 'objectId' => $objectId,\n 'noteObject' => $noteObject->getObjectType(),\n 'reason' => $e->getMessage(),\n ]);\n\n \\Sentry::captureException($e);\n }\n\n return null;\n }\n\n public function updateEngagement(string $objectId, array $engagement, array $metadata): void\n {\n $this->getInstance()->engagements()->update($objectId, $engagement, $metadata);\n }\n\n public function getEngagementData(string $engagementId): array\n {\n $engagement = $this->getInstance()->engagements()->get($engagementId);\n\n return $engagement->toArray();\n }\n\n public function createEngagement(array $engagement, array $associations, array $metadata): Response\n {\n return $this->getInstance()\n ->engagements()\n ->create($engagement, $associations, $metadata);\n }\n\n public function isUnauthorizedException(\\Exception $e): bool\n {\n // Check for specific HubSpot API exception types first\n if ($e instanceof BadRequest) {\n // BadRequest can contain 401 status codes\n return $e->getCode() === 401;\n }\n\n // Check for HTTP client exceptions with status codes\n if ($e instanceof \\GuzzleHttp\\Exception\\RequestException && $e->hasResponse()) {\n $response = $e->getResponse();\n if ($response !== null) {\n return $response->getStatusCode() === 401;\n }\n }\n\n // Check for Guzzle HTTP exceptions\n if ($e instanceof \\GuzzleHttp\\Exception\\ClientException) {\n return $e->getCode() === 401;\n }\n\n // Fallback to string matching as last resort, but be more specific\n $message = strtolower($e->getMessage());\n\n return str_contains($message, '401 unauthorized') ||\n str_contains($message, 'http 401') ||\n str_contains($message, 'status code 401') ||\n (preg_match('/\\b401\\b/', $message) && str_contains($message, 'unauthorized'));\n }\n\n /**\n * Validates and refreshes the access token if needed before API requests.\n * This ensures long-running processes don't fail due to token expiration.\n *\n * @throws SocialAccountTokenInvalidException\n */\n public function ensureValidToken(): void\n {\n if ($this->oauthAccount === null) {\n return;\n }\n\n $newToken = $this->tokenManager->ensureValidToken($this->oauthAccount);\n if ($newToken !== null) {\n $this->accessToken = $newToken;\n }\n }\n\n public function getConfig()\n {\n return $this->config;\n }\n\n // returns only active (archived=false)\n public function getOwners(): array\n {\n return $this->getNewInstance()->crm()->owners()->getAll();\n }\n\n /**\n * @param bool $archived\n *\n * @return array<Owner>|[]\n */\n public function getOwnersArchived(bool $archived = true): array\n {\n $endpoint = '/crm/v3/owners';\n $queryParams = [\n 'archived' => $archived ? 'true' : 'false',\n ];\n $queryString = http_build_query($queryParams);\n\n $owners = [];\n\n try {\n $response = $this->makeRequest(endpoint: $endpoint, queryString: $queryString);\n $responseData = $response?->toArray();\n\n foreach ($responseData['results'] as $result) {\n try {\n $owners[] = Owner::create($result);\n } catch (Throwable $e) {\n $this->log->error('[HubSpot] Failed to process owner data', [\n 'result' => $result,\n 'error' => $e->getMessage(),\n ]);\n\n continue;\n }\n }\n } catch (Throwable $e) {\n $this->log->error('HubSpot] Failed to fetch owners', [\n 'archived' => $archived,\n 'error' => $e->getMessage(),\n ]);\n\n return [];\n }\n\n return $owners;\n }\n\n public function getMeeting(string $engagementId): ObjectWithAssociations\n {\n return $this->getNewInstance()->crm()->objects()->basicApi()\n ->getById('meeting', $engagementId, null, 'contact,company,deal');\n }\n\n public function deleteEngagement(string $engagementId): void\n {\n $this->getInstance()->engagements()->delete((int) $engagementId);\n }\n\n public function getAssociationsData(array $ids, string $fromObject, string $toObject): array\n {\n $associationData = [];\n $idChunks = array_chunk($ids, self::ASSOCIATIONS_BATCH_SIZE_LIMIT);\n\n foreach ($idChunks as $idChunk) {\n try {\n $batchInput = new \\HubSpot\\Client\\Crm\\Associations\\Model\\BatchInputPublicObjectId();\n $batchInput->setInputs(array_map(function ($id) {\n $publicObjectId = new \\HubSpot\\Client\\Crm\\Associations\\Model\\PublicObjectId();\n $publicObjectId->setId($id);\n\n return $publicObjectId;\n }, $idChunk));\n\n $associatedObjectsData = $this\n ->getNewInstance()\n ->crm()\n ->associations()\n ->batchApi()\n ->read($fromObject, $toObject, $batchInput);\n\n if ($associatedObjectsData instanceof \\HubSpot\\Client\\Crm\\Associations\\Model\\BatchResponsePublicAssociationMulti) {\n foreach ($associatedObjectsData->getResults() as $association) {\n $from = $association->getFrom()->getId();\n $toAssociations = $association->getTo();\n\n if (! empty($toAssociations)) {\n $associationData[$from] = array_map(function ($item) {\n return $item->getId();\n }, $toAssociations);\n }\n }\n }\n } catch (\\Exception $e) {\n $this->log->error('[Hubspot] Failed to fetch associations', [\n 'from_object' => $fromObject,\n 'to_object' => $toObject,\n 'reason' => $e->getMessage(),\n ]);\n }\n }\n\n return $associationData;\n }\n\n /**\n * @throws \\Exception\n */\n private function getNoteAssociationType(NoteObject $noteObject): string\n {\n return match($noteObject) {\n NoteObject::Opportunity => 'note_to_deal',\n NoteObject::Lead, NoteObject::Contact => 'note_to_contact', // or 'note_to_lead' if your portal supports it\n NoteObject::Account => 'note_to_company',\n NoteObject::Call, NoteObject::Event => throw new \\Exception('Not supported'),\n };\n }\n\n /**\n * @throws \\Exception\n */\n private function getNoteObject(NoteObject $noteObject): string\n {\n return match($noteObject) {\n NoteObject::Opportunity => 'deal',\n NoteObject::Lead, NoteObject::Contact => 'contact',\n NoteObject::Account => 'company',\n NoteObject::Call, NoteObject::Event => throw new \\Exception('Not supported'),\n };\n }\n\n public function addAssociations(string $objectType, string $associationType, array $payload): Response\n {\n $endpoint = \"/crm/v4/associations/$objectType/$associationType/batch/create\";\n\n return $this->makeRequest($endpoint, 'POST', $payload);\n }\n\n public function removeAssociations(string $objectType, string $associationType, array $payload): Response\n {\n $endpoint = \"/crm/v4/associations/$objectType/$associationType/batch/archive\";\n\n return $this->makeRequest($endpoint, 'POST', $payload);\n }\n}","depth":4,"on_screen":true,"value":"<?php\n\ndeclare(strict_types=1);\n\nnamespace Jiminny\\Services\\Crm\\Hubspot;\n\nuse HubSpot\\Client\\Crm\\Deals\\ApiException as DealApiException;\nuse HubSpot\\Client\\Crm\\Contacts\\ApiException as ContactApiException;\nuse HubSpot\\Client\\Crm\\Companies\\ApiException as CompanyApiException;\nuse HubSpot\\Client\\Crm\\Contacts\\Model\\SimplePublicObjectWithAssociations as ContactsWithAssociations;\nuse HubSpot\\Client\\Crm\\Companies\\Model\\SimplePublicObjectWithAssociations as CompaniesWithAssociations;\nuse HubSpot\\Client\\Crm\\Deals\\Model\\SimplePublicObjectWithAssociations as DealWithAssociations;\nuse HubSpot\\Client\\Crm\\Objects\\Model\\SimplePublicObjectInput;\nuse HubSpot\\Client\\Crm\\Objects\\Model\\SimplePublicObjectWithAssociations as ObjectWithAssociations;\nuse HubSpot\\Client\\Crm\\Pipelines\\Model\\Error;\nuse HubSpot\\Client\\Crm\\Pipelines\\Model\\PipelineStage;\nuse HubSpot\\Client\\Crm\\Properties\\Model\\Property;\nuse HubSpot\\Discovery\\Discovery;\nuse Jiminny\\Component\\Utility\\Service\\ProviderRateLimiter;\nuse Jiminny\\Exceptions\\CrmException;\nuse Jiminny\\Exceptions\\RateLimitException;\nuse Jiminny\\Exceptions\\SocialAccountTokenInvalidException;\nuse Jiminny\\Jobs\\Crm\\NoteObject;\nuse Jiminny\\Models\\Crm\\Field;\nuse Jiminny\\Services\\Crm\\BaseClient;\nuse Jiminny\\Services\\Crm\\Hubspot\\DTO\\Response\\Owner;\nuse Jiminny\\Services\\SocialAccountService;\nuse SevenShores\\Hubspot\\Exceptions\\BadRequest;\nuse SevenShores\\Hubspot\\Exceptions\\HubspotException;\nuse SevenShores\\Hubspot\\Factory;\nuse SevenShores\\Hubspot\\Http\\Response;\nuse Jiminny\\Services\\Crm\\Hubspot\\Pagination\\HubspotPaginationService;\nuse Throwable;\n\n/**\n * @phpstan-type CrmFieldOption array{id:string, label:string, value?:string}\n */\nclass Client extends BaseClient implements HubspotClientInterface\n{\n public const string MIN_API_VERSION = '2';\n\n public const string BASE_URL = 'https://api.hubapi.com';\n\n public const int ASSOCIATIONS_BATCH_SIZE_LIMIT = 1000;\n\n private HubspotPaginationService $paginationService;\n private HubspotTokenManager $tokenManager;\n private ProviderRateLimiter $rateLimiter;\n\n public function __construct(\n SocialAccountService $socialAccountService,\n HubspotPaginationService $paginationService,\n HubspotTokenManager $tokenManager,\n ProviderRateLimiter $rateLimiter,\n ) {\n parent::__construct($socialAccountService);\n $this->paginationService = $paginationService;\n $this->tokenManager = $tokenManager;\n $this->rateLimiter = $rateLimiter;\n\n $this->setBaseUrl(self::BASE_URL);\n $this->setVersion(self::MIN_API_VERSION);\n }\n\n /**\n * Single entry point for every HubSpot API call. Enforces the per-portal\n * rate limit configured in the rate_limits table (morphed to the current\n * Configuration) and reacts to a real 429 from HubSpot by translating it\n * into a RateLimitException carrying Retry-After.\n *\n * Wrap any outbound HubSpot call (SDK or raw HTTP) like:\n *\n * $this->executeRequest(fn () => $this->getNewInstance()->crm()->...);\n *\n * @template T\n * @param callable(): T $apiCall\n * @return T\n *\n * @throws RateLimitException\n */\n private function executeRequest(callable $apiCall)\n {\n if (! $this->rateLimiter->canMakeRequest($this->config)) {\n $retryAfter = $this->rateLimiter->requestAvailableIn($this->config);\n\n $this->log->warning('[Hubspot] Rate limit exceeded, deferring request', [\n 'team_id' => $this->config->team_id,\n 'config_id' => $this->config->getId(),\n 'retry_after' => $retryAfter,\n ]);\n\n throw new RateLimitException(\n 'Hubspot rate limit reached for configuration ' . $this->config->getId(),\n $retryAfter,\n );\n }\n\n $this->rateLimiter->incrementRequestCount($this->config);\n\n try {\n return $apiCall();\n } catch (Throwable $e) {\n if ($this->isHubspotRateLimit($e)) {\n $retryAfter = $this->parseRetryAfter($e);\n\n $this->log->warning('[Hubspot] Received 429 from API', [\n 'team_id' => $this->config->team_id,\n 'config_id' => $this->config->getId(),\n 'retry_after' => $retryAfter,\n 'reason' => $e->getMessage(),\n ]);\n\n throw new RateLimitException('Hubspot returned 429', $retryAfter, $e);\n }\n\n throw $e;\n }\n }\n\n private function isHubspotRateLimit(Throwable $e): bool\n {\n return method_exists($e, 'getCode') && (int) $e->getCode() === 429;\n }\n\n private function parseRetryAfter(Throwable $e): int\n {\n if (method_exists($e, 'getResponseHeaders')) {\n $headers = $e->getResponseHeaders() ?: [];\n $value = $headers['Retry-After'] ?? $headers['retry-after'] ?? null;\n if (is_array($value)) {\n $value = $value[0] ?? null;\n }\n if (is_numeric($value)) {\n return (int) $value;\n }\n }\n\n return 10;\n }\n\n public function getMinimumApiVersion(): string\n {\n return self::MIN_API_VERSION;\n }\n\n public function getInstance(): Factory\n {\n return new Factory([\n 'key' => $this->accessToken,\n 'oauth2' => true,\n 'base_url' => $this->baseUrl,\n ]);\n }\n\n public function getNewInstance(): Discovery\n {\n return \\HubSpot\\Factory::createWithAccessToken($this->accessToken);\n }\n\n /**\n * Secondly and daily limits for Hubspot API\n *\n * Product Tier: Free & Starter | Professional & Enterprise | API add-on (any tier)\n * Burst: 100/10 seconds | 150/10 seconds | 200/10 seconds\n * Daily: 250,000 | 500,000 | 1,000,000\n *\n * Official documentation states: The search endpoints are rate limited to five requests per second.\n * Since with 5 RPS were still hitting secondly rate limits we lowered it to 4\n */\n public function getPaginatedData(array $payload, string $type, int $offset = 0): array\n {\n $total = 0;\n $lastId = null;\n $rows = [];\n foreach ($this->getPaginatedDataGenerator($payload, $type, $offset, $total, $lastId) as $row) {\n $rows[] = $row;\n }\n\n return ['results' => $rows, 'total' => $total, 'last_record' => $lastId];\n }\n\n /**\n * @throws HubspotException\n * @throws SocialAccountTokenInvalidException\n * @throws BadRequest\n */\n public function getPaginatedDataGenerator(\n array $payload,\n string $type,\n int $offset = 0,\n int &$total = 0,\n ?string &$lastRecordId = null\n ): \\Generator {\n return $this->paginationService->getPaginatedDataGenerator(\n $this,\n $payload,\n $type,\n $offset,\n $total,\n $lastRecordId\n );\n }\n\n /**\n * @throws DealApiException\n * @throws CrmException\n */\n public function getOpportunityById(string $crmId, array $fields): array\n {\n try {\n $deal = $this->executeRequest(fn () => $this->getNewInstance()->crm()->deals()->basicApi()->getById(\n $crmId,\n implode(',', $fields),\n 'companies,contacts'\n ));\n } catch (DealApiException $e) {\n $this->log->info('[Hubspot] Failed to fetch opportunity', [\n 'crm_id' => $crmId,\n 'reason' => $e->getMessage(),\n ]);\n\n throw $e;\n }\n\n if (! $deal instanceof DealWithAssociations) {\n throw new CrmException('Deal not found');\n }\n\n return [\n 'id' => $deal->getId(),\n 'properties' => $deal->getProperties(),\n 'associations' => $deal->getAssociations(),\n ];\n }\n\n /**\n * Generic batch read method for HubSpot objects\n *\n * @param string $objectType The object type ('deals', 'companies', 'contacts')\n * @param array<string> $crmIds Array of HubSpot object IDs (max 100)\n * @param array<string> $fields Array of property names to fetch\n *\n * @return array<string, array> Array keyed by CRM ID with object data\n */\n private function batchReadObjects(string $objectType, array $crmIds, array $fields): array\n {\n if (empty($crmIds)) {\n return [];\n }\n\n $this->validateBatchSize($objectType, $crmIds);\n $this->ensureValidToken();\n\n try {\n $batchConfig = $this->createBatchConfiguration($objectType);\n $batchReadRequest = $this->prepareBatchRequest($batchConfig, $crmIds, $fields);\n $response = $batchConfig['api']->read($batchReadRequest);\n\n $this->validateApiResponse($response, $objectType);\n\n $results = $this->processApiResults($response);\n $this->logBatchResults($objectType, $crmIds, $results);\n\n return $results;\n } catch (\\Throwable $e) {\n $this->handleBatchError($e, $objectType, $crmIds);\n }\n }\n\n private function validateBatchSize(string $objectType, array $crmIds): void\n {\n if (count($crmIds) > 100) {\n throw new \\InvalidArgumentException(\"Batch size cannot exceed 100 {$objectType}\");\n }\n }\n\n private function createBatchConfiguration(string $objectType): array\n {\n $configurations = [\n 'deals' => [\n 'batchReadRequest' => new \\HubSpot\\Client\\Crm\\Deals\\Model\\BatchReadInputSimplePublicObjectId(),\n 'inputClass' => \\HubSpot\\Client\\Crm\\Deals\\Model\\SimplePublicObjectId::class,\n 'api' => $this->getNewInstance()->crm()->deals()->batchApi(),\n ],\n 'companies' => [\n 'batchReadRequest' => new \\HubSpot\\Client\\Crm\\Companies\\Model\\BatchReadInputSimplePublicObjectId(),\n 'inputClass' => \\HubSpot\\Client\\Crm\\Companies\\Model\\SimplePublicObjectId::class,\n 'api' => $this->getNewInstance()->crm()->companies()->batchApi(),\n ],\n 'contacts' => [\n 'batchReadRequest' => new \\HubSpot\\Client\\Crm\\Contacts\\Model\\BatchReadInputSimplePublicObjectId(),\n 'inputClass' => \\HubSpot\\Client\\Crm\\Contacts\\Model\\SimplePublicObjectId::class,\n 'api' => $this->getNewInstance()->crm()->contacts()->batchApi(),\n ],\n ];\n\n if (! isset($configurations[$objectType])) {\n throw new \\InvalidArgumentException(\"Unsupported object type: {$objectType}\");\n }\n\n return $configurations[$objectType];\n }\n\n private function prepareBatchRequest(array $batchConfig, array $crmIds, array $fields): object\n {\n $batchReadRequest = $batchConfig['batchReadRequest'];\n $inputClass = $batchConfig['inputClass'];\n\n $inputs = array_map(function ($crmId) use ($inputClass) {\n $input = new $inputClass();\n $input->setId($crmId);\n\n return $input;\n }, $crmIds);\n\n $batchReadRequest->setInputs($inputs);\n $batchReadRequest->setProperties($fields);\n\n return $batchReadRequest;\n }\n\n private function validateApiResponse($response, string $objectType): void\n {\n if (! $response) {\n throw new CrmException(\"HubSpot API returned null response for {$objectType} batch read\");\n }\n }\n\n private function processApiResults($response): array\n {\n $results = [];\n $responseResults = $response->getResults();\n\n if ($responseResults) {\n foreach ($responseResults as $object) {\n if ($object && $object->getId()) {\n $results[$object->getId()] = [\n 'id' => $object->getId(),\n 'properties' => $object->getProperties() ?: [],\n ];\n }\n }\n }\n\n return $results;\n }\n\n private function logBatchResults(string $objectType, array $crmIds, array $results): void\n {\n $this->log->info(\"[HubSpot] Batch fetched {$objectType}\", [\n 'requested_count' => count($crmIds),\n 'returned_count' => count($results),\n 'crm_ids' => $crmIds,\n ]);\n }\n\n private function handleBatchError(\\Throwable $e, string $objectType, array $crmIds): void\n {\n $errorMessage = $e->getMessage() ?: 'Unknown error';\n $errorTrace = $e->getTraceAsString() ?: 'No trace available';\n\n $this->log->error(\"[HubSpot] Failed to batch fetch {$objectType}\", [\n 'crm_ids' => $crmIds,\n 'error' => $errorMessage,\n 'trace' => $errorTrace,\n ]);\n\n throw new CrmException(\"Failed to batch fetch {$objectType}: \" . $errorMessage);\n }\n\n /**\n * Batch read multiple opportunities by their CRM IDs\n *\n * @param array<string> $crmIds Array of HubSpot deal IDs (max 100)\n * @param array<string> $fields Array of property names to fetch\n *\n * @return array<string, array> Array keyed by CRM ID with opportunity data\n */\n public function getOpportunitiesByIds(array $crmIds, array $fields): array\n {\n return $this->batchReadObjects('deals', $crmIds, $fields);\n }\n\n /**\n * Batch read multiple companies by their CRM IDs\n *\n * @param array<string> $crmIds Array of HubSpot company IDs (max 100)\n * @param array<string> $fields Array of property names to fetch\n *\n * @return array<string, array> Array keyed by CRM ID with company data\n */\n public function getCompaniesByIds(array $crmIds, array $fields): array\n {\n return $this->batchReadObjects('companies', $crmIds, $fields);\n }\n\n /**\n * Batch read multiple contacts by their CRM IDs\n *\n * @param array<string> $crmIds Array of HubSpot contact IDs (max 100)\n * @param array<string> $fields Array of property names to fetch\n *\n * @return array<string, array> Array keyed by CRM ID with contact data\n */\n public function getContactsByIds(array $crmIds, array $fields): array\n {\n return $this->batchReadObjects('contacts', $crmIds, $fields);\n }\n\n /**\n * @throws CompanyApiException\n * @throws CrmException\n */\n public function getAccountById(string $crmId, array $fields): array\n {\n try {\n $company = $this->getNewInstance()->crm()->companies()->basicApi()->getById(\n $crmId,\n implode(',', $fields),\n );\n } catch (CompanyApiException $e) {\n $this->log->info('[Hubspot] Failed to fetch account', [\n 'crm_id' => $crmId,\n 'reason' => $e->getMessage(),\n ]);\n\n throw $e;\n }\n\n if (! $company instanceof CompaniesWithAssociations) {\n throw new CrmException('Account not found');\n }\n\n return [\n 'id' => $company->getId(),\n 'properties' => $company->getProperties(),\n ];\n }\n\n /**\n * @throws ContactApiException\n * @throws CrmException\n */\n public function getContactById(string $crmId, array $fields): array\n {\n try {\n $contact = $this->getNewInstance()->crm()->contacts()->basicApi()->getById(\n $crmId,\n implode(',', $fields)\n );\n } catch (ContactApiException $e) {\n $this->log->info('[Hubspot] Failed to fetch contact', [\n 'crm_id' => $crmId,\n 'reason' => $e->getMessage(),\n ]);\n\n throw $e;\n }\n\n if (! $contact instanceof ContactsWithAssociations) {\n throw new CrmException('Contact not found');\n }\n\n return [\n 'id' => $contact->getId(),\n 'properties' => $contact->getProperties(),\n ];\n }\n\n /**\n * This is email search request that Hubspot offers as GET (more generous quota)\n */\n public function getContactByEmail(string $email, array $fields = []): array\n {\n try {\n $contact = $this->getNewInstance()->crm()->contacts()->basicApi()->getById(\n $email,\n implode(',', $fields),\n null,\n false,\n 'email'\n );\n\n return [\n 'id' => $contact->getId(),\n 'properties' => $contact->getProperties(),\n ];\n } catch (ContactApiException $e) {\n $this->log->info('[Hubspot] Failed to fetch contact', [\n 'email' => $email,\n 'reason' => $e->getMessage(),\n ]);\n\n return [];\n }\n }\n\n /**\n * @throws CrmException\n */\n public function fetchProperty(string $objectType, string $propertyId): Property\n {\n $result = $this->getNewInstance()->crm()->properties()->coreApi()->getByName($objectType, $propertyId);\n\n if (! $result instanceof Property) {\n $this->log->error('[Hubspot] Failed to fetch property', [\n 'object_type' => $objectType,\n 'property_id' => $propertyId,\n 'reason' => $result->getMessage(),\n ]);\n\n throw new CrmException('Failed to fetch property');\n }\n\n return $result;\n }\n\n /**\n * @return array<CrmFieldOption>\n */\n public function fetchPropertyOptions(string $objectType, string $propertyId): array\n {\n /** @var array<CrmFieldOption> */\n return $this->fetchProperty($objectType, $propertyId)->getOptions();\n }\n\n /**\n * @return array<array{id:string, label:string, deleted:bool}>\n */\n public function fetchCallDispositions(): array\n {\n /** @var Response $response */\n $response = $this->getInstance()->engagements()->getCallDispositions();\n\n /**\n * @var array<array{\n * id:string,\n * label:string,\n * deleted: bool\n * }>\n */\n return $response->toArray();\n }\n\n /**\n * @return array<CrmFieldOption>\n */\n public function fetchOpportunityPipelineStages(): array\n {\n $stages = [];\n $apiResponse = $this->getNewInstance()->crm()->pipelines()->pipelinesApi()->getAll('deals');\n\n if ($apiResponse instanceof Error) {\n $this->log->error('[Hubspot] Failed to fetch opportunity pipelines', [\n 'reason' => $apiResponse->getMessage(),\n ]);\n\n return [];\n }\n\n foreach ($apiResponse->getResults() as $pipeline) {\n $pipelineStages = array_map(\n static function (PipelineStage $stage) {\n return [\n 'id' => $stage->getId(),\n 'label' => $stage->getLabel(),\n ];\n },\n $pipeline->getStages()\n );\n\n $stages = array_merge($stages, $pipelineStages);\n }\n\n return $stages;\n }\n\n public function fetchOpportunityPipelines(): array\n {\n $pipelines = [];\n\n try {\n $apiResponse = $this->makeRequest('/crm/v3/pipelines/deals');\n } catch (\\Exception $e) {\n $this->log->info('[Hubspot] Failed to fetch opportunity pipelines', [\n 'reason' => $e->getMessage(),\n ]);\n\n return [];\n }\n\n $response = $apiResponse->toArray();\n\n foreach ($response['results'] as $pipeline) {\n $pipelines[] = [\n 'id' => $pipeline['id'],\n 'label' => $pipeline['label'],\n ];\n }\n\n return $pipelines;\n }\n\n /**\n * @return array<CrmFieldOption>\n */\n public function fetchMeetingOutcomeFieldOptions(Field $field): array\n {\n return $field->getCrmProviderId() === 'meetingOutcome'\n ? $this->fetchMeetingOutcomeTypes()\n : $this->fetchCallActivityTypes();\n }\n\n public function fetchMeetingOutcomeTypes(): array\n {\n return $this->extractMeetingTypeOptions(\n 'https://api.hubapi.com/crm/v3/properties/meeting/hs_meeting_outcome'\n );\n }\n\n public function fetchCallActivityTypes(): array\n {\n return $this->extractMeetingTypeOptions(\n 'https://api.hubapi.com/crm/v3/properties/call/hs_activity_type'\n );\n }\n\n private function extractMeetingTypeOptions(string $endpoint): array\n {\n /** @var Response $response */\n $response = $this->getInstance()\n ->getClient()\n ->request('GET', $endpoint);\n\n /**\n * @var array<array{\n * value: string,\n * label: string,\n * displayOrder: int\n * }> $optionData\n */\n $optionData = $response->toArray()['options'] ?? [];\n\n $options = [];\n foreach ($optionData as $item) {\n $options[] = [\n 'id' => $item['value'],\n 'value' => $item['value'],\n 'label' => $item['label'],\n 'display_order' => $item['displayOrder'],\n ];\n }\n\n return $options;\n }\n\n /**\n * @return array<CrmFieldOption>\n */\n public function fetchDispositionFieldOptions(): array\n {\n $options = [];\n\n $dispositions = $this->fetchCallDispositions();\n\n foreach ($dispositions as $disposition) {\n if ($disposition['deleted'] !== false) {\n continue;\n }\n\n $option['value'] = $disposition['id'];\n $option['id'] = $disposition['id'];\n $option['label'] = $disposition['label'];\n\n $options[] = $option;\n }\n\n return $options;\n }\n\n /**\n * @return array<CrmFieldOption>\n */\n public function fetchOpportunityFieldOptions(Field $field): array\n {\n if ($field->isStageField()) {\n return $this->fetchOpportunityPipelineStages();\n }\n\n if ($field->isPipelineField()) {\n return $this->fetchOpportunityPipelines();\n }\n\n return $this->fetchPropertyOptions('deals', $field->getCrmProviderId());\n }\n\n /**\n * @throws BadRequest\n * @throws HubspotException\n */\n public function makeRequest(string $endpoint, $method = 'GET', $payload = [], ?string $queryString = null)\n {\n $endpoint = self::BASE_URL . $endpoint;\n\n if ($method === 'GET') {\n $response = $this->getInstance()->getClient()?->request(\n method: $method,\n endpoint: $endpoint,\n query_string: $queryString\n );\n } else {\n $response = $this->getInstance()->getClient()->request($method, $endpoint, [\n 'json' => ($payload),\n ]);\n }\n\n $max = $response->getHeaderLine('X-HubSpot-RateLimit-Max'); // \"110\"\n $remaining = $response->getHeaderLine('X-HubSpot-RateLimit-Remaining'); // \"109\"\n $interval = $response->getHeaderLine('X-HubSpot-RateLimit-Interval-Milliseconds'); // \"10000\"\n $body = json_decode((string) $response->getBody(), true);\n\n \\Illuminate\\Support\\Facades\\Log::channel('custom_channel')->info('$max ' . PHP_EOL . print_r($max, true));\n \\Illuminate\\Support\\Facades\\Log::channel('custom_channel')->info('$remaining ' . PHP_EOL . print_r($remaining, true));\n \\Illuminate\\Support\\Facades\\Log::channel('custom_channel')->info('$interval ' . PHP_EOL . print_r($interval, true));\n \\Illuminate\\Support\\Facades\\Log::channel('custom_channel')->info('$body ' . PHP_EOL . print_r($body, true));\n\n return $response;\n }\n\n /**\n * @throws BadRequest\n * @throws HubspotException\n */\n public function createMeeting(array $payload): Response\n {\n $endpoint = '/crm/v3/objects/meetings';\n\n return $this->makeRequest($endpoint, 'POST', $payload);\n }\n\n /**\n * @throws BadRequest\n * @throws HubspotException\n */\n public function updateMeeting(string $meetingId, array $payload): Response\n {\n $endpoint = '/crm/v3/objects/meetings/' . $meetingId;\n\n return $this->makeRequest($endpoint, 'PATCH', $payload);\n }\n\n /**\n * @throws \\Exception\n */\n public function createNote(\n string $body,\n string $ownerId,\n int $timestamp,\n string $objectId,\n NoteObject $noteObject\n ): ?string {\n try {\n $noteInput = new SimplePublicObjectInput([\n 'properties' => [\n 'hs_note_body' => $body,\n 'hubspot_owner_id' => $ownerId,\n 'hs_timestamp' => $timestamp,\n ],\n ]);\n\n // Create note\n $note = $this->getNewInstance()->crm()->objects()->basicApi()->create('note', $noteInput);\n\n $this->getNewInstance()->crm()->objects()->associationsApi()->create(\n 'note',\n $note->getId(),\n $this->getNoteObject($noteObject),\n $objectId,\n $this->getNoteAssociationType($noteObject),\n );\n\n return $note->getId();\n } catch (\\Exception $e) {\n $this->log->error('[Hubspot] Failed to create note', [\n 'objectId' => $objectId,\n 'noteObject' => $noteObject->getObjectType(),\n 'reason' => $e->getMessage(),\n ]);\n\n \\Sentry::captureException($e);\n }\n\n return null;\n }\n\n public function updateEngagement(string $objectId, array $engagement, array $metadata): void\n {\n $this->getInstance()->engagements()->update($objectId, $engagement, $metadata);\n }\n\n public function getEngagementData(string $engagementId): array\n {\n $engagement = $this->getInstance()->engagements()->get($engagementId);\n\n return $engagement->toArray();\n }\n\n public function createEngagement(array $engagement, array $associations, array $metadata): Response\n {\n return $this->getInstance()\n ->engagements()\n ->create($engagement, $associations, $metadata);\n }\n\n public function isUnauthorizedException(\\Exception $e): bool\n {\n // Check for specific HubSpot API exception types first\n if ($e instanceof BadRequest) {\n // BadRequest can contain 401 status codes\n return $e->getCode() === 401;\n }\n\n // Check for HTTP client exceptions with status codes\n if ($e instanceof \\GuzzleHttp\\Exception\\RequestException && $e->hasResponse()) {\n $response = $e->getResponse();\n if ($response !== null) {\n return $response->getStatusCode() === 401;\n }\n }\n\n // Check for Guzzle HTTP exceptions\n if ($e instanceof \\GuzzleHttp\\Exception\\ClientException) {\n return $e->getCode() === 401;\n }\n\n // Fallback to string matching as last resort, but be more specific\n $message = strtolower($e->getMessage());\n\n return str_contains($message, '401 unauthorized') ||\n str_contains($message, 'http 401') ||\n str_contains($message, 'status code 401') ||\n (preg_match('/\\b401\\b/', $message) && str_contains($message, 'unauthorized'));\n }\n\n /**\n * Validates and refreshes the access token if needed before API requests.\n * This ensures long-running processes don't fail due to token expiration.\n *\n * @throws SocialAccountTokenInvalidException\n */\n public function ensureValidToken(): void\n {\n if ($this->oauthAccount === null) {\n return;\n }\n\n $newToken = $this->tokenManager->ensureValidToken($this->oauthAccount);\n if ($newToken !== null) {\n $this->accessToken = $newToken;\n }\n }\n\n public function getConfig()\n {\n return $this->config;\n }\n\n // returns only active (archived=false)\n public function getOwners(): array\n {\n return $this->getNewInstance()->crm()->owners()->getAll();\n }\n\n /**\n * @param bool $archived\n *\n * @return array<Owner>|[]\n */\n public function getOwnersArchived(bool $archived = true): array\n {\n $endpoint = '/crm/v3/owners';\n $queryParams = [\n 'archived' => $archived ? 'true' : 'false',\n ];\n $queryString = http_build_query($queryParams);\n\n $owners = [];\n\n try {\n $response = $this->makeRequest(endpoint: $endpoint, queryString: $queryString);\n $responseData = $response?->toArray();\n\n foreach ($responseData['results'] as $result) {\n try {\n $owners[] = Owner::create($result);\n } catch (Throwable $e) {\n $this->log->error('[HubSpot] Failed to process owner data', [\n 'result' => $result,\n 'error' => $e->getMessage(),\n ]);\n\n continue;\n }\n }\n } catch (Throwable $e) {\n $this->log->error('HubSpot] Failed to fetch owners', [\n 'archived' => $archived,\n 'error' => $e->getMessage(),\n ]);\n\n return [];\n }\n\n return $owners;\n }\n\n public function getMeeting(string $engagementId): ObjectWithAssociations\n {\n return $this->getNewInstance()->crm()->objects()->basicApi()\n ->getById('meeting', $engagementId, null, 'contact,company,deal');\n }\n\n public function deleteEngagement(string $engagementId): void\n {\n $this->getInstance()->engagements()->delete((int) $engagementId);\n }\n\n public function getAssociationsData(array $ids, string $fromObject, string $toObject): array\n {\n $associationData = [];\n $idChunks = array_chunk($ids, self::ASSOCIATIONS_BATCH_SIZE_LIMIT);\n\n foreach ($idChunks as $idChunk) {\n try {\n $batchInput = new \\HubSpot\\Client\\Crm\\Associations\\Model\\BatchInputPublicObjectId();\n $batchInput->setInputs(array_map(function ($id) {\n $publicObjectId = new \\HubSpot\\Client\\Crm\\Associations\\Model\\PublicObjectId();\n $publicObjectId->setId($id);\n\n return $publicObjectId;\n }, $idChunk));\n\n $associatedObjectsData = $this\n ->getNewInstance()\n ->crm()\n ->associations()\n ->batchApi()\n ->read($fromObject, $toObject, $batchInput);\n\n if ($associatedObjectsData instanceof \\HubSpot\\Client\\Crm\\Associations\\Model\\BatchResponsePublicAssociationMulti) {\n foreach ($associatedObjectsData->getResults() as $association) {\n $from = $association->getFrom()->getId();\n $toAssociations = $association->getTo();\n\n if (! empty($toAssociations)) {\n $associationData[$from] = array_map(function ($item) {\n return $item->getId();\n }, $toAssociations);\n }\n }\n }\n } catch (\\Exception $e) {\n $this->log->error('[Hubspot] Failed to fetch associations', [\n 'from_object' => $fromObject,\n 'to_object' => $toObject,\n 'reason' => $e->getMessage(),\n ]);\n }\n }\n\n return $associationData;\n }\n\n /**\n * @throws \\Exception\n */\n private function getNoteAssociationType(NoteObject $noteObject): string\n {\n return match($noteObject) {\n NoteObject::Opportunity => 'note_to_deal',\n NoteObject::Lead, NoteObject::Contact => 'note_to_contact', // or 'note_to_lead' if your portal supports it\n NoteObject::Account => 'note_to_company',\n NoteObject::Call, NoteObject::Event => throw new \\Exception('Not supported'),\n };\n }\n\n /**\n * @throws \\Exception\n */\n private function getNoteObject(NoteObject $noteObject): string\n {\n return match($noteObject) {\n NoteObject::Opportunity => 'deal',\n NoteObject::Lead, NoteObject::Contact => 'contact',\n NoteObject::Account => 'company',\n NoteObject::Call, NoteObject::Event => throw new \\Exception('Not supported'),\n };\n }\n\n public function addAssociations(string $objectType, string $associationType, array $payload): Response\n {\n $endpoint = \"/crm/v4/associations/$objectType/$associationType/batch/create\";\n\n return $this->makeRequest($endpoint, 'POST', $payload);\n }\n\n public function removeAssociations(string $objectType, string $associationType, array $payload): Response\n {\n $endpoint = \"/crm/v4/associations/$objectType/$associationType/batch/archive\";\n\n return $this->makeRequest($endpoint, 'POST', $payload);\n }\n}","role_description":"text entry area","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"Project","depth":3,"on_screen":false,"role_description":"text"},{"role":"AXButton","text":"Project","depth":3,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"New File or Directory…","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.018055556,"height":0.026666667},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Expand Selected","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.018055556,"height":0.026666667},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Collapse All","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.018055556,"height":0.026666667},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Options","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.018055556,"height":0.026666667},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Hide","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.018055556,"height":0.026666667},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"app ~/jiminny/app, folder","depth":6,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":".circleci, folder","depth":7,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":".cursor, folder","depth":7,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":".github","depth":7,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":".sonarlint, folder","depth":7,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":".vscode, folder","depth":7,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":".windsurf, folder","depth":7,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"app, sources root","depth":7,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Actions, folder","depth":8,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Component, folder","depth":8,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Acl, folder","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"ActionItems, folder","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Activity, folder","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"ActivityAnalytics, folder","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"ActivitySearch, folder","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"AiActivityType, folder","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"AiAutomation, folder","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"AiCallScoring, folder","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"AskAnything, folder","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Dtos, folder","depth":10,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Events, folder","depth":10,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"AskAnythingPromptService.php","depth":10,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"HistoryService.php","depth":10,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"AskJiminnyAi, folder","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"AWS, folder","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"BillingManagement, folder","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Cache, folder","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"CoachingFeedback, folder","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Country, folder","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"CustomerApi, folder","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Database, folder","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Datadog, folder","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"DateTime, folder","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"DealInsights, folder","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"DealRisks, folder","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"ElasticSearch","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Eloquent","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Encoding","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Encryption","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"ES","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Faker","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"FeatureFlags","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"FFMpeg","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"FileSystem","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Gecko","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Gong","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"GuzzleHttp","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"KeyPoints","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Kiosk","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"LanguageDetection","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"LiveFeed","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Locks","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Math","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"MediaPipeline","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"MeetingBot, folder","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"MobileSettings, folder","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Model, folder","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Notification, folder","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Nudge, folder","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"ParagraphBreaker, folder","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"ParticipantSpeech, folder","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"PartitionedCookie, folder","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"PlaybackPage, folder","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Playlist, folder","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Prophet, folder","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"ProphetAi, folder","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"ProsperWorks, folder","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Queue, folder","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Job, folder","depth":10,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"RateLimitAware.php, abstract class","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"RateLimitAwareWrapper.php, class","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"BotsQueueConstants.php","depth":10,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Constants.php","depth":10,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"ProcessingQueueConstants.php","depth":10,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Router, folder","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Saml2, folder","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"SCIM, folder","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Seeder, folder","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Sentry, folder","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Serializer, folder","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Settings, folder","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Sidekick, folder","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Slack, folder","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"TeamInsights, folder","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"TimeMemoryMapper, folder","depth":9,"on_screen":false,"role_description":"text"}]...
|
-6881570513140039651
|
-8151256403349000395
|
click
|
accessibility
|
NULL
|
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
[2026-04-27 10:26:25] local.INFO: Jiminny\Console\Commands\Command::run Memory usage before starting command {"command":"mailbox:sync","memoryBeforeCommandInMb":60.0,"memoryPeakBeforeCommandInMb":99.727} {"correlation_id":"7b1ea2fb-8f36-4897-9970-91b4abba2cc7","trace_id":"265be7c0-bf8f-4037-a0d2-57df3ef1d7e1"}
[2026-04-27 10:26:25] local.INFO: [EmailSchedule] STARTING Inbox Sync {"host":"docker_lamp_1"} {"correlation_id":"7b1ea2fb-8f36-4897-9970-91b4abba2cc7","trace_id":"265be7c0-bf8f-4037-a0d2-57df3ef1d7e1"}
[2026-04-27 10:26:25] local.INFO: [EmailSchedule] FINISHED Inbox Sync {"host":"docker_lamp_1","events":2} {"correlation_id":"7b1ea2fb-8f36-4897-9970-91b4abba2cc7","trace_id":"265be7c0-bf8f-4037-a0d2-57df3ef1d7e1"}
[2026-04-27 10:26:25] local.INFO: Jiminny\Console\Commands\Command::run Memory usage for command {"command":"mailbox:sync","memoryBeforeCommandInMb":60.0,"memoryAfterCommandInMB":62.0,"memoryPeakBeforeCommandInMb":99.727,"memoryPeakAfterCommandInMB":99.727} {"correlation_id":"7b1ea2fb-8f36-4897-9970-91b4abba2cc7","trace_id":"265be7c0-bf8f-4037-a0d2-57df3ef1d7e1"}
[2026-04-27 10:26:26] local.INFO: [automated-reports] Started {"correlation_id":"d3f79034-397b-4936-9305-bdd99b23f2dd","trace_id":"aa0f8702-73eb-4947-a765-2e89244599c3"}
[2026-04-27 10:26:26] local.INFO: [automated-reports] Checking conditions {"isMonday":true,"isWeekend":false,"isFirstDayOfMonth":false,"currentMonth":4,"isQuarterlyMonth":true} {"correlation_id":"d3f79034-397b-4936-9305-bdd99b23f2dd","trace_id":"aa0f8702-73eb-4947-a765-2e89244599c3"}
[2026-04-27 10:26:26] local.INFO: [automated-reports] Processing daily reports {"correlation_id":"d3f79034-397b-4936-9305-bdd99b23f2dd","trace_id":"aa0f8702-73eb-4947-a765-2e89244599c3"}
[2026-04-27 10:26:26] local.INFO: [automated-reports] Found 1 daily reports to process {"correlation_id":"d3f79034-397b-4936-9305-bdd99b23f2dd","trace_id":"aa0f8702-73eb-4947-a765-2e89244599c3"}
[2026-04-27 10:26:26] local.INFO: [automated-reports] Dispatching Generate Report job for report {"reportUuid":"4f6ca2b5-1993-48aa-99ad-b66f19f15d43","teamId":1,"frequency":"weekly","type":"ask_jiminny"} {"correlation_id":"d3f79034-397b-4936-9305-bdd99b23f2dd","trace_id":"aa0f8702-73eb-4947-a765-2e89244599c3"}
[2026-04-27 10:26:26] local.INFO: [automated-reports] Processing weekly reports {"correlation_id":"d3f79034-397b-4936-9305-bdd99b23f2dd","trace_id":"aa0f8702-73eb-4947-a765-2e89244599c3"}
[2026-04-27 10:26:26] local.INFO: [automated-reports] Found 1 weekly reports to process {"correlation_id":"d3f79034-397b-4936-9305-bdd99b23f2dd","trace_id":"aa0f8702-73eb-4947-a765-2e89244599c3"}
[2026-04-27 10:26:26] local.INFO: [automated-reports] Dispatching Generate Report job for report {"reportUuid":"4f6ca2b5-1993-48aa-99ad-b66f19f15d43","teamId":1,"frequency":"weekly","type":"ask_jiminny"} {"correlation_id":"d3f79034-397b-4936-9305-bdd99b23f2dd","trace_id":"aa0f8702-73eb-4947-a765-2e89244599c3"}
[2026-04-27 10:26:26] local.INFO: [automated-reports] Completed {"correlation_id":"d3f79034-397b-4936-9305-bdd99b23f2dd","trace_id":"aa0f8702-73eb-4947-a765-2e89244599c3"}
[2026-04-27 10:26:26] local.INFO: [HubSpot Journal Polling] Getting offset from database {"offset":"","jiminny_team_id":1} {"correlation_id":"b453f0f4-0d2c-491c-a935-14c60265ec18","trace_id":"a50202d6-5601-46b4-a02e-cf5ac8d061fa"}
[2026-04-27 10:26:26] local.INFO: [HubSpot Journal API] Fetching latest journal entry {"url":"[URL_WITH_CREDENTIALS] {"correlation_id":"d3e3eb3d-5c83-4d6e-9592-aaa38c1843a4","trace_id":"265be7c0-bf8f-4037-a0d2-57df3ef1d7e1"}
[2026-04-27 10:26:28] local.INFO: [Gmail] imported 7 emails via full sync workflow for inbox 212 {"correlation_id":"d3e3eb3d-5c83-4d6e-9592-aaa38c1843a4","trace_id":"265be7c0-bf8f-4037-a0d2-57df3ef1d7e1"}
[2026-04-27 10:26:28] local.INFO: [Gmail] seeding inbox 212 with last message time : 2026-04-27 10:24:57 {"correlation_id":"d3e3eb3d-5c83-4d6e-9592-aaa38c1843a4","trace_id":"265be7c0-bf8f-4037-a0d2-57df3ef1d7e1"}
[2026-04-27 10:26:28] local.INFO: [Sync Mailbox] Sync complete {"inbox_id":212} {"correlation_id":"d3e3eb3d-5c83-4d6e-9592-aaa38c1843a4","trace_id":"265be7c0-bf8f-4037-a0d2-57df3ef1d7e1"}
[2026-04-27 10:27:04] local.INFO: Jiminny\Console\Commands\Command::run Memory usage before starting command {"command":"meeting-bot:schedule-bot","memoryBeforeCommandInMb":60.0,"memoryPeakBeforeCommandInMb":99.727} {"correlation_id":"f55fbf77-7e5f-4c87-accd-011697ca0fa3","trace_id":"9bd1c431-26de-4e31-83d8-3129ed9b3b79"}
[2026-04-27 10:27:04] local.INFO: [ScheduleBotCommand] Number of activities to be captured: 0 {"correlation_id":"f55fbf77-7e5f-4c87-accd-011697ca0fa3","trace_id":"9bd1c431-26de-4e31-83d8-3129ed9b3b79"}
[2026-04-27 10:27:04] local.INFO: Jiminny\Console\Commands\Command::run Memory usage for command {"command":"meeting-bot:schedule-bot","memoryBeforeCommandInMb":60.0,"memoryAfterCommandInMB":62.0,"memoryPeakBeforeCommandInMb":99.727,"memoryPeakAfterCommandInMB":99.727} {"correlation_id":"f55fbf77-7e5f-4c87-accd-011697ca0fa3","trace_id":"9bd1c431-26de-4e31-83d8-3129ed9b3b79"}
[2026-04-27 10:27:05] local.INFO: Jiminny\Console\Commands\Command::run Memory usage before starting command {"command":"dialers:monitor-activities","memoryBeforeCommandInMb":60.0,"memoryPeakBeforeCommandInMb":99.727} {"correlation_id":"da241053-5f3b-4d02-9504-4ef2987cdbd7","trace_id":"364d3fc7-0f11-40c6-8697-d18eff2c4cb6"}
[2026-04-27 10:27:05] local.INFO: Jiminny\Console\Commands\Command::run Memory usage for command {"command":"dialers:monitor-activities","memoryBeforeCommandInMb":60.0,"memoryAfterCommandInMB":62.0,"memoryPeakBeforeCommandInMb":99.727,"memoryPeakAfterCommandInMB":99.727} {"correlation_id":"da241053-5f3b-4d02-9504-4ef2987cdbd7","trace_id":"364d3fc7-0f11-40c6-8697-d18eff2c4cb6"}
[2026-04-27 10:27:06] local.NOTICE: Monitoring start {"correlation_id":"ad0d871a-a1fd-4725-b2f7-ac8f0fc1857e","trace_id":"cb504e84-d924-43a0-82d5-faf1840134ba"}
[2026-04-27 10:27:06] local.NOTICE: Monitoring end {"correlation_id":"ad0d871a-a1fd-4725-b2f7-ac8f0fc1857e","trace_id":"cb504e84-d924-43a0-82d5-faf1840134ba"}
[2026-04-27 10:27:08] local.INFO: Jiminny\Console\Commands\Command::run Memory usage before starting command {"command":"mailbox:skip-lists:refresh","memoryBeforeCommandInMb":60.0,"memoryPeakBeforeCommandInMb":99.727} {"correlation_id":"b8652f0d-1cee-44ae-a0ea-3d3137a882c1","trace_id":"7fe19940-0813-4199-a2a8-958de09bd0ee"}
[2026-04-27 10:27:08] local.INFO: Jiminny\Console\Commands\Command::run Memory usage for command {"command":"mailbox:skip-lists:refresh","memoryBeforeCommandInMb":60.0,"memoryAfterCommandInMB":62.0,"memoryPeakBeforeCommandInMb":99.727,"memoryPeakAfterCommandInMB":99.727} {"correlation_id":"b8652f0d-1cee-44ae-a0ea-3d3137a882c1","trace_id":"7fe19940-0813-4199-a2a8-958de09bd0ee"}
[2026-04-27 10:27:09] local.INFO: Jiminny\Console\Commands\Command::run Memory usage before starting command {"command":"mailbox:batch:process","memoryBeforeCommandInMb":60.0,"memoryPeakBeforeCommandInMb":99.727} {"correlation_id":"06190462-0f47-403f-8e95-ad5d05bff1fe","trace_id":"4aa6bed6-05a1-4661-a9c9-edadc61d84ac"}
[2026-04-27 10:27:09] local.INFO: [EmailSchedule] STARTING batch process {"host":"docker_lamp_1"} {"correlation_id":"06190462-0f47-403f-8e95-ad5d05bff1fe","trace_id":"4aa6bed6-05a1-4661-a9c9-edadc61d84ac"}
[2026-04-27 10:27:09] local.INFO: [EmailSchedule] FINISHED batch process {"host":"docker_lamp_1","processed":0} {"correlation_id":"06190462-0f47-403f-8e95-ad5d05bff1fe","trace_id":"4aa6bed6-05a1-4661-a9c9-edadc61d84ac"}
[2026-04-27 10:27:09] local.INFO: Jiminny\Console\Commands\Command::run Memory usage for command {"command":"mailbox:batch:process","memoryBeforeCommandInMb":60.0,"memoryAfterCommandInMB":62.0,"memoryPeakBeforeCommandInMb":99.727,"memoryPeakAfterCommandInMB":99.727} {"correlation_id":"06190462-0f47-403f-8e95-ad5d05bff1fe","trace_id":"4aa6bed6-05a1-4661-a9c9-edadc61d84ac"}
[2026-04-27 10:27:11] local.INFO: Jiminny\Console\Commands\Command::run Memory usage before starting command {"command":"mailbox:batch:create","memoryBeforeCommandInMb":60.0,"memoryPeakBeforeCommandInMb":99.727} {"correlation_id":"b561c5d5-e6e3-4091-9517-b0458190a360","trace_id":"bf9679fb-b785-40cb-b814-04a1dc385a1c"}
[2026-04-27 10:27:11] local.INFO: [EmailSchedule] STARTING batch create {"host":"docker_lamp_1"} {"correlation_id":"b561c5d5-e6e3-4091-9517-b0458190a360","trace_id":"bf9679fb-b785-40cb-b814-04a1dc385a1c"}
[2026-04-27 10:27:11] local.INFO: [EmailSchedule] FINISHED batch create {"host":"docker_lamp_1"} {"correlation_id":"b561c5d5-e6e3-4091-9517-b0458190a360","trace_id":"bf9679fb-b785-40cb-b814-04a1dc385a1c"}
[2026-04-27 10:27:11] local.INFO: Jiminny\Console\Commands\Command::run Memory usage for command {"command":"mailbox:batch:create","memoryBeforeCommandInMb":60.0,"memoryAfterCommandInMB":62.0,"memoryPeakBeforeCommandInMb":99.727,"memoryPeakAfterCommandInMB":99.727} {"correlation_id":"b561c5d5-e6e3-4091-9517-b0458190a360","trace_id":"bf9679fb-b785-40cb-b814-04a1dc385a1c"}
[2026-04-27 10:27:14] local.INFO: [Jiminny\Jobs\Mailbox\CreateBatches] processed 2 inboxes and created 1 batches {"userId":null,"batchSize":30,"maxBatches":1000} {"correlation_id":"98416296-43c2-4834-927e-4a0e86dc99cf","trace_id":"bf9679fb-b785-40cb-b814-04a1dc385a1c"}
[2026-04-27 10:28:04] local.INFO: Jiminny\Console\Commands\Command::run Memory usage before starting command {"command":"meeting-bot:schedule-bot","memoryBeforeCommandInMb":60.0,"memoryPeakBeforeCommandInMb":99.727} {"correlation_id":"5852bcf1-af92-4d24-8a2d-08a9d4a5cfac","trace_id":"25af5e8c-a148-493e-ac9f-4d53f1960177"}
[2026-04-27 10:28:04] local.INFO: [ScheduleBotCommand] Number of activities to be captured: 0 {"correlation_id":"5852bcf1-af92-4d24-8a2d-08a9d4a5cfac","trace_id":"25af5e8c-a148-493e-ac9f-4d53f1960177"}
[2026-04-27 10:28:04] local.INFO: Jiminny\Console\Commands\Command::run Memory usage for command {"command":"meeting-bot:schedule-bot","memoryBeforeCommandInMb":60.0,"memoryAfterCommandInMB":62.0,"memoryPeakBeforeCommandInMb":99.727,"memoryPeakAfterCommandInMB":99.727} {"correlation_id":"5852bcf1-af92-4d24-8a2d-08a9d4a5cfac","trace_id":"25af5e8c-a148-493e-ac9f-4d53f1960177"}
[2026-04-27 10:28:06] local.INFO: Jiminny\Console\Commands\Command::run Memory usage before starting command {"command":"dialers:monitor-activities","memoryBeforeCommandInMb":60.0,"memoryPeakBeforeCommandInMb":99.727} {"correlation_id":"cc1cff1f-71b4-4bb1-800f-66e27c0f2b3a","trace_id":"c03103a3-9ab8-480b-9753-7c7c6488f246"}
[2026-04-27 10:28:06] local.INFO: Jiminny\Console\Commands\Command::run Memory usage for command {"command":"dialers:monitor-activities","memoryBeforeCommandInMb":60.0,"memoryAfterCommandInMB":62.0,"memoryPeakBeforeCommandInMb":99.727,"memoryPeakAfterCommandInMB":99.727} {"correlation_id":"cc1cff1f-71b4-4bb1-800f-66e27c0f2b3a","trace_id":"c03103a3-9ab8-480b-9753-7c7c6488f246"}
[2026-04-27 10:28:09] local.ERROR: Target class [Jiminny\Repositories\AjReportsRepository] does not exist. {"exception":"[object] (Illuminate\\Contracts\\Container\\BindingResolutionException(code: 0): Target class [Jiminny\\Repositories\\AjReportsRepository] does not exist. at /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php:1124)
[stacktrace]
#0 /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php(933): Illuminate\\Container\\Container->build('Jiminny\\\\Reposit...')
#1 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Application.php(1078): Illuminate\\Container\\Container->resolve('Jiminny\\\\Reposit...', Array, true)
#2 /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php(864): Illuminate\\Foundation\\Application->resolve('Jiminny\\\\Reposit...', Array)
#3 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Application.php(1058): Illuminate\\Container\\Container->make('Jiminny\\\\Reposit...', Array)
#4 /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php(1336): Illuminate\\Foundation\\Application->make('Jiminny\\\\Reposit...')
#5 /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php(1237): Illuminate\\Container\\Container->resolveClass(Object(ReflectionParameter))
#6 /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php(1162): Illuminate\\Container\\Container->resolveDependencies(Array)
#7 /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php(933): Illuminate\\Container\\Container->build('Jiminny\\\\Http\\\\Co...')
#8 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Application.php(1078): Illuminate\\Container\\Container->resolve('Jiminny\\\\Http\\\\Co...', Array, true)
#9 /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php(864): Illuminate\\Foundation\\Application->resolve('Jiminny\\\\Http\\\\Co...', Array)
#10 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Application.php(1058): Illuminate\\Container\\Container->make('Jiminny\\\\Http\\\\Co...', Array)
#11 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Route.php(286): Illuminate\\Foundation\\Application->make('Jiminny\\\\Http\\\\Co...')
#12 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Route.php(1133): Illuminate\\Routing\\Route->getController()
#13 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Route.php(1062): Illuminate\\Routing\\Route->controllerMiddleware()
#14 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Router.php(834): Illuminate\\Routing\\Route->gatherMiddleware()
#15 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Router.php(816): Illuminate\\Routing\\Router->gatherRouteMiddleware(Object(Illuminate\\Routing\\Route))
#16 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Router.php(800): Illuminate\\Routing\\Router->runRouteWithinStack(Object(Illuminate\\Routing\\Route), Object(Illuminate\\Http\\Request))
#17 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Router.php(764): Illuminate\\Routing\\Router->runRoute(Object(Illuminate\\Http\\Request), Object(Illuminate\\Routing\\Route))
#18 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Router.php(753): Illuminate\\Routing\\Router->dispatchToRoute(Object(Illuminate\\Http\\Request))
#19 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Http/Kernel.php(200): Illuminate\\Routing\\Router->dispatch(Object(Illuminate\\Http\\Request))
#20 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(180): Illuminate\\Foundation\\Http\\Kernel->Illuminate\\Foundation\\Http\\{closure}(Object(Illuminate\\Http\\Request))
#21 /home/jiminny/vendor/barryvdh/laravel-debugbar/src/Middleware/InjectDebugbar.php(59): Illuminate\\Pipeline\\Pipeline->Illuminate\\Pipeline\\{closure}(Object(Illuminate\\Http\\Request))
#22 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(219): Barryvdh\\Debugbar\\Middleware\\InjectDebugbar->handle(Object(Illuminate\\Http\\Request), Object(Closure))
#23 /home/jiminny/vendor/laravel/framework/src/Illuminate/Http/Middleware/HandleCors.php(74): Illuminate\\Pipeline\\Pipeline->Illuminate\\Pipeline\\{closure}(Object(Illuminate\\Http\\Request))
#24 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(219): Illuminate\\Http\\Middleware\\HandleCors->handle(Object(Illuminate\\Http\\Request), Object(Closure))
#25 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Http/Middleware/TransformsRequest.php(21): Illuminate\\Pipeline\\Pipeline->Illuminate\\Pipeline\\{closure}(Object(Illuminate\\Http\\Request))
#26 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Http/Middleware/TrimStrings.php(51): Illuminate\\Foundation\\Http\\Middleware\\TransformsRequest->handle(Object(Illuminate\\Http\\Request), Object(Closure))
#27 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(219): Illuminate\\Foundation\\Http\\Middleware\\TrimStrings->handle(Object(Illuminate\\Http\\Request), Object(Closure))
#28 /home/jiminny/vendor/bepsvpt/secure-headers/src/SecureHeadersMiddleware.php(18): Illuminate\\Pipeline\\Pipeline->Illuminate\\Pipeline\\{closure}(Object(Illuminate\\Http\\Request))
#29 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(219): Bepsvpt\\SecureHeaders\\SecureHeadersMiddleware->handle(Object(Illuminate\\Http\\Request), Object(Closure))
#30 /home/jiminny/app/Http/Middleware/SentryContext.php(60): Illuminate\\Pipeline\\Pipeline->Illuminate\\Pipeline\\{closure}(Object(Illuminate\\Http\\Request))
#31 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(219): Jiminny\\Http\\Middleware\\SentryContext->handle(Object(Illuminate\\Http\\Request), Object(Closure))
#32 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Http/Middleware/PreventRequestsDuringMaintenance.php(109): Illuminate\\Pipeline\\Pipeline->Illuminate\\Pipeline\\{closure}(Object(Illuminate\\Http\\Request))
#33 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(219): Illuminate\\Foundation\\Http\\Middleware\\PreventRequestsDuringMaintenance->handle(Object(Illuminate\\Http\\Request), Object(Closure))
#34 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Http/Middleware/InvokeDeferredCallbacks.php(22): Illuminate\\Pipeline\\Pipeline->Illuminate\\Pipeline\\{closure}(Object(Illuminate\\Http\\Request))
#35 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(219): Illuminate\\Foundation\\Http\\Middleware\\InvokeDeferredCallbacks->handle(Object(Illuminate\\Http\\Request), Object(Closure))
#36 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(137): Illuminate\\Pipeline\\Pipeline->Illuminate\\Pipeline\\{closure}(Object(Illuminate\\Http\\Request))
#37 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Http/Kernel.php(175): Illuminate\\Pipeline\\Pipeline->then(Object(Closure))
#38 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Http/Kernel.php(144): Illuminate\\Foundation\\Http\\Kernel->sendRequestThroughRouter(Object(Illuminate\\Http\\Request))
#39 /home/jiminny/public/index.php(51): Illuminate\\Foundation\\Http\\Kernel->handle(Object(Illuminate\\Http\\Request))
#40 {main}
[previous exception] [object] (ReflectionException(code: -1): Class \"Jiminny\\Repositories\\AjReportsRepository\" does not exist at /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php:1122)
[stacktrace]
#0 /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php(1122): ReflectionClass->__construct('Jiminny\\\\Reposit...')
#1 /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php(933): Illuminate\\Container\\Container->build('Jiminny\\\\Reposit...')
#2 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Application.php(1078): Illuminate\\Container\\Container->resolve('Jiminny\\\\Reposit...', Array, true)
#3 /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php(864): Illuminate\\Foundation\\Application->resolve('Jiminny\\\\Reposit...', Array)
#4 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Application.php(1058): Illuminate\\Container\\Container->make('Jiminny\\\\Reposit...', Array)
#5 /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php(1336): Illuminate\\Foundation\\Application->make('Jiminny\\\\Reposit...')
#6 /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php(1237): Illuminate\\Container\\Container->resolveClass(Object(ReflectionParameter))
#7 /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php(1162): Illuminate\\Container\\Container->resolveDependencies(Array)
#8 /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php(933): Illuminate\\Container\\Container->build('Jiminny\\\\Http\\\\Co...')
#9 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Application.php(1078): Illuminate\\Container\\Container->resolve('Jiminny\\\\Http\\\\Co...', Array, true)
#10 /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php(864): Illuminate\\Foundation\\Application->resolve('Jiminny\\\\Http\\\\Co...', Array)
#11 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Application.php(1058): Illuminate\\Container\\Container->make('Jiminny\\\\Http\\\\Co...', Array)
#12 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Route.php(286): Illuminate\\Foundation\\Application->make('Jiminny\\\\Http\\\\Co...')
#13 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Route.php(1133): Illuminate\\Routing\\Route->getController()
#14 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Route.php(1062): Illuminate\\Routing\\Route->controllerMiddleware()
#15 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Router.php(834): Illuminate\\Routing\\Route->gatherMiddleware()
#16 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Router.php(816): Illuminate\\Routing\\Router->gatherRouteMiddleware(Object(Illuminate\\Routing\\Route))
#17 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Router.php(800): Illuminate\\Routing\\Router->runRouteWithinStack(Object(Illuminate\\Routing\\Route), Object(Illuminate\\Http\\Request))
#18 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Router.php(764): Illuminate\\Routing\\Router->runRoute(Object(Illuminate\\Http\\Request), Object(Illuminate\\Routing\\Route))
#19 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Router.php(753): Illuminate\\Routing\\Router->dispatchToRoute(Object(Illuminate\\Http\\Request))
#20 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Http/Kernel.php(200): Illuminate\\Routing\\Router->dispatch(Object(Illuminate\\Http\\Request))
#21 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(180): Illuminate\\Foundation\\Http\\Kernel->Illuminate\\Foundation\\Http\\{closure}(Object(Illuminate\\Http\\Request))
#22 /home/jiminny/vendor/barryvdh/laravel-debugbar/src/Middleware/InjectDebugbar.php(59): Illuminate\\Pipeline\\Pipeline->Illuminate\\Pipeline\\{closure}(Object(Illuminate\\Http\\Request))
#23 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(219): Barryvdh\\Debugbar\\Middleware\\InjectDebugbar->handle(Object(Illuminate\\Http\\Request), Object(Closure))
#24 /home/jiminny/vendor/laravel/framework/src/Illuminate/Http/Middleware/HandleCors.php(74): Illuminate\\Pipeline\\Pipeline->Illuminate\\Pipeline\\{closure}(Object(Illuminate\\Http\\Request))
#25 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(219): Illuminate\\Http\\Middleware\\HandleCors->handle(Object(Illuminate\\Http\\Request), Object(Closure))
#26 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Http/Middleware/TransformsRequest.php(21): Illuminate\\Pipeline\\Pipeline->Illuminate\\Pipeline\\{closure}(Object(Illuminate\\Http\\Request))
#27 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Http/Middleware/TrimStrings.php(51): Illuminate\\Foundation\\Http\\Middleware\\TransformsRequest->handle(Object(Illuminate\\Http\\Request), Object(Closure))
#28 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(219): Illuminate\\Foundation\\Http\\Middleware\\TrimStrings->handle(Object(Illuminate\\Http\\Request), Object(Closure))
#29 /home/jiminny/vendor/bepsvpt/secure-headers/src/SecureHeadersMiddleware.php(18): Illuminate\\Pipeline\\Pipeline->Illuminate\\Pipeline\\{closure}(Object(Illuminate\\Http\\Request))
#30 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(219): Bepsvpt\\SecureHeaders\\SecureHeadersMiddleware->handle(Object(Illuminate\\Http\\Request), Object(Closure))
#31 /home/jiminny/app/Http/Middleware/SentryContext.php(60): Illuminate\\Pipeline\\Pipeline->Illuminate\\Pipeline\\{closure}(Object(Illuminate\\Http\\Request))
#32 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(219): Jiminny\\Http\\Middleware\\SentryContext->handle(Object(Illuminate\\Http\\Request), Object(Closure))
#33 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Http/Middleware/PreventRequestsDuringMaintenance.php(109): Illuminate\\Pipeline\\Pipeline->Illuminate\\Pipeline\\{closure}(Object(Illuminate\\Http\\Request))
#34 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(219): Illuminate\\Foundation\\Http\\Middleware\\PreventRequestsDuringMaintenance->handle(Object(Illuminate\\Http\\Request), Object(Closure))
#35 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Http/Middleware/InvokeDeferredCallbacks.php(22): Illuminate\\Pipeline\\Pipeline->Illuminate\\Pipeline\\{closure}(Object(Illuminate\\Http\\Request))
#36 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(219): Illuminate\\Foundation\\Http\\Middleware\\InvokeDeferredCallbacks->handle(Object(Illuminate\\Http\\Request), Object(Closure))
#37 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(137): Illuminate\\Pipeline\\Pipeline->Illuminate\\Pipeline\\{closure}(Object(Illuminate\\Http\\Request))
#38 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Http/Kernel.php(175): Illuminate\\Pipeline\\Pipeline->then(Object(Closure))
#39 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Http/Kernel.php(144): Illuminate\\Foundation\\Http\\Kernel->sendRequestThroughRouter(Object(Illuminate\\Http\\Request))
#40 /home/jiminny/public/index.php(51): Illuminate\\Foundation\\Http\\Kernel->handle(Object(Illuminate\\Http\\Request))
#41 {main}
"} {"correlation_id":"dfad8817-1dde-4622-8320-f5fc1616a8fe","trace_id":"d21232b2-60fb-4ca0-838c-ba42e61593f9"}
[2026-04-27 10:28:09] local.NOTICE: Monitoring start {"correlation_id":"93744c67-4444-41ff-aaad-6aa9cafe294e","trace_id":"80e396b0-b1ac-4ec8-838c-00021f98072c"}
[2026-04-27 10:28:09] local.NOTICE: Monitoring end {"correlation_id":"93744c67-4444-41ff-aaad-6aa9cafe294e","trace_id":"80e396b0-b1ac-4ec8-838c-00021f98072c"}
[2026-04-27 10:28:10] local.ERROR: Target class [Jiminny\Repositories\AjReportsRepository] does not exist. {"exception":"[object] (Illuminate\\Contracts\\Container\\BindingResolutionException(code: 0): Target class [Jiminny\\Repositories\\AjReportsRepository] does not exist. at /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php:1124)
[stacktrace]
#0 /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php(933): Illuminate\\Container\\Container->build('Jiminny\\\\Reposit...')
#1 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Application.php(1078): Illuminate\\Container\\Container->resolve('Jiminny\\\\Reposit...', Array, true)
#2 /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php(864): Illuminate\\Foundation\\Application->resolve('Jiminny\\\\Reposit...', Array)
#3 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Application.php(1058): Illuminate\\Container\\Container->make('Jiminny\\\\Reposit...', Array)
#4 /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php(1336): Illuminate\\Foundation\\Application->make('Jiminny\\\\Reposit...')
#5 /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php(1237): Illuminate\\Container\\Container->resolveClass(Object(ReflectionParameter))
#6 /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php(1162): Illuminate\\Container\\Container->resolveDependencies(Array)
#7 /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php(933): Illuminate\\Container\\Container->build('Jiminny\\\\Http\\\\Co...')
#8 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Application.php(1078): Illuminate\\Container\\Container->resolve('Jiminny\\\\Http\\\\Co...', Array, true)
#9 /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php(864): Illuminate\\Foundation\\Application->resolve('Jiminny\\\\Http\\\\Co...', Array)
#10 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Application.php(1058): Illuminate\\Container\\Container->make('Jiminny\\\\Http\\\\Co...', Array)
#11 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Route.php(286): Illuminate\\Foundation\\Application->make('Jiminny\\\\Http\\\\Co...')
#12 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Route.php(1133): Illuminate\\Routing\\Route->getController()
#13 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Route.php(1062): Illuminate\\Routing\\Route->controllerMiddleware()
#14 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Router.php(834): Illuminate\\Routing\\Route->gatherMiddleware()
#15 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Router.php(816): Illuminate\\Routing\\Router->gatherRouteMiddleware(Object(Illuminate\\Routing\\Route))
#16 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Router.php(800): Illuminate\\Routing\\Router->runRouteWithinStack(Object(Illuminate\\Routing\\Route), Object(Illuminate\\Http\\Request))
#17 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Router.php(764): Illuminate\\Routing\\Router->runRoute(Object(Illuminate\\Http\\Request), Object(Illuminate\\Routing\\Route))
#18 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Router.php(753): Illuminate\\Routing\\Router->dispatchToRoute(Object(Illuminate\\Http\\Request))
#19 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Http/Kernel.php(200): Illuminate\\Routing\\Router->dispatch(Object(Illuminate\\Http\\Request))
#20 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(180): Illuminate\\Foundation\\Http\\Kernel->Illuminate\\Foundation\\Http\\{closure}(Object(Illuminate\\Http\\Request))
#21 /home/jiminny/vendor/barryvdh/laravel-debugbar/src/Middleware/InjectDebugbar.php(59): Illuminate\\Pipeline\\Pipeline->Illuminate\\Pipeline\\{closure}(Object(Illuminate\\Http\\Request))
#22 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(219): Barryvdh\\Debugbar\\Middleware\\InjectDebugbar->handle(Object(Illuminate\\Http\\Request), Object(Closure))
#23 /home/jiminny/vendor/laravel/framework/src/Illuminate/Http/Middleware/HandleCors.php(74): Illuminate\\Pipeline\\Pipeline->Illuminate\\Pipeline\\{closure}(Object(Illuminate\\Http\\Request))
#24 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(219): Illuminate\\Http\\Middleware\\HandleCors->handle(Object(Illuminate\\Http\\Request), Object(Closure))
#25 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Http/Middleware/TransformsRequest.php(21): Illuminate\\Pipeline\\Pipeline->Illuminate\\Pipeline\\{closure}(Object(Illuminate\\Http\\Request))
#26 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Http/Middleware/TrimStrings.php(51): Illuminate\\Foundation\\Http\\Middleware\\TransformsRequest->handle(Object(Illuminate\\Http\\Request), Object(Closure))
#27 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(219): Illuminate\\Foundation\\Http\\Middleware\\TrimStrings->handle(Object(Illuminate\\Http\\Request), Object(Closure))
#28 /home/jiminny/vendor/bepsvpt/secure-headers/src/SecureHeadersMiddleware.php(18): Illuminate\\Pipeline\\Pipeline->Illuminate\\Pipeline\\{closure}(Object(Illuminate\\Http\\Request))
#29 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(219): Bepsvpt\\SecureHeaders\\SecureHeadersMiddleware->handle(Object(Illuminate\\Http\\Request), Object(Closure))
#30 /home/jiminny/app/Http/Middleware/SentryContext.php(60): Illuminate\\Pipeline\\Pipeline->Illuminate\\Pipeline\\{closure}(Object(Illuminate\\Http\\Request))
#31 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(219): Jiminny\\Http\\Middleware\\SentryContext->handle(Object(Illuminate\\Http\\Request), Object(Closure))
#32 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Http/Middleware/PreventRequestsDuringMaintenance.php(109): Illuminate\\Pipeline\\Pipeline->Illuminate\\Pipeline\\{closure}(Object(Illuminate\\Http\\Request))
#33 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(219): Illuminate\\Foundation\\Http\\Middleware\\PreventRequestsDuringMaintenance->handle(Object(Illuminate\\Http\\Request), Object(Closure))
#34 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Http/Middleware/InvokeDeferredCallbacks.php(22): Illuminate\\Pipeline\\Pipeline->Illuminate\\Pipeline\\{closure}(Object(Illuminate\\Http\\Request))
#35 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(219): Illuminate\\Foundation\\Http\\Middleware\\InvokeDeferredCallbacks->handle(Object(Illuminate\\Http\\Request), Object(Closure))
#36 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(137): Illuminate\\Pipeline\\Pipeline->Illuminate\\Pipeline\\{closure}(Object(Illuminate\\Http\\Request))
#37 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Http/Kernel.php(175): Illuminate\\Pipeline\\Pipeline->then(Object(Closure))
#38 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Http/Kernel.php(144): Illuminate\\Foundation\\Http\\Kernel->sendRequestThroughRouter(Object(Illuminate\\Http\\Request))
#39 /home/jiminny/public/index.php(51): Illuminate\\Foundation\\Http\\Kernel->handle(Object(Illuminate\\Http\\Request))
#40 {main}
[previous exception] [object] (ReflectionException(code: -1): Class \"Jiminny\\Repositories\\AjReportsRepository\" does not exist at /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php:1122)
[stacktrace]
#0 /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php(1122): ReflectionClass->__construct('Jiminny\\\\Reposit...')
#1 /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php(933): Illuminate\\Container\\Container->build('Jiminny\\\\Reposit...')
#2 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Application.php(1078): Illuminate\\Container\\Container->resolve('Jiminny\\\\Reposit...', Array, true)
#3 /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php(864): Illuminate\\Foundation\\Application->resolve('Jiminny\\\\Reposit...', Array)
#4 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Application.php(1058): Illuminate\\Container\\Container->make('Jiminny\\\\Reposit...', Array)
#5 /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php(1336): Illuminate\\Foundation\\Application->make('Jiminny\\\\Reposit...')
#6 /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php(1237): Illuminate\\Container\\Container->resolveClass(Object(ReflectionParameter))
#7 /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php(1162): Illuminate\\Container\\Container->resolveDependencies(Array)
#8 /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php(933): Illuminate\\Container\\Container->build('Jiminny\\\\Http\\\\Co...')
#9 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Application.php(1078): Illuminate\\Container\\Container->resolve('Jiminny\\\\Http\\\\Co...', Array, true)
#10 /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php(864): Illuminate\\Foundation\\Application->resolve('Jiminny\\\\Http\\\\Co...', Array)
#11 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Application.php(1058): Illuminate\\Container\\Container->make('Jiminny\\\\Http\\\\Co...', Array)
#12 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Route.php(286): Illuminate\\Foundation\\Application->make('Jiminny\\\\Http\\\\Co...')
#13 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Route.php(1133): Illuminate\\Routing\\Route->getController()
#14 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Route.php(1062): Illuminate\\Routing\\Route->controllerMiddleware()
#15 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Router.php(834): Illuminate\\Routing\\Route->gatherMiddleware()
#16 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Router.php(816): Illuminate\\Routing\\Router->gatherRouteMiddleware(Object(Illuminate\\Routing\\Route))
#17 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Router.php(800): Illuminate\\Routing\\Router->runRouteWithinStack(Object(Illuminate\\Routing\\Route), Object(Illuminate\\Http\\Request))
#18 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Router.php(764): Illuminate\\Routing\\Router->runRoute(Object(Illuminate\\Http\\Request), Object(Illuminate\\Routing\\Route))
#19 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Router.php(753): Illuminate\\Routing\\Router->dispatchToRoute(Object(Illuminate\\Http\\Request))
#20 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Http/Kernel.php(200): Illuminate\\Routing\\Router->dispatch(Object(Illuminate\\Http\\Request))
#21 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(180): Illuminate\\Foundation\\Http\\Kernel->Illuminate\\Foundation\\Http\\{closure}(Object(Illuminate\\Http\\Request))
#22 /home/jiminny/vendor/barryvdh/laravel-debugbar/src/Middleware/InjectDebugbar.php(59): Illuminate\\Pipeline\\Pipeline->Illuminate\\Pipeline\\{closure}(Object(Illuminate\\Http\\Request))
#23 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(219): Barryvdh\\Debugbar\\Middleware\\InjectDebugbar->handle(Object(Illuminate\\Http\\Request), Object(Closure))
#24 /home/jiminny/vendor/laravel/framework/src/Illuminate/Http/Middleware/HandleCors.php(74): Illuminate\\Pipeline\\Pipeline->Illuminate\\Pipeline\\{closure}(Object(Illuminate\\Http\\Request))
#25 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(219): Illuminate\\Http\\Middleware\\HandleCors->handle(Object(Illuminate\\Http\\Request), Object(Closure))
#26 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Http/Middleware/TransformsRequest.php(21): Illuminate\\Pipeline\\Pipeline->Illuminate\\Pipeline\\{closure}(Object(Illuminate\\Http\\Request))
#27 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Http/Middleware/TrimStrings.php(51): Illuminate\\Foundation\\Http\\Middleware\\TransformsRequest->handle(Object(Illuminate\\Http\\Request), Object(Closure))
#28 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(219): Illuminate\\Foundation\\Http\\Middleware\\TrimStrings->handle(Object(Illuminate\\Http\\Request), Object(Closure))
#29 /home/jiminny/vendor/bepsvpt/secure-headers/src/SecureHeadersMiddleware.php(18): Illuminate\\Pipeline\\Pipeline->Illuminate\\Pipeline\\{closure}(Object(Illuminate\\Http\\Request))
#30 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(219): Bepsvpt\\SecureHeaders\\SecureHeadersMiddleware->handle(Object(Illuminate\\Http\\Request), Object(Closure))
#31 /home/jiminny/app/Http/Middleware/SentryContext.php(60): Illuminate\\Pipeline\\Pipeline->Illuminate\\Pipeline\\{closure}(Object(Illuminate\\Http\\Request))
#32 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(219): Jiminny\\Http\\Middleware\\SentryContext->handle(Object(Illuminate\\Http\\Request), Object(Closure))
#33 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Http/Middleware/PreventRequestsDuringMaintenance.php(109): Illuminate\\Pipeline\\Pipeline->Illuminate\\Pipeline\\{closure}(Object(Illuminate\\Http\\Request))
#34 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(219): Illuminate\\Foundation\\Http\\Middleware\\PreventRequestsDuringMaintenance->handle(Object(Illuminate\\Http\\Request), Object(Closure))
#35 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Http/Middleware/InvokeDeferredCallbacks.php(22): Illuminate\\Pipeline\\Pipeline->Illuminate\\Pipeline\\{closure}(Object(Illuminate\\Http\\Request))
#36 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(219): Illuminate\\Foundation\\Http\\Middleware\\InvokeDeferredCallbacks->handle(Object(Illuminate\\Http\\Request), Object(Closure))
#37 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(137): Illuminate\\Pipeline\\Pipeline->Illuminate\\Pipeline\\{closure}(Object(Illuminate\\Http\\Request))
#38 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Http/Kernel.php(175): Illuminate\\Pipeline\\Pipeline->then(Object(Closure))
#39 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Http/Kernel.php(144): Illuminate\\Foundation\\Http\\Kernel->sendRequestThroughRouter(Object(Illuminate\\Http\\Request))
#40 /home/jiminny/public/index.php(51): Illuminate\\Foundation\\Http\\Kernel->handle(Object(Illuminate\\Http\\Request))
#41 {main}
"} {"correlation_id":"de768781-5c05-418c-baf3-79f77a8c8694","trace_id":"fec74062-3f6d-4cfc-968a-1bb285496db3"}
[2026-04-27 10:28:12] local.INFO: Jiminny\Console\Commands\Command::run Memory usage before starting command {"command":"mailbox:skip-lists:refresh","memoryBeforeCommandInMb":60.0,"memoryPeakBeforeCommandInMb":99.727} {"correlation_id":"907d15ed-8c84-41a1-a473-590eebb55dbc","trace_id":"ec7bd353-516e-491d-89dd-de44136c1672"}
[2026-04-27 10:28:12] local.INFO: Jiminny\Console\Commands\Command::run Memory usage for command {"command":"mailbox:skip-lists:refresh","memoryBeforeCommandInMb":60.0,"memoryAfterCommandInMB":62.0,"memoryPeakBeforeCommandInMb":99.727,"memoryPeakAfterCommandInMB":99.727} {"correlation_id":"907d15ed-8c84-41a1-a473-590eebb55dbc","trace_id":"ec7bd353-516e-491d-89dd-de44136c1672"}
[2026-04-27 10:28:13] local.ERROR: Target class [Jiminny\Repositories\AjReportsRepository] does not exist. {"exception":"[object] (Illuminate\\Contracts\\Container\\BindingResolutionException(code: 0): Target class [Jiminny\\Repositories\\AjReportsRepository] does not exist. at /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php:1124)
[stacktrace]
#0 /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php(933): Illuminate\\Container\\Container->build('Jiminny\\\\Reposit...')
#1 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Application.php(1078): Illuminate\\Container\\Container->resolve('Jiminny\\\\Reposit...', Array, true)
#2 /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php(864): Illuminate\\Foundation\\Application->resolve('Jiminny\\\\Reposit...', Array)
#3 /home/jiminny/vendor/laravel/framewo...
|
2697
|
NULL
|
NULL
|
NULL
|
|
2700
|
112
|
22
|
2026-05-07T11:36:09.415867+00:00
|
/Users/lukas/.screenpipe/data/data/2026-05-07/1778 /Users/lukas/.screenpipe/data/data/2026-05-07/1778153769415_m2.jpg...
|
PhpStorm
|
faVsco.js – laravel.log
|
True
|
NULL
|
monitor_2
|
NULL
|
NULL
|
NULL
|
NULL
|
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
[2026-04-27 10:26:25] local.INFO: Jiminny\Console\Commands\Command::run Memory usage before starting command {"command":"mailbox:sync","memoryBeforeCommandInMb":60.0,"memoryPeakBeforeCommandInMb":99.727} {"correlation_id":"7b1ea2fb-8f36-4897-9970-91b4abba2cc7","trace_id":"265be7c0-bf8f-4037-a0d2-57df3ef1d7e1"}
[2026-04-27 10:26:25] local.INFO: [EmailSchedule] STARTING Inbox Sync {"host":"docker_lamp_1"} {"correlation_id":"7b1ea2fb-8f36-4897-9970-91b4abba2cc7","trace_id":"265be7c0-bf8f-4037-a0d2-57df3ef1d7e1"}
[2026-04-27 10:26:25] local.INFO: [EmailSchedule] FINISHED Inbox Sync {"host":"docker_lamp_1","events":2} {"correlation_id":"7b1ea2fb-8f36-4897-9970-91b4abba2cc7","trace_id":"265be7c0-bf8f-4037-a0d2-57df3ef1d7e1"}
[2026-04-27 10:26:25] local.INFO: Jiminny\Console\Commands\Command::run Memory usage for command {"command":"mailbox:sync","memoryBeforeCommandInMb":60.0,"memoryAfterCommandInMB":62.0,"memoryPeakBeforeCommandInMb":99.727,"memoryPeakAfterCommandInMB":99.727} {"correlation_id":"7b1ea2fb-8f36-4897-9970-91b4abba2cc7","trace_id":"265be7c0-bf8f-4037-a0d2-57df3ef1d7e1"}
[2026-04-27 10:26:26] local.INFO: [automated-reports] Started {"correlation_id":"d3f79034-397b-4936-9305-bdd99b23f2dd","trace_id":"aa0f8702-73eb-4947-a765-2e89244599c3"}
[2026-04-27 10:26:26] local.INFO: [automated-reports] Checking conditions {"isMonday":true,"isWeekend":false,"isFirstDayOfMonth":false,"currentMonth":4,"isQuarterlyMonth":true} {"correlation_id":"d3f79034-397b-4936-9305-bdd99b23f2dd","trace_id":"aa0f8702-73eb-4947-a765-2e89244599c3"}
[2026-04-27 10:26:26] local.INFO: [automated-reports] Processing daily reports {"correlation_id":"d3f79034-397b-4936-9305-bdd99b23f2dd","trace_id":"aa0f8702-73eb-4947-a765-2e89244599c3"}
[2026-04-27 10:26:26] local.INFO: [automated-reports] Found 1 daily reports to process {"correlation_id":"d3f79034-397b-4936-9305-bdd99b23f2dd","trace_id":"aa0f8702-73eb-4947-a765-2e89244599c3"}
[2026-04-27 10:26:26] local.INFO: [automated-reports] Dispatching Generate Report job for report {"reportUuid":"4f6ca2b5-1993-48aa-99ad-b66f19f15d43","teamId":1,"frequency":"weekly","type":"ask_jiminny"} {"correlation_id":"d3f79034-397b-4936-9305-bdd99b23f2dd","trace_id":"aa0f8702-73eb-4947-a765-2e89244599c3"}
[2026-04-27 10:26:26] local.INFO: [automated-reports] Processing weekly reports {"correlation_id":"d3f79034-397b-4936-9305-bdd99b23f2dd","trace_id":"aa0f8702-73eb-4947-a765-2e89244599c3"}
[2026-04-27 10:26:26] local.INFO: [automated-reports] Found 1 weekly reports to process {"correlation_id":"d3f79034-397b-4936-9305-bdd99b23f2dd","trace_id":"aa0f8702-73eb-4947-a765-2e89244599c3"}
[2026-04-27 10:26:26] local.INFO: [automated-reports] Dispatching Generate Report job for report {"reportUuid":"4f6ca2b5-1993-48aa-99ad-b66f19f15d43","teamId":1,"frequency":"weekly","type":"ask_jiminny"} {"correlation_id":"d3f79034-397b-4936-9305-bdd99b23f2dd","trace_id":"aa0f8702-73eb-4947-a765-2e89244599c3"}
[2026-04-27 10:26:26] local.INFO: [automated-reports] Completed {"correlation_id":"d3f79034-397b-4936-9305-bdd99b23f2dd","trace_id":"aa0f8702-73eb-4947-a765-2e89244599c3"}
[2026-04-27 10:26:26] local.INFO: [HubSpot Journal Polling] Getting offset from database {"offset":"","jiminny_team_id":1} {"correlation_id":"b453f0f4-0d2c-491c-a935-14c60265ec18","trace_id":"a50202d6-5601-46b4-a02e-cf5ac8d061fa"}
[2026-04-27 10:26:26] local.INFO: [HubSpot Journal API] Fetching latest journal entry {"url":"[URL_WITH_CREDENTIALS] {"correlation_id":"d3e3eb3d-5c83-4d6e-9592-aaa38c1843a4","trace_id":"265be7c0-bf8f-4037-a0d2-57df3ef1d7e1"}
[2026-04-27 10:26:28] local.INFO: [Gmail] imported 7 emails via full sync workflow for inbox 212 {"correlation_id":"d3e3eb3d-5c83-4d6e-9592-aaa38c1843a4","trace_id":"265be7c0-bf8f-4037-a0d2-57df3ef1d7e1"}
[2026-04-27 10:26:28] local.INFO: [Gmail] seeding inbox 212 with last message time : 2026-04-27 10:24:57 {"correlation_id":"d3e3eb3d-5c83-4d6e-9592-aaa38c1843a4","trace_id":"265be7c0-bf8f-4037-a0d2-57df3ef1d7e1"}
[2026-04-27 10:26:28] local.INFO: [Sync Mailbox] Sync complete {"inbox_id":212} {"correlation_id":"d3e3eb3d-5c83-4d6e-9592-aaa38c1843a4","trace_id":"265be7c0-bf8f-4037-a0d2-57df3ef1d7e1"}
[2026-04-27 10:27:04] local.INFO: Jiminny\Console\Commands\Command::run Memory usage before starting command {"command":"meeting-bot:schedule-bot","memoryBeforeCommandInMb":60.0,"memoryPeakBeforeCommandInMb":99.727} {"correlation_id":"f55fbf77-7e5f-4c87-accd-011697ca0fa3","trace_id":"9bd1c431-26de-4e31-83d8-3129ed9b3b79"}
[2026-04-27 10:27:04] local.INFO: [ScheduleBotCommand] Number of activities to be captured: 0 {"correlation_id":"f55fbf77-7e5f-4c87-accd-011697ca0fa3","trace_id":"9bd1c431-26de-4e31-83d8-3129ed9b3b79"}
[2026-04-27 10:27:04] local.INFO: Jiminny\Console\Commands\Command::run Memory usage for command {"command":"meeting-bot:schedule-bot","memoryBeforeCommandInMb":60.0,"memoryAfterCommandInMB":62.0,"memoryPeakBeforeCommandInMb":99.727,"memoryPeakAfterCommandInMB":99.727} {"correlation_id":"f55fbf77-7e5f-4c87-accd-011697ca0fa3","trace_id":"9bd1c431-26de-4e31-83d8-3129ed9b3b79"}
[2026-04-27 10:27:05] local.INFO: Jiminny\Console\Commands\Command::run Memory usage before starting command {"command":"dialers:monitor-activities","memoryBeforeCommandInMb":60.0,"memoryPeakBeforeCommandInMb":99.727} {"correlation_id":"da241053-5f3b-4d02-9504-4ef2987cdbd7","trace_id":"364d3fc7-0f11-40c6-8697-d18eff2c4cb6"}
[2026-04-27 10:27:05] local.INFO: Jiminny\Console\Commands\Command::run Memory usage for command {"command":"dialers:monitor-activities","memoryBeforeCommandInMb":60.0,"memoryAfterCommandInMB":62.0,"memoryPeakBeforeCommandInMb":99.727,"memoryPeakAfterCommandInMB":99.727} {"correlation_id":"da241053-5f3b-4d02-9504-4ef2987cdbd7","trace_id":"364d3fc7-0f11-40c6-8697-d18eff2c4cb6"}
[2026-04-27 10:27:06] local.NOTICE: Monitoring start {"correlation_id":"ad0d871a-a1fd-4725-b2f7-ac8f0fc1857e","trace_id":"cb504e84-d924-43a0-82d5-faf1840134ba"}
[2026-04-27 10:27:06] local.NOTICE: Monitoring end {"correlation_id":"ad0d871a-a1fd-4725-b2f7-ac8f0fc1857e","trace_id":"cb504e84-d924-43a0-82d5-faf1840134ba"}
[2026-04-27 10:27:08] local.INFO: Jiminny\Console\Commands\Command::run Memory usage before starting command {"command":"mailbox:skip-lists:refresh","memoryBeforeCommandInMb":60.0,"memoryPeakBeforeCommandInMb":99.727} {"correlation_id":"b8652f0d-1cee-44ae-a0ea-3d3137a882c1","trace_id":"7fe19940-0813-4199-a2a8-958de09bd0ee"}
[2026-04-27 10:27:08] local.INFO: Jiminny\Console\Commands\Command::run Memory usage for command {"command":"mailbox:skip-lists:refresh","memoryBeforeCommandInMb":60.0,"memoryAfterCommandInMB":62.0,"memoryPeakBeforeCommandInMb":99.727,"memoryPeakAfterCommandInMB":99.727} {"correlation_id":"b8652f0d-1cee-44ae-a0ea-3d3137a882c1","trace_id":"7fe19940-0813-4199-a2a8-958de09bd0ee"}
[2026-04-27 10:27:09] local.INFO: Jiminny\Console\Commands\Command::run Memory usage before starting command {"command":"mailbox:batch:process","memoryBeforeCommandInMb":60.0,"memoryPeakBeforeCommandInMb":99.727} {"correlation_id":"06190462-0f47-403f-8e95-ad5d05bff1fe","trace_id":"4aa6bed6-05a1-4661-a9c9-edadc61d84ac"}
[2026-04-27 10:27:09] local.INFO: [EmailSchedule] STARTING batch process {"host":"docker_lamp_1"} {"correlation_id":"06190462-0f47-403f-8e95-ad5d05bff1fe","trace_id":"4aa6bed6-05a1-4661-a9c9-edadc61d84ac"}
[2026-04-27 10:27:09] local.INFO: [EmailSchedule] FINISHED batch process {"host":"docker_lamp_1","processed":0} {"correlation_id":"06190462-0f47-403f-8e95-ad5d05bff1fe","trace_id":"4aa6bed6-05a1-4661-a9c9-edadc61d84ac"}
[2026-04-27 10:27:09] local.INFO: Jiminny\Console\Commands\Command::run Memory usage for command {"command":"mailbox:batch:process","memoryBeforeCommandInMb":60.0,"memoryAfterCommandInMB":62.0,"memoryPeakBeforeCommandInMb":99.727,"memoryPeakAfterCommandInMB":99.727} {"correlation_id":"06190462-0f47-403f-8e95-ad5d05bff1fe","trace_id":"4aa6bed6-05a1-4661-a9c9-edadc61d84ac"}
[2026-04-27 10:27:11] local.INFO: Jiminny\Console\Commands\Command::run Memory usage before starting command {"command":"mailbox:batch:create","memoryBeforeCommandInMb":60.0,"memoryPeakBeforeCommandInMb":99.727} {"correlation_id":"b561c5d5-e6e3-4091-9517-b0458190a360","trace_id":"bf9679fb-b785-40cb-b814-04a1dc385a1c"}
[2026-04-27 10:27:11] local.INFO: [EmailSchedule] STARTING batch create {"host":"docker_lamp_1"} {"correlation_id":"b561c5d5-e6e3-4091-9517-b0458190a360","trace_id":"bf9679fb-b785-40cb-b814-04a1dc385a1c"}
[2026-04-27 10:27:11] local.INFO: [EmailSchedule] FINISHED batch create {"host":"docker_lamp_1"} {"correlation_id":"b561c5d5-e6e3-4091-9517-b0458190a360","trace_id":"bf9679fb-b785-40cb-b814-04a1dc385a1c"}
[2026-04-27 10:27:11] local.INFO: Jiminny\Console\Commands\Command::run Memory usage for command {"command":"mailbox:batch:create","memoryBeforeCommandInMb":60.0,"memoryAfterCommandInMB":62.0,"memoryPeakBeforeCommandInMb":99.727,"memoryPeakAfterCommandInMB":99.727} {"correlation_id":"b561c5d5-e6e3-4091-9517-b0458190a360","trace_id":"bf9679fb-b785-40cb-b814-04a1dc385a1c"}
[2026-04-27 10:27:14] local.INFO: [Jiminny\Jobs\Mailbox\CreateBatches] processed 2 inboxes and created 1 batches {"userId":null,"batchSize":30,"maxBatches":1000} {"correlation_id":"98416296-43c2-4834-927e-4a0e86dc99cf","trace_id":"bf9679fb-b785-40cb-b814-04a1dc385a1c"}
[2026-04-27 10:28:04] local.INFO: Jiminny\Console\Commands\Command::run Memory usage before starting command {"command":"meeting-bot:schedule-bot","memoryBeforeCommandInMb":60.0,"memoryPeakBeforeCommandInMb":99.727} {"correlation_id":"5852bcf1-af92-4d24-8a2d-08a9d4a5cfac","trace_id":"25af5e8c-a148-493e-ac9f-4d53f1960177"}
[2026-04-27 10:28:04] local.INFO: [ScheduleBotCommand] Number of activities to be captured: 0 {"correlation_id":"5852bcf1-af92-4d24-8a2d-08a9d4a5cfac","trace_id":"25af5e8c-a148-493e-ac9f-4d53f1960177"}
[2026-04-27 10:28:04] local.INFO: Jiminny\Console\Commands\Command::run Memory usage for command {"command":"meeting-bot:schedule-bot","memoryBeforeCommandInMb":60.0,"memoryAfterCommandInMB":62.0,"memoryPeakBeforeCommandInMb":99.727,"memoryPeakAfterCommandInMB":99.727} {"correlation_id":"5852bcf1-af92-4d24-8a2d-08a9d4a5cfac","trace_id":"25af5e8c-a148-493e-ac9f-4d53f1960177"}
[2026-04-27 10:28:06] local.INFO: Jiminny\Console\Commands\Command::run Memory usage before starting command {"command":"dialers:monitor-activities","memoryBeforeCommandInMb":60.0,"memoryPeakBeforeCommandInMb":99.727} {"correlation_id":"cc1cff1f-71b4-4bb1-800f-66e27c0f2b3a","trace_id":"c03103a3-9ab8-480b-9753-7c7c6488f246"}
[2026-04-27 10:28:06] local.INFO: Jiminny\Console\Commands\Command::run Memory usage for command {"command":"dialers:monitor-activities","memoryBeforeCommandInMb":60.0,"memoryAfterCommandInMB":62.0,"memoryPeakBeforeCommandInMb":99.727,"memoryPeakAfterCommandInMB":99.727} {"correlation_id":"cc1cff1f-71b4-4bb1-800f-66e27c0f2b3a","trace_id":"c03103a3-9ab8-480b-9753-7c7c6488f246"}
[2026-04-27 10:28:09] local.ERROR: Target class [Jiminny\Repositories\AjReportsRepository] does not exist. {"exception":"[object] (Illuminate\\Contracts\\Container\\BindingResolutionException(code: 0): Target class [Jiminny\\Repositories\\AjReportsRepository] does not exist. at /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php:1124)
[stacktrace]
#0 /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php(933): Illuminate\\Container\\Container->build('Jiminny\\\\Reposit...')
#1 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Application.php(1078): Illuminate\\Container\\Container->resolve('Jiminny\\\\Reposit...', Array, true)
#2 /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php(864): Illuminate\\Foundation\\Application->resolve('Jiminny\\\\Reposit...', Array)
#3 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Application.php(1058): Illuminate\\Container\\Container->make('Jiminny\\\\Reposit...', Array)
#4 /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php(1336): Illuminate\\Foundation\\Application->make('Jiminny\\\\Reposit...')
#5 /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php(1237): Illuminate\\Container\\Container->resolveClass(Object(ReflectionParameter))
#6 /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php(1162): Illuminate\\Container\\Container->resolveDependencies(Array)
#7 /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php(933): Illuminate\\Container\\Container->build('Jiminny\\\\Http\\\\Co...')
#8 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Application.php(1078): Illuminate\\Container\\Container->resolve('Jiminny\\\\Http\\\\Co...', Array, true)
#9 /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php(864): Illuminate\\Foundation\\Application->resolve('Jiminny\\\\Http\\\\Co...', Array)
#10 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Application.php(1058): Illuminate\\Container\\Container->make('Jiminny\\\\Http\\\\Co...', Array)
#11 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Route.php(286): Illuminate\\Foundation\\Application->make('Jiminny\\\\Http\\\\Co...')
#12 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Route.php(1133): Illuminate\\Routing\\Route->getController()
#13 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Route.php(1062): Illuminate\\Routing\\Route->controllerMiddleware()
#14 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Router.php(834): Illuminate\\Routing\\Route->gatherMiddleware()
#15 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Router.php(816): Illuminate\\Routing\\Router->gatherRouteMiddleware(Object(Illuminate\\Routing\\Route))
#16 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Router.php(800): Illuminate\\Routing\\Router->runRouteWithinStack(Object(Illuminate\\Routing\\Route), Object(Illuminate\\Http\\Request))
#17 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Router.php(764): Illuminate\\Routing\\Router->runRoute(Object(Illuminate\\Http\\Request), Object(Illuminate\\Routing\\Route))
#18 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Router.php(753): Illuminate\\Routing\\Router->dispatchToRoute(Object(Illuminate\\Http\\Request))
#19 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Http/Kernel.php(200): Illuminate\\Routing\\Router->dispatch(Object(Illuminate\\Http\\Request))
#20 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(180): Illuminate\\Foundation\\Http\\Kernel->Illuminate\\Foundation\\Http\\{closure}(Object(Illuminate\\Http\\Request))
#21 /home/jiminny/vendor/barryvdh/laravel-debugbar/src/Middleware/InjectDebugbar.php(59): Illuminate\\Pipeline\\Pipeline->Illuminate\\Pipeline\\{closure}(Object(Illuminate\\Http\\Request))
#22 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(219): Barryvdh\\Debugbar\\Middleware\\InjectDebugbar->handle(Object(Illuminate\\Http\\Request), Object(Closure))
#23 /home/jiminny/vendor/laravel/framework/src/Illuminate/Http/Middleware/HandleCors.php(74): Illuminate\\Pipeline\\Pipeline->Illuminate\\Pipeline\\{closure}(Object(Illuminate\\Http\\Request))
#24 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(219): Illuminate\\Http\\Middleware\\HandleCors->handle(Object(Illuminate\\Http\\Request), Object(Closure))
#25 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Http/Middleware/TransformsRequest.php(21): Illuminate\\Pipeline\\Pipeline->Illuminate\\Pipeline\\{closure}(Object(Illuminate\\Http\\Request))
#26 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Http/Middleware/TrimStrings.php(51): Illuminate\\Foundation\\Http\\Middleware\\TransformsRequest->handle(Object(Illuminate\\Http\\Request), Object(Closure))
#27 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(219): Illuminate\\Foundation\\Http\\Middleware\\TrimStrings->handle(Object(Illuminate\\Http\\Request), Object(Closure))
#28 /home/jiminny/vendor/bepsvpt/secure-headers/src/SecureHeadersMiddleware.php(18): Illuminate\\Pipeline\\Pipeline->Illuminate\\Pipeline\\{closure}(Object(Illuminate\\Http\\Request))
#29 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(219): Bepsvpt\\SecureHeaders\\SecureHeadersMiddleware->handle(Object(Illuminate\\Http\\Request), Object(Closure))
#30 /home/jiminny/app/Http/Middleware/SentryContext.php(60): Illuminate\\Pipeline\\Pipeline->Illuminate\\Pipeline\\{closure}(Object(Illuminate\\Http\\Request))
#31 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(219): Jiminny\\Http\\Middleware\\SentryContext->handle(Object(Illuminate\\Http\\Request), Object(Closure))
#32 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Http/Middleware/PreventRequestsDuringMaintenance.php(109): Illuminate\\Pipeline\\Pipeline->Illuminate\\Pipeline\\{closure}(Object(Illuminate\\Http\\Request))
#33 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(219): Illuminate\\Foundation\\Http\\Middleware\\PreventRequestsDuringMaintenance->handle(Object(Illuminate\\Http\\Request), Object(Closure))
#34 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Http/Middleware/InvokeDeferredCallbacks.php(22): Illuminate\\Pipeline\\Pipeline->Illuminate\\Pipeline\\{closure}(Object(Illuminate\\Http\\Request))
#35 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(219): Illuminate\\Foundation\\Http\\Middleware\\InvokeDeferredCallbacks->handle(Object(Illuminate\\Http\\Request), Object(Closure))
#36 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(137): Illuminate\\Pipeline\\Pipeline->Illuminate\\Pipeline\\{closure}(Object(Illuminate\\Http\\Request))
#37 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Http/Kernel.php(175): Illuminate\\Pipeline\\Pipeline->then(Object(Closure))
#38 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Http/Kernel.php(144): Illuminate\\Foundation\\Http\\Kernel->sendRequestThroughRouter(Object(Illuminate\\Http\\Request))
#39 /home/jiminny/public/index.php(51): Illuminate\\Foundation\\Http\\Kernel->handle(Object(Illuminate\\Http\\Request))
#40 {main}
[previous exception] [object] (ReflectionException(code: -1): Class \"Jiminny\\Repositories\\AjReportsRepository\" does not exist at /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php:1122)
[stacktrace]
#0 /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php(1122): ReflectionClass->__construct('Jiminny\\\\Reposit...')
#1 /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php(933): Illuminate\\Container\\Container->build('Jiminny\\\\Reposit...')
#2 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Application.php(1078): Illuminate\\Container\\Container->resolve('Jiminny\\\\Reposit...', Array, true)
#3 /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php(864): Illuminate\\Foundation\\Application->resolve('Jiminny\\\\Reposit...', Array)
#4 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Application.php(1058): Illuminate\\Container\\Container->make('Jiminny\\\\Reposit...', Array)
#5 /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php(1336): Illuminate\\Foundation\\Application->make('Jiminny\\\\Reposit...')
#6 /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php(1237): Illuminate\\Container\\Container->resolveClass(Object(ReflectionParameter))
#7 /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php(1162): Illuminate\\Container\\Container->resolveDependencies(Array)
#8 /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php(933): Illuminate\\Container\\Container->build('Jiminny\\\\Http\\\\Co...')
#9 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Application.php(1078): Illuminate\\Container\\Container->resolve('Jiminny\\\\Http\\\\Co...', Array, true)
#10 /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php(864): Illuminate\\Foundation\\Application->resolve('Jiminny\\\\Http\\\\Co...', Array)
#11 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Application.php(1058): Illuminate\\Container\\Container->make('Jiminny\\\\Http\\\\Co...', Array)
#12 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Route.php(286): Illuminate\\Foundation\\Application->make('Jiminny\\\\Http\\\\Co...')
#13 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Route.php(1133): Illuminate\\Routing\\Route->getController()
#14 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Route.php(1062): Illuminate\\Routing\\Route->controllerMiddleware()
#15 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Router.php(834): Illuminate\\Routing\\Route->gatherMiddleware()
#16 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Router.php(816): Illuminate\\Routing\\Router->gatherRouteMiddleware(Object(Illuminate\\Routing\\Route))
#17 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Router.php(800): Illuminate\\Routing\\Router->runRouteWithinStack(Object(Illuminate\\Routing\\Route), Object(Illuminate\\Http\\Request))
#18 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Router.php(764): Illuminate\\Routing\\Router->runRoute(Object(Illuminate\\Http\\Request), Object(Illuminate\\Routing\\Route))
#19 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Router.php(753): Illuminate\\Routing\\Router->dispatchToRoute(Object(Illuminate\\Http\\Request))
#20 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Http/Kernel.php(200): Illuminate\\Routing\\Router->dispatch(Object(Illuminate\\Http\\Request))
#21 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(180): Illuminate\\Foundation\\Http\\Kernel->Illuminate\\Foundation\\Http\\{closure}(Object(Illuminate\\Http\\Request))
#22 /home/jiminny/vendor/barryvdh/laravel-debugbar/src/Middleware/InjectDebugbar.php(59): Illuminate\\Pipeline\\Pipeline->Illuminate\\Pipeline\\{closure}(Object(Illuminate\\Http\\Request))
#23 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(219): Barryvdh\\Debugbar\\Middleware\\InjectDebugbar->handle(Object(Illuminate\\Http\\Request), Object(Closure))
#24 /home/jiminny/vendor/laravel/framework/src/Illuminate/Http/Middleware/HandleCors.php(74): Illuminate\\Pipeline\\Pipeline->Illuminate\\Pipeline\\{closure}(Object(Illuminate\\Http\\Request))
#25 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(219): Illuminate\\Http\\Middleware\\HandleCors->handle(Object(Illuminate\\Http\\Request), Object(Closure))
#26 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Http/Middleware/TransformsRequest.php(21): Illuminate\\Pipeline\\Pipeline->Illuminate\\Pipeline\\{closure}(Object(Illuminate\\Http\\Request))
#27 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Http/Middleware/TrimStrings.php(51): Illuminate\\Foundation\\Http\\Middleware\\TransformsRequest->handle(Object(Illuminate\\Http\\Request), Object(Closure))
#28 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(219): Illuminate\\Foundation\\Http\\Middleware\\TrimStrings->handle(Object(Illuminate\\Http\\Request), Object(Closure))
#29 /home/jiminny/vendor/bepsvpt/secure-headers/src/SecureHeadersMiddleware.php(18): Illuminate\\Pipeline\\Pipeline->Illuminate\\Pipeline\\{closure}(Object(Illuminate\\Http\\Request))
#30 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(219): Bepsvpt\\SecureHeaders\\SecureHeadersMiddleware->handle(Object(Illuminate\\Http\\Request), Object(Closure))
#31 /home/jiminny/app/Http/Middleware/SentryContext.php(60): Illuminate\\Pipeline\\Pipeline->Illuminate\\Pipeline\\{closure}(Object(Illuminate\\Http\\Request))
#32 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(219): Jiminny\\Http\\Middleware\\SentryContext->handle(Object(Illuminate\\Http\\Request), Object(Closure))
#33 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Http/Middleware/PreventRequestsDuringMaintenance.php(109): Illuminate\\Pipeline\\Pipeline->Illuminate\\Pipeline\\{closure}(Object(Illuminate\\Http\\Request))
#34 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(219): Illuminate\\Foundation\\Http\\Middleware\\PreventRequestsDuringMaintenance->handle(Object(Illuminate\\Http\\Request), Object(Closure))
#35 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Http/Middleware/InvokeDeferredCallbacks.php(22): Illuminate\\Pipeline\\Pipeline->Illuminate\\Pipeline\\{closure}(Object(Illuminate\\Http\\Request))
#36 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(219): Illuminate\\Foundation\\Http\\Middleware\\InvokeDeferredCallbacks->handle(Object(Illuminate\\Http\\Request), Object(Closure))
#37 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(137): Illuminate\\Pipeline\\Pipeline->Illuminate\\Pipeline\\{closure}(Object(Illuminate\\Http\\Request))
#38 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Http/Kernel.php(175): Illuminate\\Pipeline\\Pipeline->then(Object(Closure))
#39 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Http/Kernel.php(144): Illuminate\\Foundation\\Http\\Kernel->sendRequestThroughRouter(Object(Illuminate\\Http\\Request))
#40 /home/jiminny/public/index.php(51): Illuminate\\Foundation\\Http\\Kernel->handle(Object(Illuminate\\Http\\Request))
#41 {main}
"} {"correlation_id":"dfad8817-1dde-4622-8320-f5fc1616a8fe","trace_id":"d21232b2-60fb-4ca0-838c-ba42e61593f9"}
[2026-04-27 10:28:09] local.NOTICE: Monitoring start {"correlation_id":"93744c67-4444-41ff-aaad-6aa9cafe294e","trace_id":"80e396b0-b1ac-4ec8-838c-00021f98072c"}
[2026-04-27 10:28:09] local.NOTICE: Monitoring end {"correlation_id":"93744c67-4444-41ff-aaad-6aa9cafe294e","trace_id":"80e396b0-b1ac-4ec8-838c-00021f98072c"}
[2026-04-27 10:28:10] local.ERROR: Target class [Jiminny\Repositories\AjReportsRepository] does not exist. {"exception":"[object] (Illuminate\\Contracts\\Container\\BindingResolutionException(code: 0): Target class [Jiminny\\Repositories\\AjReportsRepository] does not exist. at /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php:1124)
[stacktrace]
#0 /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php(933): Illuminate\\Container\\Container->build('Jiminny\\\\Reposit...')
#1 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Application.php(1078): Illuminate\\Container\\Container->resolve('Jiminny\\\\Reposit...', Array, true)
#2 /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php(864): Illuminate\\Foundation\\Application->resolve('Jiminny\\\\Reposit...', Array)
#3 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Application.php(1058): Illuminate\\Container\\Container->make('Jiminny\\\\Reposit...', Array)
#4 /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php(1336): Illuminate\\Foundation\\Application->make('Jiminny\\\\Reposit...')
#5 /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php(1237): Illuminate\\Container\\Container->resolveClass(Object(ReflectionParameter))
#6 /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php(1162): Illuminate\\Container\\Container->resolveDependencies(Array)
#7 /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php(933): Illuminate\\Container\\Container->build('Jiminny\\\\Http\\\\Co...')
#8 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Application.php(1078): Illuminate\\Container\\Container->resolve('Jiminny\\\\Http\\\\Co...', Array, true)
#9 /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php(864): Illuminate\\Foundation\\Application->resolve('Jiminny\\\\Http\\\\Co...', Array)
#10 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Application.php(1058): Illuminate\\Container\\Container->make('Jiminny\\\\Http\\\\Co...', Array)
#11 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Route.php(286): Illuminate\\Foundation\\Application->make('Jiminny\\\\Http\\\\Co...')
#12 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Route.php(1133): Illuminate\\Routing\\Route->getController()
#13 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Route.php(1062): Illuminate\\Routing\\Route->controllerMiddleware()
#14 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Router.php(834): Illuminate\\Routing\\Route->gatherMiddleware()
#15 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Router.php(816): Illuminate\\Routing\\Router->gatherRouteMiddleware(Object(Illuminate\\Routing\\Route))
#16 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Router.php(800): Illuminate\\Routing\\Router->runRouteWithinStack(Object(Illuminate\\Routing\\Route), Object(Illuminate\\Http\\Request))
#17 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Router.php(764): Illuminate\\Routing\\Router->runRoute(Object(Illuminate\\Http\\Request), Object(Illuminate\\Routing\\Route))
#18 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Router.php(753): Illuminate\\Routing\\Router->dispatchToRoute(Object(Illuminate\\Http\\Request))
#19 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Http/Kernel.php(200): Illuminate\\Routing\\Router->dispatch(Object(Illuminate\\Http\\Request))
#20 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(180): Illuminate\\Foundation\\Http\\Kernel->Illuminate\\Foundation\\Http\\{closure}(Object(Illuminate\\Http\\Request))
#21 /home/jiminny/vendor/barryvdh/laravel-debugbar/src/Middleware/InjectDebugbar.php(59): Illuminate\\Pipeline\\Pipeline->Illuminate\\Pipeline\\{closure}(Object(Illuminate\\Http\\Request))
#22 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(219): Barryvdh\\Debugbar\\Middleware\\InjectDebugbar->handle(Object(Illuminate\\Http\\Request), Object(Closure))
#23 /home/jiminny/vendor/laravel/framework/src/Illuminate/Http/Middleware/HandleCors.php(74): Illuminate\\Pipeline\\Pipeline->Illuminate\\Pipeline\\{closure}(Object(Illuminate\\Http\\Request))
#24 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(219): Illuminate\\Http\\Middleware\\HandleCors->handle(Object(Illuminate\\Http\\Request), Object(Closure))
#25 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Http/Middleware/TransformsRequest.php(21): Illuminate\\Pipeline\\Pipeline->Illuminate\\Pipeline\\{closure}(Object(Illuminate\\Http\\Request))
#26 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Http/Middleware/TrimStrings.php(51): Illuminate\\Foundation\\Http\\Middleware\\TransformsRequest->handle(Object(Illuminate\\Http\\Request), Object(Closure))
#27 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(219): Illuminate\\Foundation\\Http\\Middleware\\TrimStrings->handle(Object(Illuminate\\Http\\Request), Object(Closure))
#28 /home/jiminny/vendor/bepsvpt/secure-headers/src/SecureHeadersMiddleware.php(18): Illuminate\\Pipeline\\Pipeline->Illuminate\\Pipeline\\{closure}(Object(Illuminate\\Http\\Request))
#29 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(219): Bepsvpt\\SecureHeaders\\SecureHeadersMiddleware->handle(Object(Illuminate\\Http\\Request), Object(Closure))
#30 /home/jiminny/app/Http/Middleware/SentryContext.php(60): Illuminate\\Pipeline\\Pipeline->Illuminate\\Pipeline\\{closure}(Object(Illuminate\\Http\\Request))
#31 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(219): Jiminny\\Http\\Middleware\\SentryContext->handle(Object(Illuminate\\Http\\Request), Object(Closure))
#32 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Http/Middleware/PreventRequestsDuringMaintenance.php(109): Illuminate\\Pipeline\\Pipeline->Illuminate\\Pipeline\\{closure}(Object(Illuminate\\Http\\Request))
#33 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(219): Illuminate\\Foundation\\Http\\Middleware\\PreventRequestsDuringMaintenance->handle(Object(Illuminate\\Http\\Request), Object(Closure))
#34 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Http/Middleware/InvokeDeferredCallbacks.php(22): Illuminate\\Pipeline\\Pipeline->Illuminate\\Pipeline\\{closure}(Object(Illuminate\\Http\\Request))
#35 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(219): Illuminate\\Foundation\\Http\\Middleware\\InvokeDeferredCallbacks->handle(Object(Illuminate\\Http\\Request), Object(Closure))
#36 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(137): Illuminate\\Pipeline\\Pipeline->Illuminate\\Pipeline\\{closure}(Object(Illuminate\\Http\\Request))
#37 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Http/Kernel.php(175): Illuminate\\Pipeline\\Pipeline->then(Object(Closure))
#38 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Http/Kernel.php(144): Illuminate\\Foundation\\Http\\Kernel->sendRequestThroughRouter(Object(Illuminate\\Http\\Request))
#39 /home/jiminny/public/index.php(51): Illuminate\\Foundation\\Http\\Kernel->handle(Object(Illuminate\\Http\\Request))
#40 {main}
[previous exception] [object] (ReflectionException(code: -1): Class \"Jiminny\\Repositories\\AjReportsRepository\" does not exist at /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php:1122)
[stacktrace]
#0 /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php(1122): ReflectionClass->__construct('Jiminny\\\\Reposit...')
#1 /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php(933): Illuminate\\Container\\Container->build('Jiminny\\\\Reposit...')
#2 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Application.php(1078): Illuminate\\Container\\Container->resolve('Jiminny\\\\Reposit...', Array, true)
#3 /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php(864): Illuminate\\Foundation\\Application->resolve('Jiminny\\\\Reposit...', Array)
#4 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Application.php(1058): Illuminate\\Container\\Container->make('Jiminny\\\\Reposit...', Array)
#5 /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php(1336): Illuminate\\Foundation\\Application->make('Jiminny\\\\Reposit...')
#6 /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php(1237): Illuminate\\Container\\Container->resolveClass(Object(ReflectionParameter))
#7 /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php(1162): Illuminate\\Container\\Container->resolveDependencies(Array)
#8 /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php(933): Illuminate\\Container\\Container->build('Jiminny\\\\Http\\\\Co...')
#9 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Application.php(1078): Illuminate\\Container\\Container->resolve('Jiminny\\\\Http\\\\Co...', Array, true)
#10 /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php(864): Illuminate\\Foundation\\Application->resolve('Jiminny\\\\Http\\\\Co...', Array)
#11 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Application.php(1058): Illuminate\\Container\\Container->make('Jiminny\\\\Http\\\\Co...', Array)
#12 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Route.php(286): Illuminate\\Foundation\\Application->make('Jiminny\\\\Http\\\\Co...')
#13 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Route.php(1133): Illuminate\\Routing\\Route->getController()
#14 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Route.php(1062): Illuminate\\Routing\\Route->controllerMiddleware()
#15 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Router.php(834): Illuminate\\Routing\\Route->gatherMiddleware()
#16 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Router.php(816): Illuminate\\Routing\\Router->gatherRouteMiddleware(Object(Illuminate\\Routing\\Route))
#17 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Router.php(800): Illuminate\\Routing\\Router->runRouteWithinStack(Object(Illuminate\\Routing\\Route), Object(Illuminate\\Http\\Request))
#18 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Router.php(764): Illuminate\\Routing\\Router->runRoute(Object(Illuminate\\Http\\Request), Object(Illuminate\\Routing\\Route))
#19 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Router.php(753): Illuminate\\Routing\\Router->dispatchToRoute(Object(Illuminate\\Http\\Request))
#20 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Http/Kernel.php(200): Illuminate\\Routing\\Router->dispatch(Object(Illuminate\\Http\\Request))
#21 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(180): Illuminate\\Foundation\\Http\\Kernel->Illuminate\\Foundation\\Http\\{closure}(Object(Illuminate\\Http\\Request))
#22 /home/jiminny/vendor/barryvdh/laravel-debugbar/src/Middleware/InjectDebugbar.php(59): Illuminate\\Pipeline\\Pipeline->Illuminate\\Pipeline\\{closure}(Object(Illuminate\\Http\\Request))
#23 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(219): Barryvdh\\Debugbar\\Middleware\\InjectDebugbar->handle(Object(Illuminate\\Http\\Request), Object(Closure))
#24 /home/jiminny/vendor/laravel/framework/src/Illuminate/Http/Middleware/HandleCors.php(74): Illuminate\\Pipeline\\Pipeline->Illuminate\\Pipeline\\{closure}(Object(Illuminate\\Http\\Request))
#25 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(219): Illuminate\\Http\\Middleware\\HandleCors->handle(Object(Illuminate\\Http\\Request), Object(Closure))
#26 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Http/Middleware/TransformsRequest.php(21): Illuminate\\Pipeline\\Pipeline->Illuminate\\Pipeline\\{closure}(Object(Illuminate\\Http\\Request))
#27 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Http/Middleware/TrimStrings.php(51): Illuminate\\Foundation\\Http\\Middleware\\TransformsRequest->handle(Object(Illuminate\\Http\\Request), Object(Closure))
#28 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(219): Illuminate\\Foundation\\Http\\Middleware\\TrimStrings->handle(Object(Illuminate\\Http\\Request), Object(Closure))
#29 /home/jiminny/vendor/bepsvpt/secure-headers/src/SecureHeadersMiddleware.php(18): Illuminate\\Pipeline\\Pipeline->Illuminate\\Pipeline\\{closure}(Object(Illuminate\\Http\\Request))
#30 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(219): Bepsvpt\\SecureHeaders\\SecureHeadersMiddleware->handle(Object(Illuminate\\Http\\Request), Object(Closure))
#31 /home/jiminny/app/Http/Middleware/SentryContext.php(60): Illuminate\\Pipeline\\Pipeline->Illuminate\\Pipeline\\{closure}(Object(Illuminate\\Http\\Request))
#32 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(219): Jiminny\\Http\\Middleware\\SentryContext->handle(Object(Illuminate\\Http\\Request), Object(Closure))
#33 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Http/Middleware/PreventRequestsDuringMaintenance.php(109): Illuminate\\Pipeline\\Pipeline->Illuminate\\Pipeline\\{closure}(Object(Illuminate\\Http\\Request))
#34 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(219): Illuminate\\Foundation\\Http\\Middleware\\PreventRequestsDuringMaintenance->handle(Object(Illuminate\\Http\\Request), Object(Closure))
#35 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Http/Middleware/InvokeDeferredCallbacks.php(22): Illuminate\\Pipeline\\Pipeline->Illuminate\\Pipeline\\{closure}(Object(Illuminate\\Http\\Request))
#36 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(219): Illuminate\\Foundation\\Http\\Middleware\\InvokeDeferredCallbacks->handle(Object(Illuminate\\Http\\Request), Object(Closure))
#37 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(137): Illuminate\\Pipeline\\Pipeline->Illuminate\\Pipeline\\{closure}(Object(Illuminate\\Http\\Request))
#38 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Http/Kernel.php(175): Illuminate\\Pipeline\\Pipeline->then(Object(Closure))
#39 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Http/Kernel.php(144): Illuminate\\Foundation\\Http\\Kernel->sendRequestThroughRouter(Object(Illuminate\\Http\\Request))
#40 /home/jiminny/public/index.php(51): Illuminate\\Foundation\\Http\\Kernel->handle(Object(Illuminate\\Http\\Request))
#41 {main}
"} {"correlation_id":"de768781-5c05-418c-baf3-79f77a8c8694","trace_id":"fec74062-3f6d-4cfc-968a-1bb285496db3"}
[2026-04-27 10:28:12] local.INFO: Jiminny\Console\Commands\Command::run Memory usage before starting command {"command":"mailbox:skip-lists:refresh","memoryBeforeCommandInMb":60.0,"memoryPeakBeforeCommandInMb":99.727} {"correlation_id":"907d15ed-8c84-41a1-a473-590eebb55dbc","trace_id":"ec7bd353-516e-491d-89dd-de44136c1672"}
[2026-04-27 10:28:12] local.INFO: Jiminny\Console\Commands\Command::run Memory usage for command {"command":"mailbox:skip-lists:refresh","memoryBeforeCommandInMb":60.0,"memoryAfterCommandInMB":62.0,"memoryPeakBeforeCommandInMb":99.727,"memoryPeakAfterCommandInMB":99.727} {"correlation_id":"907d15ed-8c84-41a1-a473-590eebb55dbc","trace_id":"ec7bd353-516e-491d-89dd-de44136c1672"}
[2026-04-27 10:28:13] local.ERROR: Target class [Jiminny\Repositories\AjReportsRepository] does not exist. {"exception":"[object] (Illuminate\\Contracts\\Container\\BindingResolutionException(code: 0): Target class [Jiminny\\Repositories\\AjReportsRepository] does not exist. at /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php:1124)
[stacktrace]
#0 /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php(933): Illuminate\\Container\\Container->build('Jiminny\\\\Reposit...')
#1 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Application.php(1078): Illuminate\\Container\\Container->resolve('Jiminny\\\\Reposit...', Array, true)
#2 /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php(864): Illuminate\\Foundation\\Application->resolve('Jiminny\\\\Reposit...', Array)
#3 /home/jiminny/vendor/laravel/framewo...
|
[{"role":"AXButton","text" [{"role":"AXButton","text":"Project: faVsco.js, menu","depth":5,"bounds":{"left":0.025930852,"top":0.019952115,"width":0.03856383,"height":0.025538707},"on_screen":true,"help_text":"~/jiminny/app","role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"master, menu","depth":5,"bounds":{"left":0.064494684,"top":0.019952115,"width":0.034242023,"height":0.025538707},"on_screen":true,"help_text":"Git Branch: master","role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Start Listening for PHP Debug Connections","depth":5,"bounds":{"left":0.8081782,"top":0.019952115,"width":0.011303191,"height":0.025538707},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"AskJiminnyReportActivityServiceTest","depth":6,"bounds":{"left":0.8234708,"top":0.019952115,"width":0.09208777,"height":0.025538707},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Run 'AskJiminnyReportActivityServiceTest'","depth":6,"bounds":{"left":0.9155585,"top":0.019952115,"width":0.011303191,"height":0.025538707},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Debug 'AskJiminnyReportActivityServiceTest'","depth":6,"bounds":{"left":0.9268617,"top":0.019952115,"width":0.011303191,"height":0.025538707},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"More Actions","depth":6,"bounds":{"left":0.9381649,"top":0.019952115,"width":0.011303191,"height":0.025538707},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"JetBrains AI","depth":5,"bounds":{"left":0.96609044,"top":0.019952115,"width":0.011303191,"height":0.025538707},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Search Everywhere","depth":5,"bounds":{"left":0.9773936,"top":0.019952115,"width":0.011303191,"height":0.025538707},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"IDE and Project Settings","depth":5,"bounds":{"left":0.9886968,"top":0.019952115,"width":0.011303186,"height":0.025538707},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXTextArea","text":"[2026-04-27 10:26:25] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage before starting command {\"command\":\"mailbox:sync\",\"memoryBeforeCommandInMb\":60.0,\"memoryPeakBeforeCommandInMb\":99.727} {\"correlation_id\":\"7b1ea2fb-8f36-4897-9970-91b4abba2cc7\",\"trace_id\":\"265be7c0-bf8f-4037-a0d2-57df3ef1d7e1\"}\n[2026-04-27 10:26:25] local.INFO: [EmailSchedule] STARTING Inbox Sync {\"host\":\"docker_lamp_1\"} {\"correlation_id\":\"7b1ea2fb-8f36-4897-9970-91b4abba2cc7\",\"trace_id\":\"265be7c0-bf8f-4037-a0d2-57df3ef1d7e1\"}\n[2026-04-27 10:26:25] local.INFO: [EmailSchedule] FINISHED Inbox Sync {\"host\":\"docker_lamp_1\",\"events\":2} {\"correlation_id\":\"7b1ea2fb-8f36-4897-9970-91b4abba2cc7\",\"trace_id\":\"265be7c0-bf8f-4037-a0d2-57df3ef1d7e1\"}\n[2026-04-27 10:26:25] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage for command {\"command\":\"mailbox:sync\",\"memoryBeforeCommandInMb\":60.0,\"memoryAfterCommandInMB\":62.0,\"memoryPeakBeforeCommandInMb\":99.727,\"memoryPeakAfterCommandInMB\":99.727} {\"correlation_id\":\"7b1ea2fb-8f36-4897-9970-91b4abba2cc7\",\"trace_id\":\"265be7c0-bf8f-4037-a0d2-57df3ef1d7e1\"}\n[2026-04-27 10:26:26] local.INFO: [automated-reports] Started {\"correlation_id\":\"d3f79034-397b-4936-9305-bdd99b23f2dd\",\"trace_id\":\"aa0f8702-73eb-4947-a765-2e89244599c3\"}\n[2026-04-27 10:26:26] local.INFO: [automated-reports] Checking conditions {\"isMonday\":true,\"isWeekend\":false,\"isFirstDayOfMonth\":false,\"currentMonth\":4,\"isQuarterlyMonth\":true} {\"correlation_id\":\"d3f79034-397b-4936-9305-bdd99b23f2dd\",\"trace_id\":\"aa0f8702-73eb-4947-a765-2e89244599c3\"}\n[2026-04-27 10:26:26] local.INFO: [automated-reports] Processing daily reports {\"correlation_id\":\"d3f79034-397b-4936-9305-bdd99b23f2dd\",\"trace_id\":\"aa0f8702-73eb-4947-a765-2e89244599c3\"}\n[2026-04-27 10:26:26] local.INFO: [automated-reports] Found 1 daily reports to process {\"correlation_id\":\"d3f79034-397b-4936-9305-bdd99b23f2dd\",\"trace_id\":\"aa0f8702-73eb-4947-a765-2e89244599c3\"}\n[2026-04-27 10:26:26] local.INFO: [automated-reports] Dispatching Generate Report job for report {\"reportUuid\":\"4f6ca2b5-1993-48aa-99ad-b66f19f15d43\",\"teamId\":1,\"frequency\":\"weekly\",\"type\":\"ask_jiminny\"} {\"correlation_id\":\"d3f79034-397b-4936-9305-bdd99b23f2dd\",\"trace_id\":\"aa0f8702-73eb-4947-a765-2e89244599c3\"}\n[2026-04-27 10:26:26] local.INFO: [automated-reports] Processing weekly reports {\"correlation_id\":\"d3f79034-397b-4936-9305-bdd99b23f2dd\",\"trace_id\":\"aa0f8702-73eb-4947-a765-2e89244599c3\"}\n[2026-04-27 10:26:26] local.INFO: [automated-reports] Found 1 weekly reports to process {\"correlation_id\":\"d3f79034-397b-4936-9305-bdd99b23f2dd\",\"trace_id\":\"aa0f8702-73eb-4947-a765-2e89244599c3\"}\n[2026-04-27 10:26:26] local.INFO: [automated-reports] Dispatching Generate Report job for report {\"reportUuid\":\"4f6ca2b5-1993-48aa-99ad-b66f19f15d43\",\"teamId\":1,\"frequency\":\"weekly\",\"type\":\"ask_jiminny\"} {\"correlation_id\":\"d3f79034-397b-4936-9305-bdd99b23f2dd\",\"trace_id\":\"aa0f8702-73eb-4947-a765-2e89244599c3\"}\n[2026-04-27 10:26:26] local.INFO: [automated-reports] Completed {\"correlation_id\":\"d3f79034-397b-4936-9305-bdd99b23f2dd\",\"trace_id\":\"aa0f8702-73eb-4947-a765-2e89244599c3\"}\n[2026-04-27 10:26:26] local.INFO: [HubSpot Journal Polling] Getting offset from database {\"offset\":\"\",\"jiminny_team_id\":1} {\"correlation_id\":\"b453f0f4-0d2c-491c-a935-14c60265ec18\",\"trace_id\":\"a50202d6-5601-46b4-a02e-cf5ac8d061fa\"}\n[2026-04-27 10:26:26] local.INFO: [HubSpot Journal API] Fetching latest journal entry {\"url\":\"https://api.hubapi.com/webhooks/v4/journal/latest\"} {\"correlation_id\":\"b453f0f4-0d2c-491c-a935-14c60265ec18\",\"trace_id\":\"a50202d6-5601-46b4-a02e-cf5ac8d061fa\"}\n[2026-04-27 10:26:26] local.INFO: [AskJiminnyReport:Generate] Started {\"automatedReportUuid\":\"4f6ca2b5-1993-48aa-99ad-b66f19f15d43\"} {\"correlation_id\":\"c6277ddd-09a6-4b04-8d42-bd395fc07d0a\",\"trace_id\":\"aa0f8702-73eb-4947-a765-2e89244599c3\"}\n[2026-04-27 10:26:26] local.INFO: [HubSpot Journal Polling] No data {\"correlation_id\":\"b453f0f4-0d2c-491c-a935-14c60265ec18\",\"trace_id\":\"a50202d6-5601-46b4-a02e-cf5ac8d061fa\"}\n[2026-04-27 10:26:26] local.WARNING: [HubSpot Journal Polling] Maximum empty results reached, stopping {\"empty_results\":5,\"max_empty_results\":5} {\"correlation_id\":\"b453f0f4-0d2c-491c-a935-14c60265ec18\",\"trace_id\":\"a50202d6-5601-46b4-a02e-cf5ac8d061fa\"}\n[2026-04-27 10:26:26] local.WARNING: [HubSpot Journal Polling] Maximum empty results reached, stopping {\"empty_results\":5,\"max_empty_results\":5} {\"correlation_id\":\"b453f0f4-0d2c-491c-a935-14c60265ec18\",\"trace_id\":\"a50202d6-5601-46b4-a02e-cf5ac8d061fa\"}\n[2026-04-27 10:26:26] local.INFO: [HubSpot Journal Polling] Service ending {\"runtime_seconds\":56,\"total_cycles\":5,\"files_downloaded\":0,\"empty_files\":0,\"other_portal_skipped\":0,\"total_events\":0,\"events_per_file\":0,\"avg_api_ms\":273.7,\"avg_download_ms\":0.0,\"avg_transform_ms\":0.0,\"avg_process_ms\":0.0,\"peak_memory_mb\":99.73} {\"correlation_id\":\"b453f0f4-0d2c-491c-a935-14c60265ec18\",\"trace_id\":\"a50202d6-5601-46b4-a02e-cf5ac8d061fa\"}\n[2026-04-27 10:26:26] local.INFO: [HubSpot Journal Polling] Released polling lock {\"correlation_id\":\"b453f0f4-0d2c-491c-a935-14c60265ec18\",\"trace_id\":\"a50202d6-5601-46b4-a02e-cf5ac8d061fa\"}\n[2026-04-27 10:26:27] local.INFO: [AskJiminnyReport] Fetched activity IDs for saved search {\"saved_search_id\":1977,\"user_id\":143,\"activity_count\":0} {\"correlation_id\":\"c6277ddd-09a6-4b04-8d42-bd395fc07d0a\",\"trace_id\":\"aa0f8702-73eb-4947-a765-2e89244599c3\"}\n[2026-04-27 10:26:27] local.INFO: [AskJiminnyReport:Generate] Fetched activity IDs {\"automatedReportUuid\":\"4f6ca2b5-1993-48aa-99ad-b66f19f15d43\",\"activityCount\":0} {\"correlation_id\":\"c6277ddd-09a6-4b04-8d42-bd395fc07d0a\",\"trace_id\":\"aa0f8702-73eb-4947-a765-2e89244599c3\"}\n[2026-04-27 10:26:27] local.INFO: [AskJiminnyReport:Generate] Not enough activities, skipped {\"automatedReportUuid\":\"4f6ca2b5-1993-48aa-99ad-b66f19f15d43\",\"activityCount\":0} {\"correlation_id\":\"c6277ddd-09a6-4b04-8d42-bd395fc07d0a\",\"trace_id\":\"aa0f8702-73eb-4947-a765-2e89244599c3\"}\n[2026-04-27 10:26:27] local.INFO: [AskJiminnyReport:Generate] Dispatched not-generated notifications {\"automatedReportUuid\":\"4f6ca2b5-1993-48aa-99ad-b66f19f15d43\",\"recipientsCount\":1} {\"correlation_id\":\"c6277ddd-09a6-4b04-8d42-bd395fc07d0a\",\"trace_id\":\"aa0f8702-73eb-4947-a765-2e89244599c3\"}\n[2026-04-27 10:26:27] local.INFO: [AskJiminnyReport:Generate] Started {\"automatedReportUuid\":\"4f6ca2b5-1993-48aa-99ad-b66f19f15d43\"} {\"correlation_id\":\"f1d3e4df-9591-487f-ac2a-a43ddb89a466\",\"trace_id\":\"aa0f8702-73eb-4947-a765-2e89244599c3\"}\n[2026-04-27 10:26:27] local.INFO: [AskJiminnyReport] Fetched activity IDs for saved search {\"saved_search_id\":1977,\"user_id\":143,\"activity_count\":0} {\"correlation_id\":\"f1d3e4df-9591-487f-ac2a-a43ddb89a466\",\"trace_id\":\"aa0f8702-73eb-4947-a765-2e89244599c3\"}\n[2026-04-27 10:26:27] local.INFO: [AskJiminnyReport:Generate] Fetched activity IDs {\"automatedReportUuid\":\"4f6ca2b5-1993-48aa-99ad-b66f19f15d43\",\"activityCount\":0} {\"correlation_id\":\"f1d3e4df-9591-487f-ac2a-a43ddb89a466\",\"trace_id\":\"aa0f8702-73eb-4947-a765-2e89244599c3\"}\n[2026-04-27 10:26:27] local.INFO: [AskJiminnyReport:Generate] Not enough activities, skipped {\"automatedReportUuid\":\"4f6ca2b5-1993-48aa-99ad-b66f19f15d43\",\"activityCount\":0} {\"correlation_id\":\"f1d3e4df-9591-487f-ac2a-a43ddb89a466\",\"trace_id\":\"aa0f8702-73eb-4947-a765-2e89244599c3\"}\n[2026-04-27 10:26:27] local.INFO: [AskJiminnyReport:Generate] Dispatched not-generated notifications {\"automatedReportUuid\":\"4f6ca2b5-1993-48aa-99ad-b66f19f15d43\",\"recipientsCount\":1} {\"correlation_id\":\"f1d3e4df-9591-487f-ac2a-a43ddb89a466\",\"trace_id\":\"aa0f8702-73eb-4947-a765-2e89244599c3\"}\n[2026-04-27 10:26:27] local.INFO: [Send Report Not Generated Mail] Email sent {\"uuid\":\"cd5b5081-22f8-4d1a-9d6f-f82aca1121f6\",\"email\":\"lukas.kovalik@jiminny.com\"} {\"correlation_id\":\"4255983f-07a4-4872-9ea6-5491fa32bdd5\",\"trace_id\":\"aa0f8702-73eb-4947-a765-2e89244599c3\"}\n[2026-04-27 10:26:28] local.INFO: [Sync Mailbox] Sync start {\"inbox_id\":59} {\"correlation_id\":\"280d5bf6-c34c-45ea-bea7-05d9c028e9d3\",\"trace_id\":\"265be7c0-bf8f-4037-a0d2-57df3ef1d7e1\"}\n[2026-04-27 10:26:28] local.INFO: [Inbox service] Skipping METADATA SYNC for inbox 59 due to unauthorized access to the mailbox {\"correlation_id\":\"280d5bf6-c34c-45ea-bea7-05d9c028e9d3\",\"trace_id\":\"265be7c0-bf8f-4037-a0d2-57df3ef1d7e1\"}\n[2026-04-27 10:26:28] local.INFO: [Sync Mailbox] Sync complete {\"inbox_id\":59} {\"correlation_id\":\"280d5bf6-c34c-45ea-bea7-05d9c028e9d3\",\"trace_id\":\"265be7c0-bf8f-4037-a0d2-57df3ef1d7e1\"}\n[2026-04-27 10:26:28] local.INFO: [Sync Mailbox] Sync start {\"inbox_id\":212} {\"correlation_id\":\"d3e3eb3d-5c83-4d6e-9592-aaa38c1843a4\",\"trace_id\":\"265be7c0-bf8f-4037-a0d2-57df3ef1d7e1\"}\n[2026-04-27 10:26:28] local.INFO: [SocialAccountService] Fetching token {\"socialAccountId\":1500,\"provider\":\"salesforce\"} {\"correlation_id\":\"d3e3eb3d-5c83-4d6e-9592-aaa38c1843a4\",\"trace_id\":\"265be7c0-bf8f-4037-a0d2-57df3ef1d7e1\"}\n[2026-04-27 10:26:28] local.INFO: [SocialAccountService] Token retrieved {\"socialAccountId\":1500,\"provider\":\"salesforce\"} {\"correlation_id\":\"d3e3eb3d-5c83-4d6e-9592-aaa38c1843a4\",\"trace_id\":\"265be7c0-bf8f-4037-a0d2-57df3ef1d7e1\"}\n[2026-04-27 10:26:28] local.INFO: [EncryptedTokenManager] Generating access token. {\"mode\":\"legacy\"} {\"correlation_id\":\"d3e3eb3d-5c83-4d6e-9592-aaa38c1843a4\",\"trace_id\":\"265be7c0-bf8f-4037-a0d2-57df3ef1d7e1\"}\n[2026-04-27 10:26:28] local.INFO: [CrmOwnerResolver] Integration owner matched as CRM Owner {\"crm_provider\":\"salesforce\",\"crm_owner\":143,\"team_id\":1} {\"correlation_id\":\"d3e3eb3d-5c83-4d6e-9592-aaa38c1843a4\",\"trace_id\":\"265be7c0-bf8f-4037-a0d2-57df3ef1d7e1\"}\n[2026-04-27 10:26:28] local.INFO: [SocialAccountService] Fetching token {\"socialAccountId\":1354,\"provider\":\"google\"} {\"correlation_id\":\"d3e3eb3d-5c83-4d6e-9592-aaa38c1843a4\",\"trace_id\":\"265be7c0-bf8f-4037-a0d2-57df3ef1d7e1\"}\n[2026-04-27 10:26:28] local.INFO: [SocialAccountService] Token retrieved {\"socialAccountId\":1354,\"provider\":\"google\"} {\"correlation_id\":\"d3e3eb3d-5c83-4d6e-9592-aaa38c1843a4\",\"trace_id\":\"265be7c0-bf8f-4037-a0d2-57df3ef1d7e1\"}\n[2026-04-27 10:26:28] local.INFO: [EncryptedTokenManager] Generating access token. {\"mode\":\"legacy\"} {\"correlation_id\":\"d3e3eb3d-5c83-4d6e-9592-aaa38c1843a4\",\"trace_id\":\"265be7c0-bf8f-4037-a0d2-57df3ef1d7e1\"}\n[2026-04-27 10:26:28] local.INFO: [Gmail] Performing incremental sync for inbox 212 using history ID: @1777284288 {\"correlation_id\":\"d3e3eb3d-5c83-4d6e-9592-aaa38c1843a4\",\"trace_id\":\"265be7c0-bf8f-4037-a0d2-57df3ef1d7e1\"}\n[2026-04-27 10:26:28] local.INFO: [Gmail] imported 7 emails via full sync workflow for inbox 212 {\"correlation_id\":\"d3e3eb3d-5c83-4d6e-9592-aaa38c1843a4\",\"trace_id\":\"265be7c0-bf8f-4037-a0d2-57df3ef1d7e1\"}\n[2026-04-27 10:26:28] local.INFO: [Gmail] seeding inbox 212 with last message time : 2026-04-27 10:24:57 {\"correlation_id\":\"d3e3eb3d-5c83-4d6e-9592-aaa38c1843a4\",\"trace_id\":\"265be7c0-bf8f-4037-a0d2-57df3ef1d7e1\"}\n[2026-04-27 10:26:28] local.INFO: [Sync Mailbox] Sync complete {\"inbox_id\":212} {\"correlation_id\":\"d3e3eb3d-5c83-4d6e-9592-aaa38c1843a4\",\"trace_id\":\"265be7c0-bf8f-4037-a0d2-57df3ef1d7e1\"}\n[2026-04-27 10:27:04] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage before starting command {\"command\":\"meeting-bot:schedule-bot\",\"memoryBeforeCommandInMb\":60.0,\"memoryPeakBeforeCommandInMb\":99.727} {\"correlation_id\":\"f55fbf77-7e5f-4c87-accd-011697ca0fa3\",\"trace_id\":\"9bd1c431-26de-4e31-83d8-3129ed9b3b79\"}\n[2026-04-27 10:27:04] local.INFO: [ScheduleBotCommand] Number of activities to be captured: 0 {\"correlation_id\":\"f55fbf77-7e5f-4c87-accd-011697ca0fa3\",\"trace_id\":\"9bd1c431-26de-4e31-83d8-3129ed9b3b79\"}\n[2026-04-27 10:27:04] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage for command {\"command\":\"meeting-bot:schedule-bot\",\"memoryBeforeCommandInMb\":60.0,\"memoryAfterCommandInMB\":62.0,\"memoryPeakBeforeCommandInMb\":99.727,\"memoryPeakAfterCommandInMB\":99.727} {\"correlation_id\":\"f55fbf77-7e5f-4c87-accd-011697ca0fa3\",\"trace_id\":\"9bd1c431-26de-4e31-83d8-3129ed9b3b79\"}\n[2026-04-27 10:27:05] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage before starting command {\"command\":\"dialers:monitor-activities\",\"memoryBeforeCommandInMb\":60.0,\"memoryPeakBeforeCommandInMb\":99.727} {\"correlation_id\":\"da241053-5f3b-4d02-9504-4ef2987cdbd7\",\"trace_id\":\"364d3fc7-0f11-40c6-8697-d18eff2c4cb6\"}\n[2026-04-27 10:27:05] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage for command {\"command\":\"dialers:monitor-activities\",\"memoryBeforeCommandInMb\":60.0,\"memoryAfterCommandInMB\":62.0,\"memoryPeakBeforeCommandInMb\":99.727,\"memoryPeakAfterCommandInMB\":99.727} {\"correlation_id\":\"da241053-5f3b-4d02-9504-4ef2987cdbd7\",\"trace_id\":\"364d3fc7-0f11-40c6-8697-d18eff2c4cb6\"}\n[2026-04-27 10:27:06] local.NOTICE: Monitoring start {\"correlation_id\":\"ad0d871a-a1fd-4725-b2f7-ac8f0fc1857e\",\"trace_id\":\"cb504e84-d924-43a0-82d5-faf1840134ba\"}\n[2026-04-27 10:27:06] local.NOTICE: Monitoring end {\"correlation_id\":\"ad0d871a-a1fd-4725-b2f7-ac8f0fc1857e\",\"trace_id\":\"cb504e84-d924-43a0-82d5-faf1840134ba\"}\n[2026-04-27 10:27:08] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage before starting command {\"command\":\"mailbox:skip-lists:refresh\",\"memoryBeforeCommandInMb\":60.0,\"memoryPeakBeforeCommandInMb\":99.727} {\"correlation_id\":\"b8652f0d-1cee-44ae-a0ea-3d3137a882c1\",\"trace_id\":\"7fe19940-0813-4199-a2a8-958de09bd0ee\"}\n[2026-04-27 10:27:08] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage for command {\"command\":\"mailbox:skip-lists:refresh\",\"memoryBeforeCommandInMb\":60.0,\"memoryAfterCommandInMB\":62.0,\"memoryPeakBeforeCommandInMb\":99.727,\"memoryPeakAfterCommandInMB\":99.727} {\"correlation_id\":\"b8652f0d-1cee-44ae-a0ea-3d3137a882c1\",\"trace_id\":\"7fe19940-0813-4199-a2a8-958de09bd0ee\"}\n[2026-04-27 10:27:09] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage before starting command {\"command\":\"mailbox:batch:process\",\"memoryBeforeCommandInMb\":60.0,\"memoryPeakBeforeCommandInMb\":99.727} {\"correlation_id\":\"06190462-0f47-403f-8e95-ad5d05bff1fe\",\"trace_id\":\"4aa6bed6-05a1-4661-a9c9-edadc61d84ac\"}\n[2026-04-27 10:27:09] local.INFO: [EmailSchedule] STARTING batch process {\"host\":\"docker_lamp_1\"} {\"correlation_id\":\"06190462-0f47-403f-8e95-ad5d05bff1fe\",\"trace_id\":\"4aa6bed6-05a1-4661-a9c9-edadc61d84ac\"}\n[2026-04-27 10:27:09] local.INFO: [EmailSchedule] FINISHED batch process {\"host\":\"docker_lamp_1\",\"processed\":0} {\"correlation_id\":\"06190462-0f47-403f-8e95-ad5d05bff1fe\",\"trace_id\":\"4aa6bed6-05a1-4661-a9c9-edadc61d84ac\"}\n[2026-04-27 10:27:09] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage for command {\"command\":\"mailbox:batch:process\",\"memoryBeforeCommandInMb\":60.0,\"memoryAfterCommandInMB\":62.0,\"memoryPeakBeforeCommandInMb\":99.727,\"memoryPeakAfterCommandInMB\":99.727} {\"correlation_id\":\"06190462-0f47-403f-8e95-ad5d05bff1fe\",\"trace_id\":\"4aa6bed6-05a1-4661-a9c9-edadc61d84ac\"}\n[2026-04-27 10:27:11] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage before starting command {\"command\":\"mailbox:batch:create\",\"memoryBeforeCommandInMb\":60.0,\"memoryPeakBeforeCommandInMb\":99.727} {\"correlation_id\":\"b561c5d5-e6e3-4091-9517-b0458190a360\",\"trace_id\":\"bf9679fb-b785-40cb-b814-04a1dc385a1c\"}\n[2026-04-27 10:27:11] local.INFO: [EmailSchedule] STARTING batch create {\"host\":\"docker_lamp_1\"} {\"correlation_id\":\"b561c5d5-e6e3-4091-9517-b0458190a360\",\"trace_id\":\"bf9679fb-b785-40cb-b814-04a1dc385a1c\"}\n[2026-04-27 10:27:11] local.INFO: [EmailSchedule] FINISHED batch create {\"host\":\"docker_lamp_1\"} {\"correlation_id\":\"b561c5d5-e6e3-4091-9517-b0458190a360\",\"trace_id\":\"bf9679fb-b785-40cb-b814-04a1dc385a1c\"}\n[2026-04-27 10:27:11] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage for command {\"command\":\"mailbox:batch:create\",\"memoryBeforeCommandInMb\":60.0,\"memoryAfterCommandInMB\":62.0,\"memoryPeakBeforeCommandInMb\":99.727,\"memoryPeakAfterCommandInMB\":99.727} {\"correlation_id\":\"b561c5d5-e6e3-4091-9517-b0458190a360\",\"trace_id\":\"bf9679fb-b785-40cb-b814-04a1dc385a1c\"}\n[2026-04-27 10:27:14] local.INFO: [Jiminny\\Jobs\\Mailbox\\CreateBatches] processed 2 inboxes and created 1 batches {\"userId\":null,\"batchSize\":30,\"maxBatches\":1000} {\"correlation_id\":\"98416296-43c2-4834-927e-4a0e86dc99cf\",\"trace_id\":\"bf9679fb-b785-40cb-b814-04a1dc385a1c\"}\n[2026-04-27 10:28:04] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage before starting command {\"command\":\"meeting-bot:schedule-bot\",\"memoryBeforeCommandInMb\":60.0,\"memoryPeakBeforeCommandInMb\":99.727} {\"correlation_id\":\"5852bcf1-af92-4d24-8a2d-08a9d4a5cfac\",\"trace_id\":\"25af5e8c-a148-493e-ac9f-4d53f1960177\"}\n[2026-04-27 10:28:04] local.INFO: [ScheduleBotCommand] Number of activities to be captured: 0 {\"correlation_id\":\"5852bcf1-af92-4d24-8a2d-08a9d4a5cfac\",\"trace_id\":\"25af5e8c-a148-493e-ac9f-4d53f1960177\"}\n[2026-04-27 10:28:04] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage for command {\"command\":\"meeting-bot:schedule-bot\",\"memoryBeforeCommandInMb\":60.0,\"memoryAfterCommandInMB\":62.0,\"memoryPeakBeforeCommandInMb\":99.727,\"memoryPeakAfterCommandInMB\":99.727} {\"correlation_id\":\"5852bcf1-af92-4d24-8a2d-08a9d4a5cfac\",\"trace_id\":\"25af5e8c-a148-493e-ac9f-4d53f1960177\"}\n[2026-04-27 10:28:06] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage before starting command {\"command\":\"dialers:monitor-activities\",\"memoryBeforeCommandInMb\":60.0,\"memoryPeakBeforeCommandInMb\":99.727} {\"correlation_id\":\"cc1cff1f-71b4-4bb1-800f-66e27c0f2b3a\",\"trace_id\":\"c03103a3-9ab8-480b-9753-7c7c6488f246\"}\n[2026-04-27 10:28:06] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage for command {\"command\":\"dialers:monitor-activities\",\"memoryBeforeCommandInMb\":60.0,\"memoryAfterCommandInMB\":62.0,\"memoryPeakBeforeCommandInMb\":99.727,\"memoryPeakAfterCommandInMB\":99.727} {\"correlation_id\":\"cc1cff1f-71b4-4bb1-800f-66e27c0f2b3a\",\"trace_id\":\"c03103a3-9ab8-480b-9753-7c7c6488f246\"}\n[2026-04-27 10:28:09] local.ERROR: Target class [Jiminny\\Repositories\\AjReportsRepository] does not exist. {\"exception\":\"[object] (Illuminate\\\\Contracts\\\\Container\\\\BindingResolutionException(code: 0): Target class [Jiminny\\\\Repositories\\\\AjReportsRepository] does not exist. at /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php:1124)\n[stacktrace]\n#0 /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php(933): Illuminate\\\\Container\\\\Container->build('Jiminny\\\\\\\\Reposit...')\n#1 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Application.php(1078): Illuminate\\\\Container\\\\Container->resolve('Jiminny\\\\\\\\Reposit...', Array, true)\n#2 /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php(864): Illuminate\\\\Foundation\\\\Application->resolve('Jiminny\\\\\\\\Reposit...', Array)\n#3 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Application.php(1058): Illuminate\\\\Container\\\\Container->make('Jiminny\\\\\\\\Reposit...', Array)\n#4 /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php(1336): Illuminate\\\\Foundation\\\\Application->make('Jiminny\\\\\\\\Reposit...')\n#5 /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php(1237): Illuminate\\\\Container\\\\Container->resolveClass(Object(ReflectionParameter))\n#6 /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php(1162): Illuminate\\\\Container\\\\Container->resolveDependencies(Array)\n#7 /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php(933): Illuminate\\\\Container\\\\Container->build('Jiminny\\\\\\\\Http\\\\\\\\Co...')\n#8 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Application.php(1078): Illuminate\\\\Container\\\\Container->resolve('Jiminny\\\\\\\\Http\\\\\\\\Co...', Array, true)\n#9 /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php(864): Illuminate\\\\Foundation\\\\Application->resolve('Jiminny\\\\\\\\Http\\\\\\\\Co...', Array)\n#10 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Application.php(1058): Illuminate\\\\Container\\\\Container->make('Jiminny\\\\\\\\Http\\\\\\\\Co...', Array)\n#11 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Route.php(286): Illuminate\\\\Foundation\\\\Application->make('Jiminny\\\\\\\\Http\\\\\\\\Co...')\n#12 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Route.php(1133): Illuminate\\\\Routing\\\\Route->getController()\n#13 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Route.php(1062): Illuminate\\\\Routing\\\\Route->controllerMiddleware()\n#14 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Router.php(834): Illuminate\\\\Routing\\\\Route->gatherMiddleware()\n#15 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Router.php(816): Illuminate\\\\Routing\\\\Router->gatherRouteMiddleware(Object(Illuminate\\\\Routing\\\\Route))\n#16 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Router.php(800): Illuminate\\\\Routing\\\\Router->runRouteWithinStack(Object(Illuminate\\\\Routing\\\\Route), Object(Illuminate\\\\Http\\\\Request))\n#17 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Router.php(764): Illuminate\\\\Routing\\\\Router->runRoute(Object(Illuminate\\\\Http\\\\Request), Object(Illuminate\\\\Routing\\\\Route))\n#18 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Router.php(753): Illuminate\\\\Routing\\\\Router->dispatchToRoute(Object(Illuminate\\\\Http\\\\Request))\n#19 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Http/Kernel.php(200): Illuminate\\\\Routing\\\\Router->dispatch(Object(Illuminate\\\\Http\\\\Request))\n#20 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(180): Illuminate\\\\Foundation\\\\Http\\\\Kernel->Illuminate\\\\Foundation\\\\Http\\\\{closure}(Object(Illuminate\\\\Http\\\\Request))\n#21 /home/jiminny/vendor/barryvdh/laravel-debugbar/src/Middleware/InjectDebugbar.php(59): Illuminate\\\\Pipeline\\\\Pipeline->Illuminate\\\\Pipeline\\\\{closure}(Object(Illuminate\\\\Http\\\\Request))\n#22 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(219): Barryvdh\\\\Debugbar\\\\Middleware\\\\InjectDebugbar->handle(Object(Illuminate\\\\Http\\\\Request), Object(Closure))\n#23 /home/jiminny/vendor/laravel/framework/src/Illuminate/Http/Middleware/HandleCors.php(74): Illuminate\\\\Pipeline\\\\Pipeline->Illuminate\\\\Pipeline\\\\{closure}(Object(Illuminate\\\\Http\\\\Request))\n#24 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(219): Illuminate\\\\Http\\\\Middleware\\\\HandleCors->handle(Object(Illuminate\\\\Http\\\\Request), Object(Closure))\n#25 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Http/Middleware/TransformsRequest.php(21): Illuminate\\\\Pipeline\\\\Pipeline->Illuminate\\\\Pipeline\\\\{closure}(Object(Illuminate\\\\Http\\\\Request))\n#26 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Http/Middleware/TrimStrings.php(51): Illuminate\\\\Foundation\\\\Http\\\\Middleware\\\\TransformsRequest->handle(Object(Illuminate\\\\Http\\\\Request), Object(Closure))\n#27 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(219): Illuminate\\\\Foundation\\\\Http\\\\Middleware\\\\TrimStrings->handle(Object(Illuminate\\\\Http\\\\Request), Object(Closure))\n#28 /home/jiminny/vendor/bepsvpt/secure-headers/src/SecureHeadersMiddleware.php(18): Illuminate\\\\Pipeline\\\\Pipeline->Illuminate\\\\Pipeline\\\\{closure}(Object(Illuminate\\\\Http\\\\Request))\n#29 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(219): Bepsvpt\\\\SecureHeaders\\\\SecureHeadersMiddleware->handle(Object(Illuminate\\\\Http\\\\Request), Object(Closure))\n#30 /home/jiminny/app/Http/Middleware/SentryContext.php(60): Illuminate\\\\Pipeline\\\\Pipeline->Illuminate\\\\Pipeline\\\\{closure}(Object(Illuminate\\\\Http\\\\Request))\n#31 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(219): Jiminny\\\\Http\\\\Middleware\\\\SentryContext->handle(Object(Illuminate\\\\Http\\\\Request), Object(Closure))\n#32 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Http/Middleware/PreventRequestsDuringMaintenance.php(109): Illuminate\\\\Pipeline\\\\Pipeline->Illuminate\\\\Pipeline\\\\{closure}(Object(Illuminate\\\\Http\\\\Request))\n#33 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(219): Illuminate\\\\Foundation\\\\Http\\\\Middleware\\\\PreventRequestsDuringMaintenance->handle(Object(Illuminate\\\\Http\\\\Request), Object(Closure))\n#34 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Http/Middleware/InvokeDeferredCallbacks.php(22): Illuminate\\\\Pipeline\\\\Pipeline->Illuminate\\\\Pipeline\\\\{closure}(Object(Illuminate\\\\Http\\\\Request))\n#35 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(219): Illuminate\\\\Foundation\\\\Http\\\\Middleware\\\\InvokeDeferredCallbacks->handle(Object(Illuminate\\\\Http\\\\Request), Object(Closure))\n#36 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(137): Illuminate\\\\Pipeline\\\\Pipeline->Illuminate\\\\Pipeline\\\\{closure}(Object(Illuminate\\\\Http\\\\Request))\n#37 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Http/Kernel.php(175): Illuminate\\\\Pipeline\\\\Pipeline->then(Object(Closure))\n#38 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Http/Kernel.php(144): Illuminate\\\\Foundation\\\\Http\\\\Kernel->sendRequestThroughRouter(Object(Illuminate\\\\Http\\\\Request))\n#39 /home/jiminny/public/index.php(51): Illuminate\\\\Foundation\\\\Http\\\\Kernel->handle(Object(Illuminate\\\\Http\\\\Request))\n#40 {main}\n\n[previous exception] [object] (ReflectionException(code: -1): Class \\\"Jiminny\\\\Repositories\\\\AjReportsRepository\\\" does not exist at /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php:1122)\n[stacktrace]\n#0 /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php(1122): ReflectionClass->__construct('Jiminny\\\\\\\\Reposit...')\n#1 /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php(933): Illuminate\\\\Container\\\\Container->build('Jiminny\\\\\\\\Reposit...')\n#2 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Application.php(1078): Illuminate\\\\Container\\\\Container->resolve('Jiminny\\\\\\\\Reposit...', Array, true)\n#3 /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php(864): Illuminate\\\\Foundation\\\\Application->resolve('Jiminny\\\\\\\\Reposit...', Array)\n#4 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Application.php(1058): Illuminate\\\\Container\\\\Container->make('Jiminny\\\\\\\\Reposit...', Array)\n#5 /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php(1336): Illuminate\\\\Foundation\\\\Application->make('Jiminny\\\\\\\\Reposit...')\n#6 /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php(1237): Illuminate\\\\Container\\\\Container->resolveClass(Object(ReflectionParameter))\n#7 /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php(1162): Illuminate\\\\Container\\\\Container->resolveDependencies(Array)\n#8 /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php(933): Illuminate\\\\Container\\\\Container->build('Jiminny\\\\\\\\Http\\\\\\\\Co...')\n#9 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Application.php(1078): Illuminate\\\\Container\\\\Container->resolve('Jiminny\\\\\\\\Http\\\\\\\\Co...', Array, true)\n#10 /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php(864): Illuminate\\\\Foundation\\\\Application->resolve('Jiminny\\\\\\\\Http\\\\\\\\Co...', Array)\n#11 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Application.php(1058): Illuminate\\\\Container\\\\Container->make('Jiminny\\\\\\\\Http\\\\\\\\Co...', Array)\n#12 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Route.php(286): Illuminate\\\\Foundation\\\\Application->make('Jiminny\\\\\\\\Http\\\\\\\\Co...')\n#13 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Route.php(1133): Illuminate\\\\Routing\\\\Route->getController()\n#14 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Route.php(1062): Illuminate\\\\Routing\\\\Route->controllerMiddleware()\n#15 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Router.php(834): Illuminate\\\\Routing\\\\Route->gatherMiddleware()\n#16 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Router.php(816): Illuminate\\\\Routing\\\\Router->gatherRouteMiddleware(Object(Illuminate\\\\Routing\\\\Route))\n#17 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Router.php(800): Illuminate\\\\Routing\\\\Router->runRouteWithinStack(Object(Illuminate\\\\Routing\\\\Route), Object(Illuminate\\\\Http\\\\Request))\n#18 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Router.php(764): Illuminate\\\\Routing\\\\Router->runRoute(Object(Illuminate\\\\Http\\\\Request), Object(Illuminate\\\\Routing\\\\Route))\n#19 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Router.php(753): Illuminate\\\\Routing\\\\Router->dispatchToRoute(Object(Illuminate\\\\Http\\\\Request))\n#20 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Http/Kernel.php(200): Illuminate\\\\Routing\\\\Router->dispatch(Object(Illuminate\\\\Http\\\\Request))\n#21 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(180): Illuminate\\\\Foundation\\\\Http\\\\Kernel->Illuminate\\\\Foundation\\\\Http\\\\{closure}(Object(Illuminate\\\\Http\\\\Request))\n#22 /home/jiminny/vendor/barryvdh/laravel-debugbar/src/Middleware/InjectDebugbar.php(59): Illuminate\\\\Pipeline\\\\Pipeline->Illuminate\\\\Pipeline\\\\{closure}(Object(Illuminate\\\\Http\\\\Request))\n#23 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(219): Barryvdh\\\\Debugbar\\\\Middleware\\\\InjectDebugbar->handle(Object(Illuminate\\\\Http\\\\Request), Object(Closure))\n#24 /home/jiminny/vendor/laravel/framework/src/Illuminate/Http/Middleware/HandleCors.php(74): Illuminate\\\\Pipeline\\\\Pipeline->Illuminate\\\\Pipeline\\\\{closure}(Object(Illuminate\\\\Http\\\\Request))\n#25 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(219): Illuminate\\\\Http\\\\Middleware\\\\HandleCors->handle(Object(Illuminate\\\\Http\\\\Request), Object(Closure))\n#26 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Http/Middleware/TransformsRequest.php(21): Illuminate\\\\Pipeline\\\\Pipeline->Illuminate\\\\Pipeline\\\\{closure}(Object(Illuminate\\\\Http\\\\Request))\n#27 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Http/Middleware/TrimStrings.php(51): Illuminate\\\\Foundation\\\\Http\\\\Middleware\\\\TransformsRequest->handle(Object(Illuminate\\\\Http\\\\Request), Object(Closure))\n#28 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(219): Illuminate\\\\Foundation\\\\Http\\\\Middleware\\\\TrimStrings->handle(Object(Illuminate\\\\Http\\\\Request), Object(Closure))\n#29 /home/jiminny/vendor/bepsvpt/secure-headers/src/SecureHeadersMiddleware.php(18): Illuminate\\\\Pipeline\\\\Pipeline->Illuminate\\\\Pipeline\\\\{closure}(Object(Illuminate\\\\Http\\\\Request))\n#30 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(219): Bepsvpt\\\\SecureHeaders\\\\SecureHeadersMiddleware->handle(Object(Illuminate\\\\Http\\\\Request), Object(Closure))\n#31 /home/jiminny/app/Http/Middleware/SentryContext.php(60): Illuminate\\\\Pipeline\\\\Pipeline->Illuminate\\\\Pipeline\\\\{closure}(Object(Illuminate\\\\Http\\\\Request))\n#32 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(219): Jiminny\\\\Http\\\\Middleware\\\\SentryContext->handle(Object(Illuminate\\\\Http\\\\Request), Object(Closure))\n#33 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Http/Middleware/PreventRequestsDuringMaintenance.php(109): Illuminate\\\\Pipeline\\\\Pipeline->Illuminate\\\\Pipeline\\\\{closure}(Object(Illuminate\\\\Http\\\\Request))\n#34 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(219): Illuminate\\\\Foundation\\\\Http\\\\Middleware\\\\PreventRequestsDuringMaintenance->handle(Object(Illuminate\\\\Http\\\\Request), Object(Closure))\n#35 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Http/Middleware/InvokeDeferredCallbacks.php(22): Illuminate\\\\Pipeline\\\\Pipeline->Illuminate\\\\Pipeline\\\\{closure}(Object(Illuminate\\\\Http\\\\Request))\n#36 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(219): Illuminate\\\\Foundation\\\\Http\\\\Middleware\\\\InvokeDeferredCallbacks->handle(Object(Illuminate\\\\Http\\\\Request), Object(Closure))\n#37 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(137): Illuminate\\\\Pipeline\\\\Pipeline->Illuminate\\\\Pipeline\\\\{closure}(Object(Illuminate\\\\Http\\\\Request))\n#38 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Http/Kernel.php(175): Illuminate\\\\Pipeline\\\\Pipeline->then(Object(Closure))\n#39 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Http/Kernel.php(144): Illuminate\\\\Foundation\\\\Http\\\\Kernel->sendRequestThroughRouter(Object(Illuminate\\\\Http\\\\Request))\n#40 /home/jiminny/public/index.php(51): Illuminate\\\\Foundation\\\\Http\\\\Kernel->handle(Object(Illuminate\\\\Http\\\\Request))\n#41 {main}\n\"} {\"correlation_id\":\"dfad8817-1dde-4622-8320-f5fc1616a8fe\",\"trace_id\":\"d21232b2-60fb-4ca0-838c-ba42e61593f9\"}\n[2026-04-27 10:28:09] local.NOTICE: Monitoring start {\"correlation_id\":\"93744c67-4444-41ff-aaad-6aa9cafe294e\",\"trace_id\":\"80e396b0-b1ac-4ec8-838c-00021f98072c\"}\n[2026-04-27 10:28:09] local.NOTICE: Monitoring end {\"correlation_id\":\"93744c67-4444-41ff-aaad-6aa9cafe294e\",\"trace_id\":\"80e396b0-b1ac-4ec8-838c-00021f98072c\"}\n[2026-04-27 10:28:10] local.ERROR: Target class [Jiminny\\Repositories\\AjReportsRepository] does not exist. {\"exception\":\"[object] (Illuminate\\\\Contracts\\\\Container\\\\BindingResolutionException(code: 0): Target class [Jiminny\\\\Repositories\\\\AjReportsRepository] does not exist. at /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php:1124)\n[stacktrace]\n#0 /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php(933): Illuminate\\\\Container\\\\Container->build('Jiminny\\\\\\\\Reposit...')\n#1 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Application.php(1078): Illuminate\\\\Container\\\\Container->resolve('Jiminny\\\\\\\\Reposit...', Array, true)\n#2 /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php(864): Illuminate\\\\Foundation\\\\Application->resolve('Jiminny\\\\\\\\Reposit...', Array)\n#3 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Application.php(1058): Illuminate\\\\Container\\\\Container->make('Jiminny\\\\\\\\Reposit...', Array)\n#4 /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php(1336): Illuminate\\\\Foundation\\\\Application->make('Jiminny\\\\\\\\Reposit...')\n#5 /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php(1237): Illuminate\\\\Container\\\\Container->resolveClass(Object(ReflectionParameter))\n#6 /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php(1162): Illuminate\\\\Container\\\\Container->resolveDependencies(Array)\n#7 /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php(933): Illuminate\\\\Container\\\\Container->build('Jiminny\\\\\\\\Http\\\\\\\\Co...')\n#8 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Application.php(1078): Illuminate\\\\Container\\\\Container->resolve('Jiminny\\\\\\\\Http\\\\\\\\Co...', Array, true)\n#9 /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php(864): Illuminate\\\\Foundation\\\\Application->resolve('Jiminny\\\\\\\\Http\\\\\\\\Co...', Array)\n#10 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Application.php(1058): Illuminate\\\\Container\\\\Container->make('Jiminny\\\\\\\\Http\\\\\\\\Co...', Array)\n#11 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Route.php(286): Illuminate\\\\Foundation\\\\Application->make('Jiminny\\\\\\\\Http\\\\\\\\Co...')\n#12 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Route.php(1133): Illuminate\\\\Routing\\\\Route->getController()\n#13 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Route.php(1062): Illuminate\\\\Routing\\\\Route->controllerMiddleware()\n#14 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Router.php(834): Illuminate\\\\Routing\\\\Route->gatherMiddleware()\n#15 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Router.php(816): Illuminate\\\\Routing\\\\Router->gatherRouteMiddleware(Object(Illuminate\\\\Routing\\\\Route))\n#16 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Router.php(800): Illuminate\\\\Routing\\\\Router->runRouteWithinStack(Object(Illuminate\\\\Routing\\\\Route), Object(Illuminate\\\\Http\\\\Request))\n#17 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Router.php(764): Illuminate\\\\Routing\\\\Router->runRoute(Object(Illuminate\\\\Http\\\\Request), Object(Illuminate\\\\Routing\\\\Route))\n#18 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Router.php(753): Illuminate\\\\Routing\\\\Router->dispatchToRoute(Object(Illuminate\\\\Http\\\\Request))\n#19 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Http/Kernel.php(200): Illuminate\\\\Routing\\\\Router->dispatch(Object(Illuminate\\\\Http\\\\Request))\n#20 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(180): Illuminate\\\\Foundation\\\\Http\\\\Kernel->Illuminate\\\\Foundation\\\\Http\\\\{closure}(Object(Illuminate\\\\Http\\\\Request))\n#21 /home/jiminny/vendor/barryvdh/laravel-debugbar/src/Middleware/InjectDebugbar.php(59): Illuminate\\\\Pipeline\\\\Pipeline->Illuminate\\\\Pipeline\\\\{closure}(Object(Illuminate\\\\Http\\\\Request))\n#22 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(219): Barryvdh\\\\Debugbar\\\\Middleware\\\\InjectDebugbar->handle(Object(Illuminate\\\\Http\\\\Request), Object(Closure))\n#23 /home/jiminny/vendor/laravel/framework/src/Illuminate/Http/Middleware/HandleCors.php(74): Illuminate\\\\Pipeline\\\\Pipeline->Illuminate\\\\Pipeline\\\\{closure}(Object(Illuminate\\\\Http\\\\Request))\n#24 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(219): Illuminate\\\\Http\\\\Middleware\\\\HandleCors->handle(Object(Illuminate\\\\Http\\\\Request), Object(Closure))\n#25 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Http/Middleware/TransformsRequest.php(21): Illuminate\\\\Pipeline\\\\Pipeline->Illuminate\\\\Pipeline\\\\{closure}(Object(Illuminate\\\\Http\\\\Request))\n#26 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Http/Middleware/TrimStrings.php(51): Illuminate\\\\Foundation\\\\Http\\\\Middleware\\\\TransformsRequest->handle(Object(Illuminate\\\\Http\\\\Request), Object(Closure))\n#27 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(219): Illuminate\\\\Foundation\\\\Http\\\\Middleware\\\\TrimStrings->handle(Object(Illuminate\\\\Http\\\\Request), Object(Closure))\n#28 /home/jiminny/vendor/bepsvpt/secure-headers/src/SecureHeadersMiddleware.php(18): Illuminate\\\\Pipeline\\\\Pipeline->Illuminate\\\\Pipeline\\\\{closure}(Object(Illuminate\\\\Http\\\\Request))\n#29 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(219): Bepsvpt\\\\SecureHeaders\\\\SecureHeadersMiddleware->handle(Object(Illuminate\\\\Http\\\\Request), Object(Closure))\n#30 /home/jiminny/app/Http/Middleware/SentryContext.php(60): Illuminate\\\\Pipeline\\\\Pipeline->Illuminate\\\\Pipeline\\\\{closure}(Object(Illuminate\\\\Http\\\\Request))\n#31 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(219): Jiminny\\\\Http\\\\Middleware\\\\SentryContext->handle(Object(Illuminate\\\\Http\\\\Request), Object(Closure))\n#32 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Http/Middleware/PreventRequestsDuringMaintenance.php(109): Illuminate\\\\Pipeline\\\\Pipeline->Illuminate\\\\Pipeline\\\\{closure}(Object(Illuminate\\\\Http\\\\Request))\n#33 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(219): Illuminate\\\\Foundation\\\\Http\\\\Middleware\\\\PreventRequestsDuringMaintenance->handle(Object(Illuminate\\\\Http\\\\Request), Object(Closure))\n#34 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Http/Middleware/InvokeDeferredCallbacks.php(22): Illuminate\\\\Pipeline\\\\Pipeline->Illuminate\\\\Pipeline\\\\{closure}(Object(Illuminate\\\\Http\\\\Request))\n#35 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(219): Illuminate\\\\Foundation\\\\Http\\\\Middleware\\\\InvokeDeferredCallbacks->handle(Object(Illuminate\\\\Http\\\\Request), Object(Closure))\n#36 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(137): Illuminate\\\\Pipeline\\\\Pipeline->Illuminate\\\\Pipeline\\\\{closure}(Object(Illuminate\\\\Http\\\\Request))\n#37 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Http/Kernel.php(175): Illuminate\\\\Pipeline\\\\Pipeline->then(Object(Closure))\n#38 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Http/Kernel.php(144): Illuminate\\\\Foundation\\\\Http\\\\Kernel->sendRequestThroughRouter(Object(Illuminate\\\\Http\\\\Request))\n#39 /home/jiminny/public/index.php(51): Illuminate\\\\Foundation\\\\Http\\\\Kernel->handle(Object(Illuminate\\\\Http\\\\Request))\n#40 {main}\n\n[previous exception] [object] (ReflectionException(code: -1): Class \\\"Jiminny\\\\Repositories\\\\AjReportsRepository\\\" does not exist at /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php:1122)\n[stacktrace]\n#0 /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php(1122): ReflectionClass->__construct('Jiminny\\\\\\\\Reposit...')\n#1 /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php(933): Illuminate\\\\Container\\\\Container->build('Jiminny\\\\\\\\Reposit...')\n#2 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Application.php(1078): Illuminate\\\\Container\\\\Container->resolve('Jiminny\\\\\\\\Reposit...', Array, true)\n#3 /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php(864): Illuminate\\\\Foundation\\\\Application->resolve('Jiminny\\\\\\\\Reposit...', Array)\n#4 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Application.php(1058): Illuminate\\\\Container\\\\Container->make('Jiminny\\\\\\\\Reposit...', Array)\n#5 /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php(1336): Illuminate\\\\Foundation\\\\Application->make('Jiminny\\\\\\\\Reposit...')\n#6 /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php(1237): Illuminate\\\\Container\\\\Container->resolveClass(Object(ReflectionParameter))\n#7 /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php(1162): Illuminate\\\\Container\\\\Container->resolveDependencies(Array)\n#8 /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php(933): Illuminate\\\\Container\\\\Container->build('Jiminny\\\\\\\\Http\\\\\\\\Co...')\n#9 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Application.php(1078): Illuminate\\\\Container\\\\Container->resolve('Jiminny\\\\\\\\Http\\\\\\\\Co...', Array, true)\n#10 /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php(864): Illuminate\\\\Foundation\\\\Application->resolve('Jiminny\\\\\\\\Http\\\\\\\\Co...', Array)\n#11 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Application.php(1058): Illuminate\\\\Container\\\\Container->make('Jiminny\\\\\\\\Http\\\\\\\\Co...', Array)\n#12 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Route.php(286): Illuminate\\\\Foundation\\\\Application->make('Jiminny\\\\\\\\Http\\\\\\\\Co...')\n#13 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Route.php(1133): Illuminate\\\\Routing\\\\Route->getController()\n#14 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Route.php(1062): Illuminate\\\\Routing\\\\Route->controllerMiddleware()\n#15 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Router.php(834): Illuminate\\\\Routing\\\\Route->gatherMiddleware()\n#16 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Router.php(816): Illuminate\\\\Routing\\\\Router->gatherRouteMiddleware(Object(Illuminate\\\\Routing\\\\Route))\n#17 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Router.php(800): Illuminate\\\\Routing\\\\Router->runRouteWithinStack(Object(Illuminate\\\\Routing\\\\Route), Object(Illuminate\\\\Http\\\\Request))\n#18 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Router.php(764): Illuminate\\\\Routing\\\\Router->runRoute(Object(Illuminate\\\\Http\\\\Request), Object(Illuminate\\\\Routing\\\\Route))\n#19 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Router.php(753): Illuminate\\\\Routing\\\\Router->dispatchToRoute(Object(Illuminate\\\\Http\\\\Request))\n#20 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Http/Kernel.php(200): Illuminate\\\\Routing\\\\Router->dispatch(Object(Illuminate\\\\Http\\\\Request))\n#21 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(180): Illuminate\\\\Foundation\\\\Http\\\\Kernel->Illuminate\\\\Foundation\\\\Http\\\\{closure}(Object(Illuminate\\\\Http\\\\Request))\n#22 /home/jiminny/vendor/barryvdh/laravel-debugbar/src/Middleware/InjectDebugbar.php(59): Illuminate\\\\Pipeline\\\\Pipeline->Illuminate\\\\Pipeline\\\\{closure}(Object(Illuminate\\\\Http\\\\Request))\n#23 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(219): Barryvdh\\\\Debugbar\\\\Middleware\\\\InjectDebugbar->handle(Object(Illuminate\\\\Http\\\\Request), Object(Closure))\n#24 /home/jiminny/vendor/laravel/framework/src/Illuminate/Http/Middleware/HandleCors.php(74): Illuminate\\\\Pipeline\\\\Pipeline->Illuminate\\\\Pipeline\\\\{closure}(Object(Illuminate\\\\Http\\\\Request))\n#25 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(219): Illuminate\\\\Http\\\\Middleware\\\\HandleCors->handle(Object(Illuminate\\\\Http\\\\Request), Object(Closure))\n#26 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Http/Middleware/TransformsRequest.php(21): Illuminate\\\\Pipeline\\\\Pipeline->Illuminate\\\\Pipeline\\\\{closure}(Object(Illuminate\\\\Http\\\\Request))\n#27 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Http/Middleware/TrimStrings.php(51): Illuminate\\\\Foundation\\\\Http\\\\Middleware\\\\TransformsRequest->handle(Object(Illuminate\\\\Http\\\\Request), Object(Closure))\n#28 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(219): Illuminate\\\\Foundation\\\\Http\\\\Middleware\\\\TrimStrings->handle(Object(Illuminate\\\\Http\\\\Request), Object(Closure))\n#29 /home/jiminny/vendor/bepsvpt/secure-headers/src/SecureHeadersMiddleware.php(18): Illuminate\\\\Pipeline\\\\Pipeline->Illuminate\\\\Pipeline\\\\{closure}(Object(Illuminate\\\\Http\\\\Request))\n#30 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(219): Bepsvpt\\\\SecureHeaders\\\\SecureHeadersMiddleware->handle(Object(Illuminate\\\\Http\\\\Request), Object(Closure))\n#31 /home/jiminny/app/Http/Middleware/SentryContext.php(60): Illuminate\\\\Pipeline\\\\Pipeline->Illuminate\\\\Pipeline\\\\{closure}(Object(Illuminate\\\\Http\\\\Request))\n#32 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(219): Jiminny\\\\Http\\\\Middleware\\\\SentryContext->handle(Object(Illuminate\\\\Http\\\\Request), Object(Closure))\n#33 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Http/Middleware/PreventRequestsDuringMaintenance.php(109): Illuminate\\\\Pipeline\\\\Pipeline->Illuminate\\\\Pipeline\\\\{closure}(Object(Illuminate\\\\Http\\\\Request))\n#34 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(219): Illuminate\\\\Foundation\\\\Http\\\\Middleware\\\\PreventRequestsDuringMaintenance->handle(Object(Illuminate\\\\Http\\\\Request), Object(Closure))\n#35 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Http/Middleware/InvokeDeferredCallbacks.php(22): Illuminate\\\\Pipeline\\\\Pipeline->Illuminate\\\\Pipeline\\\\{closure}(Object(Illuminate\\\\Http\\\\Request))\n#36 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(219): Illuminate\\\\Foundation\\\\Http\\\\Middleware\\\\InvokeDeferredCallbacks->handle(Object(Illuminate\\\\Http\\\\Request), Object(Closure))\n#37 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(137): Illuminate\\\\Pipeline\\\\Pipeline->Illuminate\\\\Pipeline\\\\{closure}(Object(Illuminate\\\\Http\\\\Request))\n#38 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Http/Kernel.php(175): Illuminate\\\\Pipeline\\\\Pipeline->then(Object(Closure))\n#39 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Http/Kernel.php(144): Illuminate\\\\Foundation\\\\Http\\\\Kernel->sendRequestThroughRouter(Object(Illuminate\\\\Http\\\\Request))\n#40 /home/jiminny/public/index.php(51): Illuminate\\\\Foundation\\\\Http\\\\Kernel->handle(Object(Illuminate\\\\Http\\\\Request))\n#41 {main}\n\"} {\"correlation_id\":\"de768781-5c05-418c-baf3-79f77a8c8694\",\"trace_id\":\"fec74062-3f6d-4cfc-968a-1bb285496db3\"}\n[2026-04-27 10:28:12] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage before starting command {\"command\":\"mailbox:skip-lists:refresh\",\"memoryBeforeCommandInMb\":60.0,\"memoryPeakBeforeCommandInMb\":99.727} {\"correlation_id\":\"907d15ed-8c84-41a1-a473-590eebb55dbc\",\"trace_id\":\"ec7bd353-516e-491d-89dd-de44136c1672\"}\n[2026-04-27 10:28:12] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage for command {\"command\":\"mailbox:skip-lists:refresh\",\"memoryBeforeCommandInMb\":60.0,\"memoryAfterCommandInMB\":62.0,\"memoryPeakBeforeCommandInMb\":99.727,\"memoryPeakAfterCommandInMB\":99.727} {\"correlation_id\":\"907d15ed-8c84-41a1-a473-590eebb55dbc\",\"trace_id\":\"ec7bd353-516e-491d-89dd-de44136c1672\"}\n[2026-04-27 10:28:13] local.ERROR: Target class [Jiminny\\Repositories\\AjReportsRepository] does not exist. {\"exception\":\"[object] (Illuminate\\\\Contracts\\\\Container\\\\BindingResolutionException(code: 0): Target class [Jiminny\\\\Repositories\\\\AjReportsRepository] does not exist. at /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php:1124)\n[stacktrace]\n#0 /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php(933): Illuminate\\\\Container\\\\Container->build('Jiminny\\\\\\\\Reposit...')\n#1 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Application.php(1078): Illuminate\\\\Container\\\\Container->resolve('Jiminny\\\\\\\\Reposit...', Array, true)\n#2 /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php(864): Illuminate\\\\Foundation\\\\Application->resolve('Jiminny\\\\\\\\Reposit...', Array)\n#3 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Application.php(1058): Illuminate\\\\Container\\\\Container->make('Jiminny\\\\\\\\Reposit...', Array)\n#4 /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php(1336): Illuminate\\\\Foundation\\\\Application->make('Jiminny\\\\\\\\Reposit...')\n#5 /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php(1237): Illuminate\\\\Container\\\\Container->resolveClass(Object(ReflectionParameter))\n#6 /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php(1162): Illuminate\\\\Container\\\\Container->resolveDependencies(Array)\n#7 /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php(933): Illuminate\\\\Container\\\\Container->build('Jiminny\\\\\\\\Http\\\\\\\\Co...')\n#8 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Application.php(1078): Illuminate\\\\Container\\\\Container->resolve('Jiminny\\\\\\\\Http\\\\\\\\Co...', Array, true)\n#9 /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php(864): Illuminate\\\\Foundation\\\\Application->resolve('Jiminny\\\\\\\\Http\\\\\\\\Co...', Array)\n#10 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Application.php(1058): Illuminate\\\\Container\\\\Container->make('Jiminny\\\\\\\\Http\\\\\\\\Co...', Array)\n#11 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Route.php(286): Illuminate\\\\Foundation\\\\Application->make('Jiminny\\\\\\\\Http\\\\\\\\Co...')\n#12 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Route.php(1133): Illuminate\\\\Routing\\\\Route->getController()\n#13 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Route.php(1062): Illuminate\\\\Routing\\\\Route->controllerMiddleware()\n#14 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Router.php(834): Illuminate\\\\Routing\\\\Route->gatherMiddleware()\n#15 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Router.php(816): Illuminate\\\\Routing\\\\Router->gatherRouteMiddleware(Object(Illuminate\\\\Routing\\\\Route))\n#16 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Router.php(800): Illuminate\\\\Routing\\\\Router->runRouteWithinStack(Object(Illuminate\\\\Routing\\\\Route), Object(Illuminate\\\\Http\\\\Request))\n#17 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Router.php(764): Illuminate\\\\Routing\\\\Router->runRoute(Object(Illuminate\\\\Http\\\\Request), Object(Illuminate\\\\Routing\\\\Route))\n#18 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Router.php(753): Illuminate\\\\Routing\\\\Router->dispatchToRoute(Object(Illuminate\\\\Http\\\\Request))\n#19 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Http/Kernel.php(200): Illuminate\\\\Routing\\\\Router->dispatch(Object(Illuminate\\\\Http\\\\Request))\n#20 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(180): Illuminate\\\\Foundation\\\\Http\\\\Kernel->Illuminate\\\\Foundation\\\\Http\\\\{closure}(Object(Illuminate\\\\Http\\\\Request))\n#21 /home/jiminny/vendor/barryvdh/laravel-debugbar/src/Middleware/InjectDebugbar.php(59): Illuminate\\\\Pipeline\\\\Pipeline->Illuminate\\\\Pipeline\\\\{closure}(Object(Illuminate\\\\Http\\\\Request))\n#22 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(219): Barryvdh\\\\Debugbar\\\\Middleware\\\\InjectDebugbar->handle(Object(Illuminate\\\\Http\\\\Request), Object(Closure))\n#23 /home/jiminny/vendor/laravel/framework/src/Illuminate/Http/Middleware/HandleCors.php(74): Illuminate\\\\Pipeline\\\\Pipeline->Illuminate\\\\Pipeline\\\\{closure}(Object(Illuminate\\\\Http\\\\Request))\n#24 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(219): Illuminate\\\\Http\\\\Middleware\\\\HandleCors->handle(Object(Illuminate\\\\Http\\\\Request), Object(Closure))\n#25 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Http/Middleware/TransformsRequest.php(21): Illuminate\\\\Pipeline\\\\Pipeline->Illuminate\\\\Pipeline\\\\{closure}(Object(Illuminate\\\\Http\\\\Request))\n#26 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Http/Middleware/TrimStrings.php(51): Illuminate\\\\Foundation\\\\Http\\\\Middleware\\\\TransformsRequest->handle(Object(Illuminate\\\\Http\\\\Request), Object(Closure))\n#27 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(219): Illuminate\\\\Foundation\\\\Http\\\\Middleware\\\\TrimStrings->handle(Object(Illuminate\\\\Http\\\\Request), Object(Closure))\n#28 /home/jiminny/vendor/bepsvpt/secure-headers/src/SecureHeadersMiddleware.php(18): Illuminate\\\\Pipeline\\\\Pipeline->Illuminate\\\\Pipeline\\\\{closure}(Object(Illuminate\\\\Http\\\\Request))\n#29 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(219): Bepsvpt\\\\SecureHeaders\\\\SecureHeadersMiddleware->handle(Object(Illuminate\\\\Http\\\\Request), Object(Closure))\n#30 /home/jiminny/app/Http/Middleware/SentryContext.php(60): Illuminate\\\\Pipeline\\\\Pipeline->Illuminate\\\\Pipeline\\\\{closure}(Object(Illuminate\\\\Http\\\\Request))\n#31 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(219): Jiminny\\\\Http\\\\Middleware\\\\SentryContext->handle(Object(Illuminate\\\\Http\\\\Request), Object(Closure))\n#32 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Http/Middleware/PreventRequestsDuringMaintenance.php(109): Illuminate\\\\Pipeline\\\\Pipeline->Illuminate\\\\Pipeline\\\\{closure}(Object(Illuminate\\\\Http\\\\Request))\n#33 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(219): Illuminate\\\\Foundation\\\\Http\\\\Middleware\\\\PreventRequestsDuringMaintenance->handle(Object(Illuminate\\\\Http\\\\Request), Object(Closure))\n#34 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Http/Middleware/InvokeDeferredCallbacks.php(22): Illuminate\\\\Pipeline\\\\Pipeline->Illuminate\\\\Pipeline\\\\{closure}(Object(Illuminate\\\\Http\\\\Request))\n#35 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(219): Illuminate\\\\Foundation\\\\Http\\\\Middleware\\\\InvokeDeferredCallbacks->handle(Object(Illuminate\\\\Http\\\\Request), Object(Closure))\n#36 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(137): Illuminate\\\\Pipeline\\\\Pipeline->Illuminate\\\\Pipeline\\\\{closure}(Object(Illuminate\\\\Http\\\\Request))\n#37 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Http/Kernel.php(175): Illuminate\\\\Pipeline\\\\Pipeline->then(Object(Closure))\n#38 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Http/Kernel.php(144): Illuminate\\\\Foundation\\\\Http\\\\Kernel->sendRequestThroughRouter(Object(Illuminate\\\\Http\\\\Request))\n#39 /home/jiminny/public/index.php(51): Illuminate\\\\Foundation\\\\Http\\\\Kernel->handle(Object(Illuminate\\\\Http\\\\Request))\n#40 {main}\n\n[previous exception] [object] (ReflectionException(code: -1): Class \\\"Jiminny\\\\Repositories\\\\AjReportsRepository\\\" does not exist at /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php:1122)\n[stacktrace]\n#0 /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php(1122): ReflectionClass->__construct('Jiminny\\\\\\\\Reposit...')\n#1 /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php(933): Illuminate\\\\Container\\\\Container->build('Jiminny\\\\\\\\Reposit...')\n#2 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Application.php(1078): Illuminate\\\\Container\\\\Container->resolve('Jiminny\\\\\\\\Reposit...', Array, true)\n#3 /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php(864): Illuminate\\\\Foundation\\\\Application->resolve('Jiminny\\\\\\\\Reposit...', Array)\n#4 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Application.php(1058): Illuminate\\\\Container\\\\Container->make('Jiminny\\\\\\\\Reposit...', Array)\n#5 /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php(1336): Illuminate\\\\Foundation\\\\Application->make('Jiminny\\\\\\\\Reposit...')\n#6 /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php(1237): Illuminate\\\\Container\\\\Container->resolveClass(Object(ReflectionParameter))\n#7 /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php(1162): Illuminate\\\\Container\\\\Container->resolveDependencies(Array)\n#8 /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php(933): Illuminate\\\\Container\\\\Container->build('Jiminny\\\\\\\\Http\\\\\\\\Co...')\n#9 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Application.php(1078): Illuminate\\\\Container\\\\Container->resolve('Jiminny\\\\\\\\Http\\\\\\\\Co...', Array, true)\n#10 /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php(864): Illuminate\\\\Foundation\\\\Application->resolve('Jiminny\\\\\\\\Http\\\\\\\\Co...', Array)\n#11 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Application.php(1058): Illuminate\\\\Container\\\\Container->make('Jiminny\\\\\\\\Http\\\\\\\\Co...', Array)\n#12 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Route.php(286): Illuminate\\\\Foundation\\\\Application->make('Jiminny\\\\\\\\Http\\\\\\\\Co...')\n#13 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Route.php(1133): Illuminate\\\\Routing\\\\Route->getController()\n#14 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Route.php(1062): Illuminate\\\\Routing\\\\Route->controllerMiddleware()\n#15 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Router.php(834): Illuminate\\\\Routing\\\\Route->gatherMiddleware()\n#16 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Router.php(816): Illuminate\\\\Routing\\\\Router->gatherRouteMiddleware(Object(Illuminate\\\\Routing\\\\Route))\n#17 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Router.php(800): Illuminate\\\\Routing\\\\Router->runRouteWithinStack(Object(Illuminate\\\\Routing\\\\Route), Object(Illuminate\\\\Http\\\\Request))\n#18 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Router.php(764): Illuminate\\\\Routing\\\\Router->runRoute(Object(Illuminate\\\\Http\\\\Request), Object(Illuminate\\\\Routing\\\\Route))\n#19 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Router.php(753): Illuminate\\\\Routing\\\\Router->dispatchToRoute(Object(Illuminate\\\\Http\\\\Request))\n#20 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Http/Kernel.php(200): Illuminate\\\\Routing\\\\Router->dispatch(Object(Illuminate\\\\Http\\\\Request))\n#21 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(180): Illuminate\\\\Foundation\\\\Http\\\\Kernel->Illuminate\\\\Foundation\\\\Http\\\\{closure}(Object(Illuminate\\\\Http\\\\Request))\n#22 /home/jiminny/vendor/barryvdh/laravel-debugbar/src/Middleware/InjectDebugbar.php(59): Illuminate\\\\Pipeline\\\\Pipeline->Illuminate\\\\Pipeline\\\\{closure}(Object(Illuminate\\\\Http\\\\Request))\n#23 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(219): Barryvdh\\\\Debugbar\\\\Middleware\\\\InjectDebugbar->handle(Object(Illuminate\\\\Http\\\\Request), Object(Closure))\n#24 /home/jiminny/vendor/laravel/framework/src/Illuminate/Http/Middleware/HandleCors.php(74): Illuminate\\\\Pipeline\\\\Pipeline->Illuminate\\\\Pipeline\\\\{closure}(Object(Illuminate\\\\Http\\\\Request))\n#25 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(219): Illuminate\\\\Http\\\\Middleware\\\\HandleCors->handle(Object(Illuminate\\\\Http\\\\Request), Object(Closure))\n#26 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Http/Middleware/TransformsRequest.php(21): Illuminate\\\\Pipeline\\\\Pipeline->Illuminate\\\\Pipeline\\\\{closure}(Object(Illuminate\\\\Http\\\\Request))\n#27 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Http/Middleware/TrimStrings.php(51): Illuminate\\\\Foundation\\\\Http\\\\Middleware\\\\TransformsRequest->handle(Object(Illuminate\\\\Http\\\\Request), Object(Closure))\n#28 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(219): Illuminate\\\\Foundation\\\\Http\\\\Middleware\\\\TrimStrings->handle(Object(Illuminate\\\\Http\\\\Request), Object(Closure))\n#29 /home/jiminny/vendor/bepsvpt/secure-headers/src/SecureHeadersMiddleware.php(18): Illuminate\\\\Pipeline\\\\Pipeline->Illuminate\\\\Pipeline\\\\{closure}(Object(Illuminate\\\\Http\\\\Request))\n#30 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(219): Bepsvpt\\\\SecureHeaders\\\\SecureHeadersMiddleware->handle(Object(Illuminate\\\\Http\\\\Request), Object(Closure))\n#31 /home/jiminny/app/Http/Middleware/SentryContext.php(60): Illuminate\\\\Pipeline\\\\Pipeline->Illuminate\\\\Pipeline\\\\{closure}(Object(Illuminate\\\\Http\\\\Request))\n#32 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(219): Jiminny\\\\Http\\\\Middleware\\\\SentryContext->handle(Object(Illuminate\\\\Http\\\\Request), Object(Closure))\n#33 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Http/Middleware/PreventRequestsDuringMaintenance.php(109): Illuminate\\\\Pipeline\\\\Pipeline->Illuminate\\\\Pipeline\\\\{closure}(Object(Illuminate\\\\Http\\\\Request))\n#34 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(219): Illuminate\\\\Foundation\\\\Http\\\\Middleware\\\\PreventRequestsDuringMaintenance->handle(Object(Illuminate\\\\Http\\\\Request), Object(Closure))\n#35 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Http/Middleware/InvokeDeferredCallbacks.php(22): Illuminate\\\\Pipeline\\\\Pipeline->Illuminate\\\\Pipeline\\\\{closure}(Object(Illuminate\\\\Http\\\\Request))\n#36 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(219): Illuminate\\\\Foundation\\\\Http\\\\Middleware\\\\InvokeDeferredCallbacks->handle(Object(Illuminate\\\\Http\\\\Request), Object(Closure))\n#37 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(137): Illuminate\\\\Pipeline\\\\Pipeline->Illuminate\\\\Pipeline\\\\{closure}(Object(Illuminate\\\\Http\\\\Request))\n#38 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Http/Kernel.php(175): Illuminate\\\\Pipeline\\\\Pipeline->then(Object(Closure))\n#39 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Http/Kernel.php(144): Illuminate\\\\Foundation\\\\Http\\\\Kernel->sendRequestThroughRouter(Object(Illuminate\\\\Http\\\\Request))\n#40 /home/jiminny/public/index.php(51): Illuminate\\\\Foundation\\\\Http\\\\Kernel->handle(Object(Illuminate\\\\Http\\\\Request))\n#41 {main}\n\"} {\"correlation_id\":\"d8709bb9-0f8c-4798-a8c7-e68e24ab6265\",\"trace_id\":\"c5bea1cc-53fe-4bda-a8e2-bec002030bc2\"}\n[2026-04-27 10:28:14] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage before starting command {\"command\":\"mailbox:batch:process\",\"memoryBeforeCommandInMb\":60.0,\"memoryPeakBeforeCommandInMb\":99.727} {\"correlation_id\":\"5fb3c8c8-3160-4964-ac56-02d6e2cb998c\",\"trace_id\":\"3a6d5e64-ecf2-4700-98cd-3edd56a1dc34\"}\n[2026-04-27 10:28:14] local.INFO: [EmailSchedule] STARTING batch process {\"host\":\"docker_lamp_1\"} {\"correlation_id\":\"5fb3c8c8-3160-4964-ac56-02d6e2cb998c\",\"trace_id\":\"3a6d5e64-ecf2-4700-98cd-3edd56a1dc34\"}\n[2026-04-27 10:28:14] local.INFO: Processing email batch 98446 for inbox 212 {\"correlation_id\":\"5fb3c8c8-3160-4964-ac56-02d6e2cb998c\",\"trace_id\":\"3a6d5e64-ecf2-4700-98cd-3edd56a1dc34\"}\n[2026-04-27 10:28:14] local.INFO: [SocialAccountService] Fetching token {\"socialAccountId\":1500,\"provider\":\"salesforce\"} {\"correlation_id\":\"5fb3c8c8-3160-4964-ac56-02d6e2cb998c\",\"trace_id\":\"3a6d5e64-ecf2-4700-98cd-3edd56a1dc34\"}\n[2026-04-27 10:28:14] local.INFO: [SocialAccountService] Token retrieved {\"socialAccountId\":1500,\"provider\":\"salesforce\"} {\"correlation_id\":\"5fb3c8c8-3160-4964-ac56-02d6e2cb998c\",\"trace_id\":\"3a6d5e64-ecf2-4700-98cd-3edd56a1dc34\"}\n[2026-04-27 10:28:14] local.INFO: [EncryptedTokenManager] Generating access token. {\"mode\":\"legacy\"} {\"correlation_id\":\"5fb3c8c8-3160-4964-ac56-02d6e2cb998c\",\"trace_id\":\"3a6d5e64-ecf2-4700-98cd-3edd56a1dc34\"}\n[2026-04-27 10:28:14] local.INFO: [CrmOwnerResolver] Integration owner matched as CRM Owner {\"crm_provider\":\"salesforce\",\"crm_owner\":143,\"team_id\":1} {\"correlation_id\":\"5fb3c8c8-3160-4964-ac56-02d6e2cb998c\",\"trace_id\":\"3a6d5e64-ecf2-4700-98cd-3edd56a1dc34\"}\n[2026-04-27 10:28:14] local.INFO: [SocialAccountService] Fetching token {\"socialAccountId\":1354,\"provider\":\"google\"} {\"correlation_id\":\"5fb3c8c8-3160-4964-ac56-02d6e2cb998c\",\"trace_id\":\"3a6d5e64-ecf2-4700-98cd-3edd56a1dc34\"}\n[2026-04-27 10:28:14] local.INFO: [SocialAccountService] Token retrieved {\"socialAccountId\":1354,\"provider\":\"google\"} {\"correlation_id\":\"5fb3c8c8-3160-4964-ac56-02d6e2cb998c\",\"trace_id\":\"3a6d5e64-ecf2-4700-98cd-3edd56a1dc34\"}\n[2026-04-27 10:28:14] local.INFO: [EncryptedTokenManager] Generating access token. {\"mode\":\"legacy\"} {\"correlation_id\":\"5fb3c8c8-3160-4964-ac56-02d6e2cb998c\",\"trace_id\":\"3a6d5e64-ecf2-4700-98cd-3edd56a1dc34\"}\n[2026-04-27 10:28:15] local.INFO: Processing an email from inbox batch {\"batch\":98446,\"inbox_id\":212,\"email\":\"lukas.kovalik@jiminny.com\",\"email_id\":\"19dce788ac964bbf\",\"from\":\"Nikolay Yankov <notifications@github.com>\",\"to\":\"\\\"jiminny/app\\\" <app@noreply.github.com>\",\"cc\":\"Push <push@noreply.github.com>\"} {\"correlation_id\":\"5fb3c8c8-3160-4964-ac56-02d6e2cb998c\",\"trace_id\":\"3a6d5e64-ecf2-4700-98cd-3edd56a1dc34\"}\n[2026-04-27 10:28:15] local.INFO: [SocialAccountService] Fetching token {\"socialAccountId\":1500,\"provider\":\"salesforce\"} {\"correlation_id\":\"5fb3c8c8-3160-4964-ac56-02d6e2cb998c\",\"trace_id\":\"3a6d5e64-ecf2-4700-98cd-3edd56a1dc34\"}\n[2026-04-27 10:28:15] local.INFO: [SocialAccountService] Token retrieved {\"socialAccountId\":1500,\"provider\":\"salesforce\"} {\"correlation_id\":\"5fb3c8c8-3160-4964-ac56-02d6e2cb998c\",\"trace_id\":\"3a6d5e64-ecf2-4700-98cd-3edd56a1dc34\"}\n[2026-04-27 10:28:15] local.INFO: [EncryptedTokenManager] Generating access token. {\"mode\":\"legacy\"} {\"correlation_id\":\"5fb3c8c8-3160-4964-ac56-02d6e2cb998c\",\"trace_id\":\"3a6d5e64-ecf2-4700-98cd-3edd56a1dc34\"}\n[2026-04-27 10:28:15] local.INFO: [CrmOwnerResolver] Integration owner matched as CRM Owner {\"crm_provider\":\"salesforce\",\"crm_owner\":143,\"team_id\":1} {\"correlation_id\":\"5fb3c8c8-3160-4964-ac56-02d6e2cb998c\",\"trace_id\":\"3a6d5e64-ecf2-4700-98cd-3edd56a1dc34\"}\n[2026-04-27 10:28:15] local.INFO: [EmailImport\\ParticipantsResolver] The sender email is blacklisted, skipping {\"email\":\"notifications@github.com\",\"inbox_id\":212,\"message_provider_id\":\"19dce788ac964bbf\",\"team_id\":1} {\"correlation_id\":\"5fb3c8c8-3160-4964-ac56-02d6e2cb998c\",\"trace_id\":\"3a6d5e64-ecf2-4700-98cd-3edd56a1dc34\"}\n[2026-04-27 10:28:15] local.INFO: [EmailImport\\ParticipantsValidator] Email participants are less than 2 {\"inbox_id\":212,\"message_provider_id\":\"19dce788ac964bbf\",\"message_id\":\"<jiminny/app/pull/11998/before/2b8a97d6be8b053441d5a845b72dd04803cbb53c/after/8a68a94d39cc76ae546e19f354e8b0dabd577314@github.com>\"} {\"correlation_id\":\"5fb3c8c8-3160-4964-ac56-02d6e2cb998c\",\"trace_id\":\"3a6d5e64-ecf2-4700-98cd-3edd56a1dc34\"}\n[2026-04-27 10:28:15] local.INFO: Processing an email from inbox batch {\"batch\":98446,\"inbox_id\":212,\"email\":\"lukas.kovalik@jiminny.com\",\"email_id\":\"19dce7883f2fea19\",\"from\":\"The Jiminny Team <no-reply@dev.jiminny.com>\",\"to\":\"lukas.kovalik@jiminny.com\",\"cc\":null} {\"correlation_id\":\"5fb3c8c8-3160-4964-ac56-02d6e2cb998c\",\"trace_id\":\"3a6d5e64-ecf2-4700-98cd-3edd56a1dc34\"}\n[2026-04-27 10:28:15] local.INFO: [SocialAccountService] Fetching token {\"socialAccountId\":1500,\"provider\":\"salesforce\"} {\"correlation_id\":\"5fb3c8c8-3160-4964-ac56-02d6e2cb998c\",\"trace_id\":\"3a6d5e64-ecf2-4700-98cd-3edd56a1dc34\"}\n[2026-04-27 10:28:15] local.INFO: [SocialAccountService] Token retrieved {\"socialAccountId\":1500,\"provider\":\"salesforce\"} {\"correlation_id\":\"5fb3c8c8-3160-4964-ac56-02d6e2cb998c\",\"trace_id\":\"3a6d5e64-ecf2-4700-98cd-3edd56a1dc34\"}\n[2026-04-27 10:28:15] local.INFO: [EncryptedTokenManager] Generating access token. {\"mode\":\"legacy\"} {\"correlation_id\":\"5fb3c8c8-3160-4964-ac56-02d6e2cb998c\",\"trace_id\":\"3a6d5e64-ecf2-4700-98cd-3edd56a1dc34\"}\n[2026-04-27 10:28:15] local.INFO: [CrmOwnerResolver] Integration owner matched as CRM Owner {\"crm_provider\":\"salesforce\",\"crm_owner\":143,\"team_id\":1} {\"correlation_id\":\"5fb3c8c8-3160-4964-ac56-02d6e2cb998c\",\"trace_id\":\"3a6d5e64-ecf2-4700-98cd-3edd56a1dc34\"}\n[2026-04-27 10:28:15] local.INFO: [EmailImport\\ParticipantsResolver] The sender email is blacklisted, skipping {\"email\":\"no-reply@dev.jiminny.com\",\"inbox_id\":212,\"message_provider_id\":\"19dce7883f2fea19\",\"team_id\":1} {\"correlation_id\":\"5fb3c8c8-3160-4964-ac56-02d6e2cb998c\",\"trace_id\":\"3a6d5e64-ecf2-4700-98cd-3edd56a1dc34\"}\n[2026-04-27 10:28:15] local.INFO: [EmailImport\\ParticipantsValidator] Email participants are less than 2 {\"inbox_id\":212,\"message_provider_id\":\"19dce7883f2fea19\",\"message_id\":\"<d70689e8-7b14-42ac-a3bf-5f1e81da943e@mtasv.net>\"} {\"correlation_id\":\"5fb3c8c8-3160-4964-ac56-02d6e2cb998c\",\"trace_id\":\"3a6d5e64-ecf2-4700-98cd-3edd56a1dc34\"}\n[2026-04-27 10:28:15] local.INFO: Processing an email from inbox batch {\"batch\":98446,\"inbox_id\":212,\"email\":\"lukas.kovalik@jiminny.com\",\"email_id\":\"19dce761cb049fba\",\"from\":\"The Jiminny Team <no-reply@dev.jiminny.com>\",\"to\":\"lukas.kovalik@jiminny.com\",\"cc\":null} {\"correlation_id\":\"5fb3c8c8-3160-4964-ac56-02d6e2cb998c\",\"trace_id\":\"3a6d5e64-ecf2-4700-98cd-3edd56a1dc34\"}\n[2026-04-27 10:28:15] local.INFO: [SocialAccountService] Fetching token {\"socialAccountId\":1500,\"provider\":\"salesforce\"} {\"correlation_id\":\"5fb3c8c8-3160-4964-ac56-02d6e2cb998c\",\"trace_id\":\"3a6d5e64-ecf2-4700-98cd-3edd56a1dc34\"}\n[2026-04-27 10:28:15] local.INFO: [SocialAccountService] Token retrieved {\"socialAccountId\":1500,\"provider\":\"salesforce\"} {\"correlation_id\":\"5fb3c8c8-3160-4964-ac56-02d6e2cb998c\",\"trace_id\":\"3a6d5e64-ecf2-4700-98cd-3edd56a1dc34\"}\n[2026-04-27 10:28:15] local.INFO: [EncryptedTokenManager] Generating access token. {\"mode\":\"legacy\"} {\"correlation_id\":\"5fb3c8c8-3160-4964-ac56-02d6e2cb998c\",\"trace_id\":\"3a6d5e64-ecf2-4700-98cd-3edd56a1dc34\"}\n[2026-04-27 10:28:15] local.INFO: [CrmOwnerResolver] Integration owner matched as CRM Owner {\"crm_provider\":\"salesforce\",\"crm_owner\":143,\"team_id\":1} {\"correlation_id\":\"5fb3c8c8-3160-4964-ac56-02d6e2cb998c\",\"trace_id\":\"3a6d5e64-ecf2-4700-98cd-3edd56a1dc34\"}\n[2026-04-27 10:28:15] local.INFO: [EmailImport\\ParticipantsResolver] The sender email is blacklisted, skipping {\"email\":\"no-reply@dev.jiminny.com\",\"inbox_id\":212,\"message_provider_id\":\"19dce761cb049fba\",\"team_id\":1} {\"correlation_id\":\"5fb3c8c8-3160-4964-ac56-02d6e2cb998c\",\"trace_id\":\"3a6d5e64-ecf2-4700-98cd-3edd56a1dc34\"}\n[2026-04-27 10:28:15] local.INFO: [EmailImport\\ParticipantsValidator] Email participants are less than 2 {\"inbox_id\":212,\"message_provider_id\":\"19dce761cb049fba\",\"message_id\":\"<017f0e66-9989-446e-9788-7ae83a99137e@mtasv.net>\"} {\"correlation_id\":\"5fb3c8c8-3160-4964-ac56-02d6e2cb998c\",\"trace_id\":\"3a6d5e64-ecf2-4700-98cd-3edd56a1dc34\"}\n[2026-04-27 10:28:15] local.INFO: Processing an email from inbox batch {\"batch\":98446,\"inbox_id\":212,\"email\":\"lukas.kovalik@jiminny.com\",\"email_id\":\"19dce74ed398d2c1\",\"from\":\"\\\"sonarqubecloud[bot]\\\" <notifications@github.com>\",\"to\":\"\\\"jiminny/app\\\" <app@noreply.github.com>\",\"cc\":\"Lukas Kovalik <kovaliklukas@gmail.com>, Author <author@noreply.github.com>\"} {\"correlation_id\":\"5fb3c8c8-3160-4964-ac56-02d6e2cb998c\",\"trace_id\":\"3a6d5e64-ecf2-4700-98cd-3edd56a1dc34\"}\n[2026-04-27 10:28:15] local.INFO: [SocialAccountService] Fetching token {\"socialAccountId\":1500,\"provider\":\"salesforce\"} {\"correlation_id\":\"5fb3c8c8-3160-4964-ac56-02d6e2cb998c\",\"trace_id\":\"3a6d5e64-ecf2-4700-98cd-3edd56a1dc34\"}\n[2026-04-27 10:28:15] local.INFO: [SocialAccountService] Token retrieved {\"socialAccountId\":1500,\"provider\":\"salesforce\"} {\"correlation_id\":\"5fb3c8c8-3160-4964-ac56-02d6e2cb998c\",\"trace_id\":\"3a6d5e64-ecf2-4700-98cd-3edd56a1dc34\"}\n[2026-04-27 10:28:15] local.INFO: [EncryptedTokenManager] Generating access token. {\"mode\":\"legacy\"} {\"correlation_id\":\"5fb3c8c8-3160-4964-ac56-02d6e2cb998c\",\"trace_id\":\"3a6d5e64-ecf2-4700-98cd-3edd56a1dc34\"}\n[2026-04-27 10:28:15] local.INFO: [CrmOwnerResolver] Integration owner matched as CRM Owner {\"crm_provider\":\"salesforce\",\"crm_owner\":143,\"team_id\":1} {\"correlation_id\":\"5fb3c8c8-3160-4964-ac56-02d6e2cb998c\",\"trace_id\":\"3a6d5e64-ecf2-4700-98cd-3edd56a1dc34\"}\n[2026-04-27 10:28:15] local.INFO: [EmailImport\\ParticipantsResolver] The sender email is blacklisted, skipping {\"email\":\"notifications@github.com\",\"inbox_id\":212,\"message_provider_id\":\"19dce74ed398d2c1\",\"team_id\":1} {\"correlation_id\":\"5fb3c8c8-3160-4964-ac56-02d6e2cb998c\",\"trace_id\":\"3a6d5e64-ecf2-4700-98cd-3edd56a1dc34\"}\n[2026-04-27 10:28:15] local.INFO: [EmailImport\\ParticipantsValidator] Email participants are less than 2 {\"inbox_id\":212,\"message_provider_id\":\"19dce74ed398d2c1\",\"message_id\":\"<jiminny/app/pull/12016/c4326142661@github.com>\"} {\"correlation_id\":\"5fb3c8c8-3160-4964-ac56-02d6e2cb998c\",\"trace_id\":\"3a6d5e64-ecf2-4700-98cd-3edd56a1dc34\"}\n[2026-04-27 10:28:15] local.INFO: Processing an email from inbox batch {\"batch\":98446,\"inbox_id\":212,\"email\":\"lukas.kovalik@jiminny.com\",\"email_id\":\"19dce713dbecf41f\",\"from\":\"\\\"sonarqubecloud[bot]\\\" <notifications@github.com>\",\"to\":\"\\\"jiminny/infrastructure\\\" <infrastructure@noreply.github.com>\",\"cc\":\"Subscribed <subscribed@noreply.github.com>\"} {\"correlation_id\":\"5fb3c8c8-3160-4964-ac56-02d6e2cb998c\",\"trace_id\":\"3a6d5e64-ecf2-4700-98cd-3edd56a1dc34\"}\n[2026-04-27 10:28:15] local.INFO: [SocialAccountService] Fetching token {\"socialAccountId\":1500,\"provider\":\"salesforce\"} {\"correlation_id\":\"5fb3c8c8-3160-4964-ac56-02d6e2cb998c\",\"trace_id\":\"3a6d5e64-ecf2-4700-98cd-3edd56a1dc34\"}\n[2026-04-27 10:28:15] local.INFO: [SocialAccountService] Token retrieved {\"socialAccountId\":1500,\"provider\":\"salesforce\"} {\"correlation_id\":\"5fb3c8c8-3160-4964-ac56-02d6e2cb998c\",\"trace_id\":\"3a6d5e64-ecf2-4700-98cd-3edd56a1dc34\"}\n[2026-04-27 10:28:15] local.INFO: [EncryptedTokenManager] Generating access token. {\"mode\":\"legacy\"} {\"correlation_id\":\"5fb3c8c8-3160-4964-ac56-02d6e2cb998c\",\"trace_id\":\"3a6d5e64-ecf2-4700-98cd-3edd56a1dc34\"}\n[2026-04-27 10:28:15] local.INFO: [CrmOwnerResolver] Integration owner matched as CRM Owner {\"crm_provider\":\"salesforce\",\"crm_owner\":143,\"team_id\":1} {\"correlation_id\":\"5fb3c8c8-3160-4964-ac56-02d6e2cb998c\",\"trace_id\":\"3a6d5e64-ecf2-4700-98cd-3edd56a1dc34\"}\n[2026-04-27 10:28:15] local.INFO: [EmailImport\\ParticipantsResolver] The sender email is blacklisted, skipping {\"email\":\"notifications@github.com\",\"inbox_id\":212,\"message_provider_id\":\"19dce713dbecf41f\",\"team_id\":1} {\"correlation_id\":\"5fb3c8c8-3160-4964-ac56-02d6e2cb998c\",\"trace_id\":\"3a6d5e64-ecf2-4700-98cd-3edd56a1dc34\"}\n[2026-04-27 10:28:15] local.INFO: [EmailImport\\ParticipantsValidator] Email participants are less than 2 {\"inbox_id\":212,\"message_provider_id\":\"19dce713dbecf41f\",\"message_id\":\"<jiminny/infrastructure/pull/725/c4326108250@github.com>\"} {\"correlation_id\":\"5fb3c8c8-3160-4964-ac56-02d6e2cb998c\",\"trace_id\":\"3a6d5e64-ecf2-4700-98cd-3edd56a1dc34\"}\n[2026-04-27 10:28:15] local.INFO: Processing an email from inbox batch {\"batch\":98446,\"inbox_id\":212,\"email\":\"lukas.kovalik@jiminny.com\",\"email_id\":\"19dce708ed8b5acb\",\"from\":\"Veselin Kulov <notifications@github.com>\",\"to\":\"\\\"jiminny/infrastructure\\\" <infrastructure@noreply.github.com>\",\"cc\":\"Push <push@noreply.github.com>\"} {\"correlation_id\":\"5fb3c8c8-3160-4964-ac56-02d6e2cb998c\",\"trace_id\":\"3a6d5e64-ecf2-4700-98cd-3edd56a1dc34\"}\n[2026-04-27 10:28:15] local.INFO: [SocialAccountService] Fetching token {\"socialAccountId\":1500,\"provider\":\"salesforce\"} {\"correlation_id\":\"5fb3c8c8-3160-4964-ac56-02d6e2cb998c\",\"trace_id\":\"3a6d5e64-ecf2-4700-98cd-3edd56a1dc34\"}\n[2026-04-27 10:28:15] local.INFO: [SocialAccountService] Token retrieved {\"socialAccountId\":1500,\"provider\":\"salesforce\"} {\"correlation_id\":\"5fb3c8c8-3160-4964-ac56-02d6e2cb998c\",\"trace_id\":\"3a6d5e64-ecf2-4700-98cd-3edd56a1dc34\"}\n[2026-04-27 10:28:15] local.INFO: [EncryptedTokenManager] Generating access token. {\"mode\":\"legacy\"} {\"correlation_id\":\"5fb3c8c8-3160-4964-ac56-02d6e2cb998c\",\"trace_id\":\"3a6d5e64-ecf2-4700-98cd-3edd56a1dc34\"}\n[2026-04-27 10:28:15] local.INFO: [CrmOwnerResolver] Integration owner matched as CRM Owner {\"crm_provider\":\"salesforce\",\"crm_owner\":143,\"team_id\":1} {\"correlation_id\":\"5fb3c8c8-3160-4964-ac56-02d6e2cb998c\",\"trace_id\":\"3a6d5e64-ecf2-4700-98cd-3edd56a1dc34\"}\n[2026-04-27 10:28:15] local.INFO: [EmailImport\\ParticipantsResolver] The sender email is blacklisted, skipping {\"email\":\"notifications@github.com\",\"inbox_id\":212,\"message_provider_id\":\"19dce708ed8b5acb\",\"team_id\":1} {\"correlation_id\":\"5fb3c8c8-3160-4964-ac56-02d6e2cb998c\",\"trace_id\":\"3a6d5e64-ecf2-4700-98cd-3edd56a1dc34\"}\n[2026-04-27 10:28:15] local.INFO: [EmailImport\\ParticipantsValidator] Email participants are less than 2 {\"inbox_id\":212,\"message_provider_id\":\"19dce708ed8b5acb\",\"message_id\":\"<jiminny/infrastructure/pull/725/before/186952cc584428fcca17aa364f6cc535d65e804a/after/078246a06b25fb93ad61c7f14472ecb755483f4d@github.com>\"} {\"correlation_id\":\"5fb3c8c8-3160-4964-ac56-02d6e2cb998c\",\"trace_id\":\"3a6d5e64-ecf2-4700-98cd-3edd56a1dc34\"}\n[2026-04-27 10:28:15] local.INFO: Processing an email from inbox batch {\"batch\":98446,\"inbox_id\":212,\"email\":\"lukas.kovalik@jiminny.com\",\"email_id\":\"19dce6eea657f2fa\",\"from\":\"\\\"sonarqubecloud[bot]\\\" <notifications@github.com>\",\"to\":\"\\\"jiminny/app\\\" <app@noreply.github.com>\",\"cc\":\"Subscribed <subscribed@noreply.github.com>\"} {\"correlation_id\":\"5fb3c8c8-3160-4964-ac56-02d6e2cb998c\",\"trace_id\":\"3a6d5e64-ecf2-4700-98cd-3edd56a1dc34\"}\n[2026-04-27 10:28:15] local.INFO: [SocialAccountService] Fetching token {\"socialAccountId\":1500,\"provider\":\"salesforce\"} {\"correlation_id\":\"5fb3c8c8-3160-4964-ac56-02d6e2cb998c\",\"trace_id\":\"3a6d5e64-ecf2-4700-98cd-3edd56a1dc34\"}\n[2026-04-27 10:28:15] local.INFO: [SocialAccountService] Token retrieved {\"socialAccountId\":1500,\"provider\":\"salesforce\"} {\"correlation_id\":\"5fb3c8c8-3160-4964-ac56-02d6e2cb998c\",\"trace_id\":\"3a6d5e64-ecf2-4700-98cd-3edd56a1dc34\"}\n[2026-04-27 10:28:15] local.INFO: [EncryptedTokenManager] Generating access token. {\"mode\":\"legacy\"} {\"correlation_id\":\"5fb3c8c8-3160-4964-ac56-02d6e2cb998c\",\"trace_id\":\"3a6d5e64-ecf2-4700-98cd-3edd56a1dc34\"}\n[2026-04-27 10:28:15] local.INFO: [CrmOwnerResolver] Integration owner matched as CRM Owner {\"crm_provider\":\"salesforce\",\"crm_owner\":143,\"team_id\":1} {\"correlation_id\":\"5fb3c8c8-3160-4964-ac56-02d6e2cb998c\",\"trace_id\":\"3a6d5e64-ecf2-4700-98cd-3edd56a1dc34\"}\n[2026-04-27 10:28:15] local.INFO: [EmailImport\\ParticipantsResolver] The sender email is blacklisted, skipping {\"email\":\"notifications@github.com\",\"inbox_id\":212,\"message_provider_id\":\"19dce6eea657f2fa\",\"team_id\":1} {\"correlation_id\":\"5fb3c8c8-3160-4964-ac56-02d6e2cb998c\",\"trace_id\":\"3a6d5e64-ecf2-4700-98cd-3edd56a1dc34\"}\n[2026-04-27 10:28:15] local.INFO: [EmailImport\\ParticipantsValidator] Email participants are less than 2 {\"inbox_id\":212,\"message_provider_id\":\"19dce6eea657f2fa\",\"message_id\":\"<jiminny/app/pull/11998/c4326088534@github.com>\"} {\"correlation_id\":\"5fb3c8c8-3160-4964-ac56-02d6e2cb998c\",\"trace_id\":\"3a6d5e64-ecf2-4700-98cd-3edd56a1dc34\"}\n[2026-04-27 10:28:15] local.INFO: Deleting successfully processed batch 98446 for inbox 212 {\"correlation_id\":\"5fb3c8c8-3160-4964-ac56-02d6e2cb998c\",\"trace_id\":\"3a6d5e64-ecf2-4700-98cd-3edd56a1dc34\"}\n[2026-04-27 10:28:15] local.INFO: [EmailSchedule] FINISHED batch process {\"host\":\"docker_lamp_1\",\"processed\":1} {\"correlation_id\":\"5fb3c8c8-3160-4964-ac56-02d6e2cb998c\",\"trace_id\":\"3a6d5e64-ecf2-4700-98cd-3edd56a1dc34\"}\n[2026-04-27 10:28:15] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage for command {\"command\":\"mailbox:batch:process\",\"memoryBeforeCommandInMb\":60.0,\"memoryAfterCommandInMB\":62.0,\"memoryPeakBeforeCommandInMb\":99.727,\"memoryPeakAfterCommandInMB\":99.727} {\"correlation_id\":\"5fb3c8c8-3160-4964-ac56-02d6e2cb998c\",\"trace_id\":\"3a6d5e64-ecf2-4700-98cd-3edd56a1dc34\"}\n[2026-04-27 10:28:16] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage before starting command {\"command\":\"conference:monitor:count\",\"memoryBeforeCommandInMb\":60.0,\"memoryPeakBeforeCommandInMb\":99.727} {\"correlation_id\":\"ef5b3e1a-a06a-4527-9120-b2adfe4e1846\",\"trace_id\":\"d932d64a-3f63-4aca-8abf-bb13e0892cba\"}\n[2026-04-27 10:28:16] local.INFO: Running conference:monitor:count command for activities in (2026-04-27 10:26:00, 2026-04-27 10:28:00] {\"correlation_id\":\"ef5b3e1a-a06a-4527-9120-b2adfe4e1846\",\"trace_id\":\"d932d64a-3f63-4aca-8abf-bb13e0892cba\"}\n[2026-04-27 10:28:16] local.INFO: [conference:monitor:count] No activities found in (2026-04-27 10:26:00, 2026-04-27 10:28:00] {\"correlation_id\":\"ef5b3e1a-a06a-4527-9120-b2adfe4e1846\",\"trace_id\":\"d932d64a-3f63-4aca-8abf-bb13e0892cba\"}\n[2026-04-27 10:28:16] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage for command {\"command\":\"conference:monitor:count\",\"memoryBeforeCommandInMb\":60.0,\"memoryAfterCommandInMB\":62.0,\"memoryPeakBeforeCommandInMb\":99.727,\"memoryPeakAfterCommandInMB\":99.727} {\"correlation_id\":\"ef5b3e1a-a06a-4527-9120-b2adfe4e1846\",\"trace_id\":\"d932d64a-3f63-4aca-8abf-bb13e0892cba\"}\n[2026-04-27 10:28:17] local.ERROR: Target class [Jiminny\\Repositories\\AjReportsRepository] does not exist. {\"exception\":\"[object] (Illuminate\\\\Contracts\\\\Container\\\\BindingResolutionException(code: 0): Target class [Jiminny\\\\Repositories\\\\AjReportsRepository] does not exist. at /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php:1124)\n[stacktrace]\n#0 /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php(933): Illuminate\\\\Container\\\\Container->build('Jiminny\\\\\\\\Reposit...')\n#1 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Application.php(1078): Illuminate\\\\Container\\\\Container->resolve('Jiminny\\\\\\\\Reposit...', Array, true)\n#2 /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php(864): Illuminate\\\\Foundation\\\\Application->resolve('Jiminny\\\\\\\\Reposit...', Array)\n#3 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Application.php(1058): Illuminate\\\\Container\\\\Container->make('Jiminny\\\\\\\\Reposit...', Array)\n#4 /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php(1336): Illuminate\\\\Foundation\\\\Application->make('Jiminny\\\\\\\\Reposit...')\n#5 /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php(1237): Illuminate\\\\Container\\\\Container->resolveClass(Object(ReflectionParameter))\n#6 /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php(1162): Illuminate\\\\Container\\\\Container->resolveDependencies(Array)\n#7 /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php(933): Illuminate\\\\Container\\\\Container->build('Jiminny\\\\\\\\Http\\\\\\\\Co...')\n#8 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Application.php(1078): Illuminate\\\\Container\\\\Container->resolve('Jiminny\\\\\\\\Http\\\\\\\\Co...', Array, true)\n#9 /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php(864): Illuminate\\\\Foundation\\\\Application->resolve('Jiminny\\\\\\\\Http\\\\\\\\Co...', Array)\n#10 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Application.php(1058): Illuminate\\\\Container\\\\Container->make('Jiminny\\\\\\\\Http\\\\\\\\Co...', Array)\n#11 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Route.php(286): Illuminate\\\\Foundation\\\\Application->make('Jiminny\\\\\\\\Http\\\\\\\\Co...')\n#12 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Route.php(1133): Illuminate\\\\Routing\\\\Route->getController()\n#13 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Route.php(1062): Illuminate\\\\Routing\\\\Route->controllerMiddleware()\n#14 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Router.php(834): Illuminate\\\\Routing\\\\Route->gatherMiddleware()\n#15 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Router.php(816): Illuminate\\\\Routing\\\\Router->gatherRouteMiddleware(Object(Illuminate\\\\Routing\\\\Route))\n#16 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Router.php(800): Illuminate\\\\Routing\\\\Router->runRouteWithinStack(Object(Illuminate\\\\Routing\\\\Route), Object(Illuminate\\\\Http\\\\Request))\n#17 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Router.php(764): Illuminate\\\\Routing\\\\Router->runRoute(Object(Illuminate\\\\Http\\\\Request), Object(Illuminate\\\\Routing\\\\Route))\n#18 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Router.php(753): Illuminate\\\\Routing\\\\Router->dispatchToRoute(Object(Illuminate\\\\Http\\\\Request))\n#19 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Http/Kernel.php(200): Illuminate\\\\Routing\\\\Router->dispatch(Object(Illuminate\\\\Http\\\\Request))\n#20 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(180): Illuminate\\\\Foundation\\\\Http\\\\Kernel->Illuminate\\\\Foundation\\\\Http\\\\{closure}(Object(Illuminate\\\\Http\\\\Request))\n#21 /home/jiminny/vendor/barryvdh/laravel-debugbar/src/Middleware/InjectDebugbar.php(59): Illuminate\\\\Pipeline\\\\Pipeline->Illuminate\\\\Pipeline\\\\{closure}(Object(Illuminate\\\\Http\\\\Request))\n#22 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(219): Barryvdh\\\\Debugbar\\\\Middleware\\\\InjectDebugbar->handle(Object(Illuminate\\\\Http\\\\Request), Object(Closure))\n#23 /home/jiminny/vendor/laravel/framework/src/Illuminate/Http/Middleware/HandleCors.php(74): Illuminate\\\\Pipeline\\\\Pipeline->Illuminate\\\\Pipeline\\\\{closure}(Object(Illuminate\\\\Http\\\\Request))\n#24 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(219): Illuminate\\\\Http\\\\Middleware\\\\HandleCors->handle(Object(Illuminate\\\\Http\\\\Request), Object(Closure))\n#25 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Http/Middleware/TransformsRequest.php(21): Illuminate\\\\Pipeline\\\\Pipeline->Illuminate\\\\Pipeline\\\\{closure}(Object(Illuminate\\\\Http\\\\Request))\n#26 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Http/Middleware/TrimStrings.php(51): Illuminate\\\\Foundation\\\\Http\\\\Middleware\\\\TransformsRequest->handle(Object(Illuminate\\\\Http\\\\Request), Object(Closure))\n#27 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(219): Illuminate\\\\Foundation\\\\Http\\\\Middleware\\\\TrimStrings->handle(Object(Illuminate\\\\Http\\\\Request), Object(Closure))\n#28 /home/jiminny/vendor/bepsvpt/secure-headers/src/SecureHeadersMiddleware.php(18): Illuminate\\\\Pipeline\\\\Pipeline->Illuminate\\\\Pipeline\\\\{closure}(Object(Illuminate\\\\Http\\\\Request))\n#29 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(219): Bepsvpt\\\\SecureHeaders\\\\SecureHeadersMiddleware->handle(Object(Illuminate\\\\Http\\\\Request), Object(Closure))\n#30 /home/jiminny/app/Http/Middleware/SentryContext.php(60): Illuminate\\\\Pipeline\\\\Pipeline->Illuminate\\\\Pipeline\\\\{closure}(Object(Illuminate\\\\Http\\\\Request))\n#31 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(219): Jiminny\\\\Http\\\\Middleware\\\\SentryContext->handle(Object(Illuminate\\\\Http\\\\Request), Object(Closure))\n#32 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Http/Middleware/PreventRequestsDuringMaintenance.php(109): Illuminate\\\\Pipeline\\\\Pipeline->Illuminate\\\\Pipeline\\\\{closure}(Object(Illuminate\\\\Http\\\\Request))\n#33 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(219): Illuminate\\\\Foundation\\\\Http\\\\Middleware\\\\PreventRequestsDuringMaintenance->handle(Object(Illuminate\\\\Http\\\\Request), Object(Closure))\n#34 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Http/Middleware/InvokeDeferredCallbacks.php(22): Illuminate\\\\Pipeline\\\\Pipeline->Illuminate\\\\Pipeline\\\\{closure}(Object(Illuminate\\\\Http\\\\Request))\n#35 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(219): Illuminate\\\\Foundation\\\\Http\\\\Middleware\\\\InvokeDeferredCallbacks->handle(Object(Illuminate\\\\Http\\\\Request), Object(Closure))\n#36 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(137): Illuminate\\\\Pipeline\\\\Pipeline->Illuminate\\\\Pipeline\\\\{closure}(Object(Illuminate\\\\Http\\\\Request))\n#37 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Http/Kernel.php(175): Illuminate\\\\Pipeline\\\\Pipeline->then(Object(Closure))\n#38 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Http/Kernel.php(144): Illuminate\\\\Foundation\\\\Http\\\\Kernel->sendRequestThroughRouter(Object(Illuminate\\\\Http\\\\Request))\n#39 /home/jiminny/public/index.php(51): Illuminate\\\\Foundation\\\\Http\\\\Kernel->handle(Object(Illuminate\\\\Http\\\\Request))\n#40 {main}\n\n[previous exception] [object] (ReflectionException(code: -1): Class \\\"Jiminny\\\\Repositories\\\\AjReportsRepository\\\" does not exist at /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php:1122)\n[stacktrace]\n#0 /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php(1122): ReflectionClass->__construct('Jiminny\\\\\\\\Reposit...')\n#1 /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php(933): Illuminate\\\\Container\\\\Container->build('Jiminny\\\\\\\\Reposit...')\n#2 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Application.php(1078): Illuminate\\\\Container\\\\Container->resolve('Jiminny\\\\\\\\Reposit...', Array, true)\n#3 /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php(864): Illuminate\\\\Foundation\\\\Application->resolve('Jiminny\\\\\\\\Reposit...', Array)\n#4 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Application.php(1058): Illuminate\\\\Container\\\\Container->make('Jiminny\\\\\\\\Reposit...', Array)\n#5 /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php(1336): Illuminate\\\\Foundation\\\\Application->make('Jiminny\\\\\\\\Reposit...')\n#6 /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php(1237): Illuminate\\\\Container\\\\Container->resolveClass(Object(ReflectionParameter))\n#7 /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php(1162): Illuminate\\\\Container\\\\Container->resolveDependencies(Array)\n#8 /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php(933): Illuminate\\\\Container\\\\Container->build('Jiminny\\\\\\\\Http\\\\\\\\Co...')\n#9 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Application.php(1078): Illuminate\\\\Container\\\\Container->resolve('Jiminny\\\\\\\\Http\\\\\\\\Co...', Array, true)\n#10 /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php(864): Illuminate\\\\Foundation\\\\Application->resolve('Jiminny\\\\\\\\Http\\\\\\\\Co...', Array)\n#11 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Application.php(1058): Illuminate\\\\Container\\\\Container->make('Jiminny\\\\\\\\Http\\\\\\\\Co...', Array)\n#12 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Route.php(286): Illuminate\\\\Foundation\\\\Application->make('Jiminny\\\\\\\\Http\\\\\\\\Co...')\n#13 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Route.php(1133): Illuminate\\\\Routing\\\\Route->getController()\n#14 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Route.php(1062): Illuminate\\\\Routing\\\\Route->controllerMiddleware()\n#15 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Router.php(834): Illuminate\\\\Routing\\\\Route->gatherMiddleware()\n#16 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Router.php(816): Illuminate\\\\Routing\\\\Router->gatherRouteMiddleware(Object(Illuminate\\\\Routing\\\\Route))\n#17 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Router.php(800): Illuminate\\\\Routing\\\\Router->runRouteWithinStack(Object(Illuminate\\\\Routing\\\\Route), Object(Illuminate\\\\Http\\\\Request))\n#18 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Router.php(764): Illuminate\\\\Routing\\\\Router->runRoute(Object(Illuminate\\\\Http\\\\Request), Object(Illuminate\\\\Routing\\\\Route))\n#19 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Router.php(753): Illuminate\\\\Routing\\\\Router->dispatchToRoute(Object(Illuminate\\\\Http\\\\Request))\n#20 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Http/Kernel.php(200): Illuminate\\\\Routing\\\\Router->dispatch(Object(Illuminate\\\\Http\\\\Request))\n#21 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(180): Illuminate\\\\Foundation\\\\Http\\\\Kernel->Illuminate\\\\Foundation\\\\Http\\\\{closure}(Object(Illuminate\\\\Http\\\\Request))\n#22 /home/jiminny/vendor/barryvdh/laravel-debugbar/src/Middleware/InjectDebugbar.php(59): Illuminate\\\\Pipeline\\\\Pipeline->Illuminate\\\\Pipeline\\\\{closure}(Object(Illuminate\\\\Http\\\\Request))\n#23 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(219): Barryvdh\\\\Debugbar\\\\Middleware\\\\InjectDebugbar->handle(Object(Illuminate\\\\Http\\\\Request), Object(Closure))\n#24 /home/jiminny/vendor/laravel/framework/src/Illuminate/Http/Middleware/HandleCors.php(74): Illuminate\\\\Pipeline\\\\Pipeline->Illuminate\\\\Pipeline\\\\{closure}(Object(Illuminate\\\\Http\\\\Request))\n#25 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(219): Illuminate\\\\Http\\\\Middleware\\\\HandleCors->handle(Object(Illuminate\\\\Http\\\\Request), Object(Closure))\n#26 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Http/Middleware/TransformsRequest.php(21): Illuminate\\\\Pipeline\\\\Pipeline->Illuminate\\\\Pipeline\\\\{closure}(Object(Illuminate\\\\Http\\\\Request))\n#27 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Http/Middleware/TrimStrings.php(51): Illuminate\\\\Foundation\\\\Http\\\\Middleware\\\\TransformsRequest->handle(Object(Illuminate\\\\Http\\\\Request), Object(Closure))\n#28 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(219): Illuminate\\\\Foundation\\\\Http\\\\Middleware\\\\TrimStrings->handle(Object(Illuminate\\\\Http\\\\Request), Object(Closure))\n#29 /home/jiminny/vendor/bepsvpt/secure-headers/src/SecureHeadersMiddleware.php(18): Illuminate\\\\Pipeline\\\\Pipeline->Illuminate\\\\Pipeline\\\\{closure}(Object(Illuminate\\\\Http\\\\Request))\n#30 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(219): Bepsvpt\\\\SecureHeaders\\\\SecureHeadersMiddleware->handle(Object(Illuminate\\\\Http\\\\Request), Object(Closure))\n#31 /home/jiminny/app/Http/Middleware/SentryContext.php(60): Illuminate\\\\Pipeline\\\\Pipeline->Illuminate\\\\Pipeline\\\\{closure}(Object(Illuminate\\\\Http\\\\Request))\n#32 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(219): Jiminny\\\\Http\\\\Middleware\\\\SentryContext->handle(Object(Illuminate\\\\Http\\\\Request), Object(Closure))\n#33 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Http/Middleware/PreventRequestsDuringMaintenance.php(109): Illuminate\\\\Pipeline\\\\Pipeline->Illuminate\\\\Pipeline\\\\{closure}(Object(Illuminate\\\\Http\\\\Request))\n#34 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(219): Illuminate\\\\Foundation\\\\Http\\\\Middleware\\\\PreventRequestsDuringMaintenance->handle(Object(Illuminate\\\\Http\\\\Request), Object(Closure))\n#35 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Http/Middleware/InvokeDeferredCallbacks.php(22): Illuminate\\\\Pipeline\\\\Pipeline->Illuminate\\\\Pipeline\\\\{closure}(Object(Illuminate\\\\Http\\\\Request))\n#36 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(219): Illuminate\\\\Foundation\\\\Http\\\\Middleware\\\\InvokeDeferredCallbacks->handle(Object(Illuminate\\\\Http\\\\Request), Object(Closure))\n#37 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(137): Illuminate\\\\Pipeline\\\\Pipeline->Illuminate\\\\Pipeline\\\\{closure}(Object(Illuminate\\\\Http\\\\Request))\n#38 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Http/Kernel.php(175): Illuminate\\\\Pipeline\\\\Pipeline->then(Object(Closure))\n#39 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Http/Kernel.php(144): Illuminate\\\\Foundation\\\\Http\\\\Kernel->sendRequestThroughRouter(Object(Illuminate\\\\Http\\\\Request))\n#40 /home/jiminny/public/index.php(51): Illuminate\\\\Foundation\\\\Http\\\\Kernel->handle(Object(Illuminate\\\\Http\\\\Request))\n#41 {main}\n\"} {\"correlation_id\":\"46b082fc-5a28-4cc4-8bd1-c670f8662e7f\",\"trace_id\":\"8ae7dc26-61c7-42e4-8617-0b85f53e54c1\"}\n[2026-04-27 10:28:19] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage before starting command {\"command\":\"calendar:sync\",\"memoryBeforeCommandInMb\":60.0,\"memoryPeakBeforeCommandInMb\":99.727} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:19] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage before starting command {\"command\":\"mailbox:batch:retry-failed\",\"memoryBeforeCommandInMb\":60.0,\"memoryPeakBeforeCommandInMb\":99.727} {\"correlation_id\":\"e155562a-84a1-45f5-8897-1779f96886f6\",\"trace_id\":\"12141267-1032-479b-aab5-e17e133007d9\"}\n[2026-04-27 10:28:19] local.NOTICE: Calendar sync start {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:19] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage for command {\"command\":\"mailbox:batch:retry-failed\",\"memoryBeforeCommandInMb\":60.0,\"memoryAfterCommandInMB\":62.0,\"memoryPeakBeforeCommandInMb\":99.727,\"memoryPeakAfterCommandInMB\":99.727} {\"correlation_id\":\"e155562a-84a1-45f5-8897-1779f96886f6\",\"trace_id\":\"12141267-1032-479b-aab5-e17e133007d9\"}\n[2026-04-27 10:28:19] local.INFO: [SocialAccountService] Fetching token {\"socialAccountId\":1393,\"provider\":\"google\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:19] local.INFO: [SocialAccountService] Token needs refreshing {\"socialAccountId\":1393,\"provider\":\"google\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:19] local.INFO: [EncryptedTokenManager] Generating access token. {\"mode\":\"legacy\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:19] local.INFO: [SocialAccountService] Refreshing token from provider {\"socialAccountId\":1393,\"provider\":\"google\",\"refreshToken\":\"5aa7e2d96b53201cd16fca5d2e4ef3ad03320971fc064781d18aee3ae7b99fbf\",\"state\":\"full-refresh\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:20] local.ERROR: [SocialAccountService] Failed to refresh token {\"socialAccountId\":1393,\"provider\":\"google\",\"responseBody\":{\"error\":\"invalid_grant\",\"error_description\":\"Account has been deleted\"}} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:20] local.INFO: [SocialAccountObserver] Saving model {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:20] local.ERROR: [SocialAccountService] Failed to refresh token {\"socialAccountId\":1393,\"provider\":\"google\",\"reason\":\"Flow refresh required.\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:20] local.INFO: [SocialAccountService] Fetching token {\"socialAccountId\":1387,\"provider\":\"google\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:20] local.INFO: [SocialAccountService] Token needs refreshing {\"socialAccountId\":1387,\"provider\":\"google\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:20] local.INFO: [EncryptedTokenManager] Generating access token. {\"mode\":\"legacy\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:20] local.INFO: [SocialAccountService] Refreshing token from provider {\"socialAccountId\":1387,\"provider\":\"google\",\"refreshToken\":\"8157ac6de94842937194009e9c50e459253600f799dacf6a40755ffdbeb5bba6\",\"state\":\"full-refresh\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:21] local.ERROR: [SocialAccountService] Failed to refresh token {\"socialAccountId\":1387,\"provider\":\"google\",\"responseBody\":{\"error\":\"invalid_grant\",\"error_description\":\"Account has been deleted\"}} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:21] local.INFO: [SocialAccountObserver] Saving model {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:21] local.ERROR: [SocialAccountService] Failed to refresh token {\"socialAccountId\":1387,\"provider\":\"google\",\"reason\":\"Flow refresh required.\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:21] local.INFO: [SocialAccountService] Fetching token {\"socialAccountId\":1348,\"provider\":\"google\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:21] local.INFO: [SocialAccountService] Token needs refreshing {\"socialAccountId\":1348,\"provider\":\"google\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:21] local.INFO: [EncryptedTokenManager] Generating access token. {\"mode\":\"legacy\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:21] local.INFO: [SocialAccountService] Refreshing token from provider {\"socialAccountId\":1348,\"provider\":\"google\",\"refreshToken\":\"9e7d13d3032d0cb1b79d8e95aef01383e8e91eb52ff8ee960c8a0b6b95cd8c73\",\"state\":\"full-refresh\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:21] local.ERROR: [SocialAccountService] Failed to refresh token {\"socialAccountId\":1348,\"provider\":\"google\",\"responseBody\":{\"error\":\"invalid_grant\",\"error_description\":\"Bad Request\"}} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:21] local.INFO: [SocialAccountObserver] Saving model {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:21] local.ERROR: [SocialAccountService] Failed to refresh token {\"socialAccountId\":1348,\"provider\":\"google\",\"reason\":\"Flow refresh required.\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:21] local.INFO: [SocialAccountService] Fetching token {\"socialAccountId\":1361,\"provider\":\"google\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:21] local.INFO: [SocialAccountService] Token needs refreshing {\"socialAccountId\":1361,\"provider\":\"google\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:21] local.INFO: [EncryptedTokenManager] Generating access token. {\"mode\":\"legacy\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:21] local.INFO: [SocialAccountService] Refreshing token from provider {\"socialAccountId\":1361,\"provider\":\"google\",\"refreshToken\":\"6c843da199c2b9907445329304fcc4ec5057a4ee748d8299641764395c08e1fd\",\"state\":\"full-refresh\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:22] local.ERROR: [SocialAccountService] Failed to refresh token {\"socialAccountId\":1361,\"provider\":\"google\",\"responseBody\":{\"error\":\"invalid_grant\",\"error_description\":\"Account has been deleted\"}} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:22] local.INFO: [SocialAccountObserver] Saving model {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:22] local.ERROR: [SocialAccountService] Failed to refresh token {\"socialAccountId\":1361,\"provider\":\"google\",\"reason\":\"Flow refresh required.\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:22] local.INFO: [SocialAccountService] Fetching token {\"socialAccountId\":1310,\"provider\":\"google\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:22] local.INFO: [SocialAccountService] Token needs refreshing {\"socialAccountId\":1310,\"provider\":\"google\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:22] local.INFO: [EncryptedTokenManager] Generating access token. {\"mode\":\"legacy\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:22] local.INFO: [SocialAccountService] Refreshing token from provider {\"socialAccountId\":1310,\"provider\":\"google\",\"refreshToken\":\"e34818922c2830a660813a63f6169a4a9a992ae2cccd7dc8dd7796cfdb470ef1\",\"state\":\"full-refresh\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:23] local.ERROR: [SocialAccountService] Failed to refresh token {\"socialAccountId\":1310,\"provider\":\"google\",\"responseBody\":{\"error\":\"invalid_grant\",\"error_description\":\"Bad Request\"}} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:23] local.INFO: [SocialAccountObserver] Saving model {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:23] local.ERROR: [SocialAccountService] Failed to refresh token {\"socialAccountId\":1310,\"provider\":\"google\",\"reason\":\"Flow refresh required.\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:23] local.INFO: [SocialAccountService] Fetching token {\"socialAccountId\":1333,\"provider\":\"google\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:23] local.INFO: [SocialAccountService] Token needs refreshing {\"socialAccountId\":1333,\"provider\":\"google\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:23] local.INFO: [EncryptedTokenManager] Generating access token. {\"mode\":\"legacy\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:23] local.INFO: [SocialAccountService] Refreshing token from provider {\"socialAccountId\":1333,\"provider\":\"google\",\"refreshToken\":\"6c902986546d8e8da1dc539b046cdc1d458f519acc972e5b5f1d6a1a295165e0\",\"state\":\"full-refresh\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:23] local.ERROR: [SocialAccountService] Failed to refresh token {\"socialAccountId\":1333,\"provider\":\"google\",\"responseBody\":{\"error\":\"unauthorized_client\",\"error_description\":\"Unauthorized\"}} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:23] local.INFO: [SocialAccountObserver] Saving model {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:23] local.ERROR: [SocialAccountService] Failed to refresh token {\"socialAccountId\":1333,\"provider\":\"google\",\"reason\":\"Flow refresh required.\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:24] local.INFO: [SocialAccountService] Fetching token {\"socialAccountId\":1368,\"provider\":\"google\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:24] local.INFO: [SocialAccountService] Token needs refreshing {\"socialAccountId\":1368,\"provider\":\"google\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:24] local.INFO: [EncryptedTokenManager] Generating access token. {\"mode\":\"legacy\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:24] local.INFO: [SocialAccountService] Refreshing token from provider {\"socialAccountId\":1368,\"provider\":\"google\",\"refreshToken\":\"d2f128898ff8543bd16b69cfae37896ab85119b0f5ed2b431d739593bb600333\",\"state\":\"full-refresh\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:24] local.ERROR: [SocialAccountService] Failed to refresh token {\"socialAccountId\":1368,\"provider\":\"google\",\"responseBody\":{\"error\":\"invalid_grant\",\"error_description\":\"Bad Request\"}} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:24] local.INFO: [SocialAccountObserver] Saving model {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:24] local.ERROR: [SocialAccountService] Failed to refresh token {\"socialAccountId\":1368,\"provider\":\"google\",\"reason\":\"Flow refresh required.\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:24] local.INFO: [SocialAccountService] Fetching token {\"socialAccountId\":1365,\"provider\":\"google\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:24] local.INFO: [SocialAccountService] Token needs refreshing {\"socialAccountId\":1365,\"provider\":\"google\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:24] local.INFO: [EncryptedTokenManager] Generating access token. {\"mode\":\"legacy\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:24] local.INFO: [SocialAccountService] Refreshing token from provider {\"socialAccountId\":1365,\"provider\":\"google\",\"refreshToken\":\"7676e4a9afcd082b413248ab5ec6e487021fec6a9bdf315860a59cefad9caad8\",\"state\":\"full-refresh\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:25] local.ERROR: [SocialAccountService] Failed to refresh token {\"socialAccountId\":1365,\"provider\":\"google\",\"responseBody\":{\"error\":\"unauthorized_client\",\"error_description\":\"Unauthorized\"}} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:25] local.INFO: [SocialAccountObserver] Saving model {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:25] local.ERROR: [SocialAccountService] Failed to refresh token {\"socialAccountId\":1365,\"provider\":\"google\",\"reason\":\"Flow refresh required.\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:25] local.INFO: [SocialAccountService] Fetching token {\"socialAccountId\":1364,\"provider\":\"google\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:25] local.INFO: [SocialAccountService] Token needs refreshing {\"socialAccountId\":1364,\"provider\":\"google\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:25] local.INFO: [EncryptedTokenManager] Generating access token. {\"mode\":\"legacy\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:25] local.INFO: [SocialAccountService] Refreshing token from provider {\"socialAccountId\":1364,\"provider\":\"google\",\"refreshToken\":\"dd5882ebce76e645292ce33ae74238abbb77c0a4ecc6a2bfe723cad82e72ba8e\",\"state\":\"full-refresh\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:25] local.ERROR: [SocialAccountService] Failed to refresh token {\"socialAccountId\":1364,\"provider\":\"google\",\"responseBody\":{\"error\":\"unauthorized_client\",\"error_description\":\"Unauthorized\"}} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:25] local.INFO: [SocialAccountObserver] Saving model {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:25] local.ERROR: [SocialAccountService] Failed to refresh token {\"socialAccountId\":1364,\"provider\":\"google\",\"reason\":\"Flow refresh required.\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:25] local.INFO: [SocialAccountService] Fetching token {\"socialAccountId\":1370,\"provider\":\"office\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:25] local.INFO: [SocialAccountService] Token needs refreshing {\"socialAccountId\":1370,\"provider\":\"office\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:25] local.INFO: [EncryptedTokenManager] Generating access token. {\"mode\":\"legacy\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:25] local.INFO: [SocialAccountService] Refreshing token from provider {\"socialAccountId\":1370,\"provider\":\"office\",\"refreshToken\":\"b7ee8035306d0043cea6e00e7c4fe14f745e44074a1194db62a31cdf8b70af3e\",\"state\":\"full-refresh\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:26] local.ERROR: [SocialAccountService] Failed to refresh token {\"socialAccountId\":1370,\"provider\":\"office\",\"responseBody\":\"{\\\"error\\\":\\\"invalid_client\\\",\\\"error_description\\\":\\\"AADSTS7000215: Invalid client secret provided. Ensure the secret being sent in the request is the client secret value, not the client secret ID, for a secret added to app 'bbcbb2ef-6200-4fae-82bd-d81f5dd738da'. Trace ID: 2be54cfa-1691-4388-a284-754fdf414400 Correlation ID: 9a934049-899b-45c5-99a5-15c9c470d8a2 Timestamp: 2026-04-27 10:28:26Z\\\",\\\"error_codes\\\":[7000215],\\\"timestamp\\\":\\\"2026-04-27 10:28:26Z\\\",\\\"trace_id\\\":\\\"2be54cfa-1691-4388-a284-754fdf414400\\\",\\\"correlation_id\\\":\\\"9a934049-899b-45c5-99a5-15c9c470d8a2\\\",\\\"error_uri\\\":\\\"https://login.microsoftonline.com/error?code=7000215\\\"}\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:26] local.INFO: [SocialAccountObserver] Saving model {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:26] local.ERROR: [SocialAccountService] Failed to refresh token {\"socialAccountId\":1370,\"provider\":\"office\",\"reason\":\"Flow refresh required.\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:26] local.INFO: [SocialAccountService] Fetching token {\"socialAccountId\":1202,\"provider\":\"office\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:26] local.INFO: [SocialAccountService] Token needs refreshing {\"socialAccountId\":1202,\"provider\":\"office\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:26] local.INFO: [EncryptedTokenManager] Generating access token. {\"mode\":\"legacy\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:26] local.INFO: [SocialAccountService] Refreshing token from provider {\"socialAccountId\":1202,\"provider\":\"office\",\"refreshToken\":\"b458799ccc29b21a6e2eb5260fdb63e49ccba21bf942a3973fb63799bd7f0afe\",\"state\":\"full-refresh\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:26] local.ERROR: [SocialAccountService] Failed to refresh token {\"socialAccountId\":1202,\"provider\":\"office\",\"responseBody\":\"{\\\"error\\\":\\\"invalid_client\\\",\\\"error_description\\\":\\\"AADSTS7000215: Invalid client secret provided. Ensure the secret being sent in the request is the client secret value, not the client secret ID, for a secret added to app 'bbcbb2ef-6200-4fae-82bd-d81f5dd738da'. Trace ID: 27729e9a-0943-41bd-9752-4ec9a8444500 Correlation ID: 824a9761-9387-4c53-ae31-5c242b1d4298 Timestamp: 2026-04-27 10:28:26Z\\\",\\\"error_codes\\\":[7000215],\\\"timestamp\\\":\\\"2026-04-27 10:28:26Z\\\",\\\"trace_id\\\":\\\"27729e9a-0943-41bd-9752-4ec9a8444500\\\",\\\"correlation_id\\\":\\\"824a9761-9387-4c53-ae31-5c242b1d4298\\\",\\\"error_uri\\\":\\\"https://login.microsoftonline.com/error?code=7000215\\\"}\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:26] local.INFO: [SocialAccountObserver] Saving model {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:26] local.ERROR: [SocialAccountService] Failed to refresh token {\"socialAccountId\":1202,\"provider\":\"office\",\"reason\":\"Flow refresh required.\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:26] local.INFO: [SocialAccountService] Fetching token {\"socialAccountId\":1502,\"provider\":\"google\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:26] local.INFO: [SocialAccountService] Token needs refreshing {\"socialAccountId\":1502,\"provider\":\"google\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:26] local.INFO: [EncryptedTokenManager] Generating access token. {\"mode\":\"legacy\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:26] local.INFO: [SocialAccountService] Refreshing token from provider {\"socialAccountId\":1502,\"provider\":\"google\",\"refreshToken\":\"d417c92ebaa137295a04675f715d0511ae8acac9d779b102eac50d6300116d3e\",\"state\":\"connected\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:27] local.INFO: [SocialAccountObserver] Saving model {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:27] local.INFO: [SocialAccountObserver] Access token was modified, encrypting {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:27] local.INFO: [SocialAccountService] Token refreshed {\"socialAccountId\":1502,\"provider\":\"google\",\"state\":\"connected\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:27] local.INFO: Calendar sync job dispatched {\"calendar_id\":501} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:27] local.INFO: [SocialAccountService] Fetching token {\"socialAccountId\":1300,\"provider\":\"google\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:27] local.INFO: [SocialAccountService] Token needs refreshing {\"socialAccountId\":1300,\"provider\":\"google\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:27] local.INFO: [EncryptedTokenManager] Generating access token. {\"mode\":\"legacy\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:27] local.INFO: [SocialAccountService] Refreshing token from provider {\"socialAccountId\":1300,\"provider\":\"google\",\"refreshToken\":\"4b811db0725fd9602a95943519a7da935e2a5065da7d9ebfcb170752e3e1ddb8\",\"state\":\"full-refresh\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:27] local.ERROR: [SocialAccountService] Failed to refresh token {\"socialAccountId\":1300,\"provider\":\"google\",\"responseBody\":{\"error\":\"invalid_grant\",\"error_description\":\"Account has been deleted\"}} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:27] local.INFO: [SocialAccountObserver] Saving model {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:27] local.ERROR: [SocialAccountService] Failed to refresh token {\"socialAccountId\":1300,\"provider\":\"google\",\"reason\":\"Flow refresh required.\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:27] local.INFO: [SocialAccountService] Fetching token {\"socialAccountId\":1409,\"provider\":\"google\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:27] local.INFO: [SocialAccountService] Token needs refreshing {\"socialAccountId\":1409,\"provider\":\"google\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:27] local.INFO: [EncryptedTokenManager] Generating access token. {\"mode\":\"legacy\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:27] local.INFO: [SocialAccountService] Refreshing token from provider {\"socialAccountId\":1409,\"provider\":\"google\",\"refreshToken\":\"e2a3f2d06894894eed1ee87d9db1ace77d4d42ee6e1288a8940ad2c10333b0c4\",\"state\":\"full-refresh\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:28] local.ERROR: [SocialAccountService] Failed to refresh token {\"socialAccountId\":1409,\"provider\":\"google\",\"responseBody\":{\"error\":\"invalid_grant\",\"error_description\":\"Bad Request\"}} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:28] local.INFO: [SocialAccountObserver] Saving model {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:28] local.ERROR: [SocialAccountService] Failed to refresh token {\"socialAccountId\":1409,\"provider\":\"google\",\"reason\":\"Flow refresh required.\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:28] local.INFO: [SocialAccountService] Fetching token {\"socialAccountId\":1352,\"provider\":\"google\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:28] local.INFO: [SocialAccountService] Token needs refreshing {\"socialAccountId\":1352,\"provider\":\"google\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:28] local.INFO: [EncryptedTokenManager] Generating access token. {\"mode\":\"legacy\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:28] local.INFO: [SocialAccountService] Refreshing token from provider {\"socialAccountId\":1352,\"provider\":\"google\",\"refreshToken\":\"dd4b16b00fdc1216da6b717c02338c073636e29162826b2de6db3f064fc029eb\",\"state\":\"full-refresh\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:28] local.ERROR: [SocialAccountService] Failed to refresh token {\"socialAccountId\":1352,\"provider\":\"google\",\"responseBody\":{\"error\":\"unauthorized_client\",\"error_description\":\"Unauthorized\"}} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:28] local.INFO: [SocialAccountObserver] Saving model {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:28] local.ERROR: [SocialAccountService] Failed to refresh token {\"socialAccountId\":1352,\"provider\":\"google\",\"reason\":\"Flow refresh required.\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:28] local.INFO: [SocialAccountService] Fetching token {\"socialAccountId\":1296,\"provider\":\"office\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:28] local.INFO: [SocialAccountService] Token needs refreshing {\"socialAccountId\":1296,\"provider\":\"office\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:28] local.INFO: [EncryptedTokenManager] Generating access token. {\"mode\":\"legacy\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:28] local.INFO: [SocialAccountService] Refreshing token from provider {\"socialAccountId\":1296,\"provider\":\"office\",\"refreshToken\":\"011ae723c9d800c674e0b4be76f49fc046dac7d501b66c59ef0d9549cfa56ae5\",\"state\":\"full-refresh\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:28] local.ERROR: [SocialAccountService] Failed to refresh token {\"socialAccountId\":1296,\"provider\":\"office\",\"responseBody\":\"{\\\"error\\\":\\\"invalid_client\\\",\\\"error_description\\\":\\\"AADSTS7000215: Invalid client secret provided. Ensure the secret being sent in the request is the client secret value, not the client secret ID, for a secret added to app 'bbcbb2ef-6200-4fae-82bd-d81f5dd738da'. Trace ID: 73b326f0-9fcd-4ff5-ad61-edd6a0504500 Correlation ID: 6bd8fef3-b24c-46d8-8bba-a9b2ab41c108 Timestamp: 2026-04-27 10:28:28Z\\\",\\\"error_codes\\\":[7000215],\\\"timestamp\\\":\\\"2026-04-27 10:28:28Z\\\",\\\"trace_id\\\":\\\"73b326f0-9fcd-4ff5-ad61-edd6a0504500\\\",\\\"correlation_id\\\":\\\"6bd8fef3-b24c-46d8-8bba-a9b2ab41c108\\\",\\\"error_uri\\\":\\\"https://login.microsoftonline.com/error?code=7000215\\\"}\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:28] local.INFO: [SocialAccountObserver] Saving model {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:28] local.ERROR: [SocialAccountService] Failed to refresh token {\"socialAccountId\":1296,\"provider\":\"office\",\"reason\":\"Flow refresh required.\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:28] local.INFO: [SocialAccountService] Fetching token {\"socialAccountId\":391,\"provider\":\"office\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:28] local.INFO: [SocialAccountService] Token needs refreshing {\"socialAccountId\":391,\"provider\":\"office\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:28] local.INFO: [EncryptedTokenManager] Generating access token. {\"mode\":\"legacy\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:28] local.INFO: [SocialAccountService] Refreshing token from provider {\"socialAccountId\":391,\"provider\":\"office\",\"refreshToken\":\"00045eebae0f39b34887c6d53f92ae78064f7145e1f4b67754aebd03cfb2d881\",\"state\":\"full-refresh\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:28] local.INFO: [SocialAccountService] Fetching token {\"socialAccountId\":1502,\"provider\":\"google\"} {\"correlation_id\":\"8a9488be-81de-40f5-8554-2503eec42566\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:28] local.INFO: [SocialAccountService] Token retrieved {\"socialAccountId\":1502,\"provider\":\"google\"} {\"correlation_id\":\"8a9488be-81de-40f5-8554-2503eec42566\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:28] local.INFO: [EncryptedTokenManager] Generating access token. {\"mode\":\"legacy\"} {\"correlation_id\":\"8a9488be-81de-40f5-8554-2503eec42566\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:29] local.INFO: [Calendar] Processing sync {\"calendarId\":\"a33076c1-8d97-431a-99f0-85c9524e118b\",\"from\":null,\"to\":null,\"delta\":\"CIiFh8TP44kDEIiFh8TP44kDGAUgkZvkzgIokZvkzgI=\",\"last_sync\":\"2024-12-09 07:12:53\",\"dateMode\":\"daily\"} {\"correlation_id\":\"8a9488be-81de-40f5-8554-2503eec42566\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:29] local.INFO: [CrmOwnerResolver] Integration owner matched as CRM Owner {\"crm_provider\":\"integration-app\",\"crm_owner\":1695,\"team_id\":3143} {\"correlation_id\":\"8a9488be-81de-40f5-8554-2503eec42566\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:29] local.INFO: [SocialAccountService] Fetching token {\"socialAccountId\":1502,\"provider\":\"google\"} {\"correlation_id\":\"8a9488be-81de-40f5-8554-2503eec42566\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:29] local.INFO: [SocialAccountService] Token retrieved {\"socialAccountId\":1502,\"provider\":\"google\"} {\"correlation_id\":\"8a9488be-81de-40f5-8554-2503eec42566\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:29] local.INFO: [EncryptedTokenManager] Generating access token. {\"mode\":\"legacy\"} {\"correlation_id\":\"8a9488be-81de-40f5-8554-2503eec42566\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:29] local.ERROR: [SocialAccountService] Failed to refresh token {\"socialAccountId\":391,\"provider\":\"office\",\"responseBody\":\"{\\\"error\\\":\\\"invalid_client\\\",\\\"error_description\\\":\\\"AADSTS7000215: Invalid client secret provided. Ensure the secret being sent in the request is the client secret value, not the client secret ID, for a secret added to app 'bbcbb2ef-6200-4fae-82bd-d81f5dd738da'. Trace ID: c5f1db6f-22fb-41cc-bd89-c46497634200 Correlation ID: 7b4df0cc-bf50-4017-9b1a-e25ffb6fe9bf Timestamp: 2026-04-27 10:28:29Z\\\",\\\"error_codes\\\":[7000215],\\\"timestamp\\\":\\\"2026-04-27 10:28:29Z\\\",\\\"trace_id\\\":\\\"c5f1db6f-22fb-41cc-bd89-c46497634200\\\",\\\"correlation_id\\\":\\\"7b4df0cc-bf50-4017-9b1a-e25ffb6fe9bf\\\",\\\"error_uri\\\":\\\"https://login.microsoftonline.com/error?code=7000215\\\"}\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:29] local.INFO: [SocialAccountObserver] Saving model {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:29] local.ERROR: [SocialAccountService] Failed to refresh token {\"socialAccountId\":391,\"provider\":\"office\",\"reason\":\"Flow refresh required.\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:29] local.INFO: [SocialAccountService] Fetching token {\"socialAccountId\":1271,\"provider\":\"office\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:29] local.INFO: [SocialAccountService] Token needs refreshing {\"socialAccountId\":1271,\"provider\":\"office\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:29] local.INFO: [EncryptedTokenManager] Generating access token. {\"mode\":\"legacy\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:29] local.INFO: [SocialAccountService] Refreshing token from provider {\"socialAccountId\":1271,\"provider\":\"office\",\"refreshToken\":\"118cde2c06993147b07ccaec4cbcd5026a819dea6c71081166a492933e392afb\",\"state\":\"full-refresh\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:29] local.INFO: [Google Calendar] Failed to watch channel for calendar {\"calendarId\":\"a33076c1-8d97-431a-99f0-85c9524e118b\",\"code\":400,\"reason\":\"{\n \\\"error\\\": {\n \\\"errors\\\": [\n {\n \\\"domain\\\": \\\"global\\\",\n \\\"reason\\\": \\\"push.webhookUrlNotHttps\\\",\n \\\"message\\\": \\\"WebHook callback must be HTTPS: /webhook/calendar/google?resourceType=event\\\"\n }\n ],\n \\\"code\\\": 400,\n \\\"message\\\": \\\"WebHook callback must be HTTPS: /webhook/calendar/google?resourceType=event\\\"\n }\n}\"} {\"correlation_id\":\"8a9488be-81de-40f5-8554-2503eec42566\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:29] local.WARNING: [Calendar] Sync failed {\"calendarId\":\"a33076c1-8d97-431a-99f0-85c9524e118b\",\"code\":400,\"reason\":\"{\n \\\"error\\\": {\n \\\"errors\\\": [\n {\n \\\"domain\\\": \\\"global\\\",\n \\\"reason\\\": \\\"push.webhookUrlNotHttps\\\",\n \\\"message\\\": \\\"WebHook callback must be HTTPS: /webhook/calendar/google?resourceType=event\\\"\n }\n ],\n \\\"code\\\": 400,\n \\\"message\\\": \\\"WebHook callback must be HTTPS: /webhook/calendar/google?resourceType=event\\\"\n }\n}\"} {\"correlation_id\":\"8a9488be-81de-40f5-8554-2503eec42566\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:29] local.ERROR: [SocialAccountService] Failed to refresh token {\"socialAccountId\":1271,\"provider\":\"office\",\"responseBody\":\"{\\\"error\\\":\\\"invalid_client\\\",\\\"error_description\\\":\\\"AADSTS7000215: Invalid client secret provided. Ensure the secret being sent in the request is the client secret value, not the client secret ID, for a secret added to app 'bbcbb2ef-6200-4fae-82bd-d81f5dd738da'. Trace ID: 8d6e82ba-88c3-4ec9-95ee-a4a905803900 Correlation ID: 6ede51d5-704d-4e24-9fb7-88f69f3da5db Timestamp: 2026-04-27 10:28:29Z\\\",\\\"error_codes\\\":[7000215],\\\"timestamp\\\":\\\"2026-04-27 10:28:29Z\\\",\\\"trace_id\\\":\\\"8d6e82ba-88c3-4ec9-95ee-a4a905803900\\\",\\\"correlation_id\\\":\\\"6ede51d5-704d-4e24-9fb7-88f69f3da5db\\\",\\\"error_uri\\\":\\\"https://login.microsoftonline.com/error?code=7000215\\\"}\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:30] local.INFO: [SocialAccountObserver] Saving model {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:30] local.ERROR: [SocialAccountService] Failed to refresh token {\"socialAccountId\":1271,\"provider\":\"office\",\"reason\":\"Flow refresh required.\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:30] local.INFO: [SocialAccountService] Fetching token {\"socialAccountId\":1351,\"provider\":\"google\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:30] local.INFO: [SocialAccountService] Token needs refreshing {\"socialAccountId\":1351,\"provider\":\"google\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:30] local.INFO: [EncryptedTokenManager] Generating access token. {\"mode\":\"legacy\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:30] local.INFO: [SocialAccountService] Refreshing token from provider {\"socialAccountId\":1351,\"provider\":\"google\",\"refreshToken\":\"4271d15b9e60a606439caddc68337f783e472c85b03dacff14d1b6dfded9051c\",\"state\":\"full-refresh\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:30] local.ERROR: [SocialAccountService] Failed to refresh token {\"socialAccountId\":1351,\"provider\":\"google\",\"responseBody\":{\"error\":\"invalid_grant\",\"error_description\":\"Token has been expired or revoked.\"}} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:30] local.INFO: [SocialAccountObserver] Saving model {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:30] local.ERROR: [SocialAccountService] Failed to refresh token {\"socialAccountId\":1351,\"provider\":\"google\",\"reason\":\"Flow refresh required.\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:30] local.INFO: [SocialAccountService] Fetching token {\"socialAccountId\":1366,\"provider\":\"google\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:30] local.INFO: [SocialAccountService] Token needs refreshing {\"socialAccountId\":1366,\"provider\":\"google\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:30] local.INFO: [EncryptedTokenManager] Generating access token. {\"mode\":\"legacy\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:30] local.INFO: [SocialAccountService] Refreshing token from provider {\"socialAccountId\":1366,\"provider\":\"google\",\"refreshToken\":\"ae21385059b2eebfd43f68aecd56eccd702a1aabb6598f1f7ab594ed8af491b4\",\"state\":\"full-refresh\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:30] local.ERROR: [SocialAccountService] Failed to refresh token {\"socialAccountId\":1366,\"provider\":\"google\",\"responseBody\":{\"error\":\"invalid_grant\",\"error_description\":\"Bad Request\"}} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:30] local.INFO: [SocialAccountObserver] Saving model {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:30] local.ERROR: [SocialAccountService] Failed to refresh token {\"socialAccountId\":1366,\"provider\":\"google\",\"reason\":\"Flow refresh required.\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:30] local.INFO: [SocialAccountService] Fetching token {\"socialAccountId\":1115,\"provider\":\"google\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:30] local.INFO: [SocialAccountService] Token needs refreshing {\"socialAccountId\":1115,\"provider\":\"google\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:30] local.INFO: [EncryptedTokenManager] Generating access token. {\"mode\":\"legacy\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:30] local.INFO: [SocialAccountService] Refreshing token from provider {\"socialAccountId\":1115,\"provider\":\"google\",\"refreshToken\":\"356b60f12e262a5e24d3042386ef47d6a6cfe3074c242f4426edcec8646192b1\",\"state\":\"connected\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:30] local.INFO: [SocialAccountObserver] Saving model {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:30] local.INFO: [SocialAccountObserver] Access token was modified, encrypting {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:31] local.INFO: [SocialAccountService] Token refreshed {\"socialAccountId\":1115,\"provider\":\"google\",\"state\":\"connected\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:31] local.INFO: Calendar sync job dispatched {\"calendar_id\":378} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:31] local.INFO: [SocialAccountService] Fetching token {\"socialAccountId\":1421,\"provider\":\"office\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:31] local.INFO: [SocialAccountService] Token needs refreshing {\"socialAccountId\":1421,\"provider\":\"office\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:31] local.INFO: [EncryptedTokenManager] Generating access token. {\"mode\":\"legacy\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:31] local.INFO: [SocialAccountService] Refreshing token from provider {\"socialAccountId\":1421,\"provider\":\"office\",\"refreshToken\":\"9e3e287aa0cc900726e3d4eead2600fb64aa34b437790f6a1fd8048306cb2db9\",\"state\":\"connected\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:31] local.INFO: [SocialAccountService] Fetching token {\"socialAccountId\":1115,\"provider\":\"google\"} {\"correlation_id\":\"8fdf411d-8a4f-46c1-a46a-cc84f3c2f2b1\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:31] local.INFO: [SocialAccountService] Token retrieved {\"socialAccountId\":1115,\"provider\":\"google\"} {\"correlation_id\":\"8fdf411d-8a4f-46c1-a46a-cc84f3c2f2b1\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:31] local.INFO: [EncryptedTokenManager] Generating access token. {\"mode\":\"legacy\"} {\"correlation_id\":\"8fdf411d-8a4f-46c1-a46a-cc84f3c2f2b1\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:31] local.INFO: [Calendar] Processing sync {\"calendarId\":\"2676cb6d-f86c-427e-bf78-591e388e3c1e\",\"from\":null,\"to\":null,\"delta\":\"CJ_x49O3jpIDEJ_x49O3jpIDGAUgw67KlwMow67KlwM=\",\"last_sync\":\"2026-01-19 07:48:40\",\"dateMode\":\"daily\"} {\"correlation_id\":\"8fdf411d-8a4f-46c1-a46a-cc84f3c2f2b1\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:31] local.WARNING: [Pipedrive] Account not connected for user {\"userId\":\"e6538737-e7b4-455f-a37a-3e79b665a220\",\"account\":{\"Jiminny\\\\Models\\\\SocialAccount\":{\"id\":1116,\"sociable_id\":241,\"provider_user_id\":\"19555731\",\"expires\":1775683749,\"refresh_token_expires\":null,\"provider\":\"pipedrive\",\"state\":\"full-refresh\",\"auth_scope\":\"base,deals:full,activities:full,contacts:full,search:read\",\"retry_after\":null,\"created_at\":\"2023-09-08 09:44:29\",\"updated_at\":\"2026-04-08 22:58:34\"}}} {\"correlation_id\":\"8fdf411d-8a4f-46c1-a46a-cc84f3c2f2b1\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:31] local.INFO: [CrmOwnerResolver] Integration owner is not connected, attempting team members {\"crm_provider\":\"pipedrive\",\"crm_owner\":241,\"team_id\":19} {\"correlation_id\":\"8fdf411d-8a4f-46c1-a46a-cc84f3c2f2b1\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:31] local.INFO: [CrmOwnerResolver] No team members found with active crm connection {\"crm_provider\":\"pipedrive\",\"team_id\":19} {\"correlation_id\":\"8fdf411d-8a4f-46c1-a46a-cc84f3c2f2b1\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:31] local.INFO: [CrmOwnerResolver] No team member found with active crm connection {\"crm_provider\":\"pipedrive\",\"team_id\":19} {\"correlation_id\":\"8fdf411d-8a4f-46c1-a46a-cc84f3c2f2b1\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:31] local.WARNING: [Calendar] CRM disconnected for user so events will not be matched {\"provider\":\"pipedrive\",\"user_id\":241,\"message\":\"Your Pipedrive account has become disconnected. Please login to Jiminny to reconnect.\"} {\"correlation_id\":\"8fdf411d-8a4f-46c1-a46a-cc84f3c2f2b1\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:31] local.INFO: [SocialAccountService] Fetching token {\"socialAccountId\":1115,\"provider\":\"google\"} {\"correlation_id\":\"8fdf411d-8a4f-46c1-a46a-cc84f3c2f2b1\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:31] local.INFO: [SocialAccountService] Token retrieved {\"socialAccountId\":1115,\"provider\":\"google\"} {\"correlation_id\":\"8fdf411d-8a4f-46c1-a46a-cc84f3c2f2b1\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:31] local.INFO: [EncryptedTokenManager] Generating access token. {\"mode\":\"legacy\"} {\"correlation_id\":\"8fdf411d-8a4f-46c1-a46a-cc84f3c2f2b1\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:31] local.INFO: [Google Calendar] Failed to watch channel for calendar {\"calendarId\":\"2676cb6d-f86c-427e-bf78-591e388e3c1e\",\"code\":400,\"reason\":\"{\n \\\"error\\\": {\n \\\"errors\\\": [\n {\n \\\"domain\\\": \\\"global\\\",\n \\\"reason\\\": \\\"push.webhookUrlNotHttps\\\",\n \\\"message\\\": \\\"WebHook callback must be HTTPS: /webhook/calendar/google?resourceType=event\\\"\n }\n ],\n \\\"code\\\": 400,\n \\\"message\\\": \\\"WebHook callback must be HTTPS: /webhook/calendar/google?resourceType=event\\\"\n }\n}\"} {\"correlation_id\":\"8fdf411d-8a4f-46c1-a46a-cc84f3c2f2b1\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:31] local.WARNING: [Calendar] Sync failed {\"calendarId\":\"2676cb6d-f86c-427e-bf78-591e388e3c1e\",\"code\":400,\"reason\":\"{\n \\\"error\\\": {\n \\\"errors\\\": [\n {\n \\\"domain\\\": \\\"global\\\",\n \\\"reason\\\": \\\"push.webhookUrlNotHttps\\\",\n \\\"message\\\": \\\"WebHook callback must be HTTPS: /webhook/calendar/google?resourceType=event\\\"\n }\n ],\n \\\"code\\\": 400,\n \\\"message\\\": \\\"WebHook callback must be HTTPS: /webhook/calendar/google?resourceType=event\\\"\n }\n}\"} {\"correlation_id\":\"8fdf411d-8a4f-46c1-a46a-cc84f3c2f2b1\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:32] local.INFO: [SocialAccountObserver] Saving model {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:32] local.INFO: [SocialAccountObserver] Access token was modified, encrypting {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:32] local.INFO: [SocialAccountObserver] Refresh token was modified, encrypting {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:32] local.INFO: [SocialAccountService] Token refreshed {\"socialAccountId\":1421,\"provider\":\"office\",\"state\":\"connected\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:32] local.INFO: Calendar sync job dispatched {\"calendar_id\":504} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:32] local.NOTICE: Calendar sync end {\"retrieved_calendars\":31,\"processed_calendars\":3} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:32] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage for command {\"command\":\"calendar:sync\",\"memoryBeforeCommandInMb\":60.0,\"memoryAfterCommandInMB\":62.0,\"memoryPeakBeforeCommandInMb\":99.727,\"memoryPeakAfterCommandInMB\":99.727} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:33] local.INFO: [SocialAccountService] Fetching token {\"socialAccountId\":1421,\"provider\":\"office\"} {\"correlation_id\":\"52b2ff86-82c7-4ed0-b528-35d7d00d69f0\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:33] local.INFO: [SocialAccountService] Token retrieved {\"socialAccountId\":1421,\"provider\":\"office\"} {\"correlation_id\":\"52b2ff86-82c7-4ed0-b528-35d7d00d69f0\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:33] local.INFO: [EncryptedTokenManager] Generating access token. {\"mode\":\"legacy\"} {\"correlation_id\":\"52b2ff86-82c7-4ed0-b528-35d7d00d69f0\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:34] local.INFO: [Calendar] Processing sync {\"calendarId\":\"9e8b1a2c-1a8f-42bd-b161-810fc0baf540\",\"from\":null,\"to\":null,\"delta\":\"R0usmcdvmMuZCBYV0hguCMx0nGVsTQEodM41jSlu-5eY2JUTYHZXE-bDro2tnepp8q_Vq3ITNS8CkncjGjcBfaHkza05dCTIlaXcSIoPNGOl9vNckYy_ZhVgE1SWvgRcCeVT-SyogdB9hP-oaWbH9r51OQrrzAwmalK5aCksyUVh9jG_A-mGVzRPnUB4SKa77o5P659lpx1egON48YU2tQ.D2oErKma-mQrBdXxAKdH7beUkp6HS2PXUWYLCgvbP2c\",\"last_sync\":\"2026-04-27 06:28:41\",\"dateMode\":\"daily\"} {\"correlation_id\":\"52b2ff86-82c7-4ed0-b528-35d7d00d69f0\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:34] local.INFO: [SocialAccountService] Fetching token {\"socialAccountId\":1499,\"provider\":\"hubspot\"} {\"correlation_id\":\"52b2ff86-82c7-4ed0-b528-35d7d00d69f0\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:34] local.INFO: [SocialAccountService] Token retrieved {\"socialAccountId\":1499,\"provider\":\"hubspot\"} {\"correlation_id\":\"52b2ff86-82c7-4ed0-b528-35d7d00d69f0\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:34] local.INFO: [EncryptedTokenManager] Generating access token. {\"mode\":\"legacy\"} {\"correlation_id\":\"52b2ff86-82c7-4ed0-b528-35d7d00d69f0\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:34] local.INFO: [CrmOwnerResolver] Integration owner matched as CRM Owner {\"crm_provider\":\"hubspot\",\"crm_owner\":89,\"team_id\":2} {\"correlation_id\":\"52b2ff86-82c7-4ed0-b528-35d7d00d69f0\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:34] local.INFO: [MS Office Calendar] Skipping delta sync for daily mode {\"calendarId\":\"9e8b1a2c-1a8f-42bd-b161-810fc0baf540\"} {\"correlation_id\":\"52b2ff86-82c7-4ed0-b528-35d7d00d69f0\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:29:06] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage before starting command {\"command\":\"meeting-bot:schedule-bot\",\"memoryBeforeCommandInMb\":60.0,\"memoryPeakBeforeCommandInMb\":99.727} {\"correlation_id\":\"7e95c05b-50c8-4e0b-a01c-efeed6af544c\",\"trace_id\":\"07db4d4f-d6e7-44d9-8e54-4f038ea69afb\"}\n[2026-04-27 10:29:06] local.INFO: [ScheduleBotCommand] Number of activities to be captured: 0 {\"correlation_id\":\"7e95c05b-50c8-4e0b-a01c-efeed6af544c\",\"trace_id\":\"07db4d4f-d6e7-44d9-8e54-4f038ea69afb\"}\n[2026-04-27 10:29:06] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage for command {\"command\":\"meeting-bot:schedule-bot\",\"memoryBeforeCommandInMb\":60.0,\"memoryAfterCommandInMB\":62.0,\"memoryPeakBeforeCommandInMb\":99.727,\"memoryPeakAfterCommandInMB\":99.727} {\"correlation_id\":\"7e95c05b-50c8-4e0b-a01c-efeed6af544c\",\"trace_id\":\"07db4d4f-d6e7-44d9-8e54-4f038ea69afb\"}\n[2026-04-27 10:29:09] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage before starting command {\"command\":\"dialers:monitor-activities\",\"memoryBeforeCommandInMb\":60.0,\"memoryPeakBeforeCommandInMb\":99.727} {\"correlation_id\":\"e6e7fbf8-6e80-4529-858e-a4e95d0d3805\",\"trace_id\":\"f91a115a-9fdc-46ce-b9f2-13a1e6d0e325\"}\n[2026-04-27 10:29:09] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage for command {\"command\":\"dialers:monitor-activities\",\"memoryBeforeCommandInMb\":60.0,\"memoryAfterCommandInMB\":62.0,\"memoryPeakBeforeCommandInMb\":99.727,\"memoryPeakAfterCommandInMB\":99.727} {\"correlation_id\":\"e6e7fbf8-6e80-4529-858e-a4e95d0d3805\",\"trace_id\":\"f91a115a-9fdc-46ce-b9f2-13a1e6d0e325\"}\n[2026-04-27 10:29:10] local.NOTICE: Monitoring start {\"correlation_id\":\"211e5e36-1c6c-4198-b928-d2f1525ab0ea\",\"trace_id\":\"c26e980d-6e32-426d-9667-f1d902432b8d\"}\n[2026-04-27 10:29:10] local.NOTICE: Monitoring end {\"correlation_id\":\"211e5e36-1c6c-4198-b928-d2f1525ab0ea\",\"trace_id\":\"c26e980d-6e32-426d-9667-f1d902432b8d\"}\n[2026-04-27 10:29:12] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage before starting command {\"command\":\"mailbox:skip-lists:refresh\",\"memoryBeforeCommandInMb\":60.0,\"memoryPeakBeforeCommandInMb\":99.727} {\"correlation_id\":\"684bdf68-76ee-42dd-9d97-62f414a33b1b\",\"trace_id\":\"284c98ef-5b7f-4572-996c-72c5bc2995f9\"}\n[2026-04-27 10:29:13] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage for command {\"command\":\"mailbox:skip-lists:refresh\",\"memoryBeforeCommandInMb\":60.0,\"memoryAfterCommandInMB\":62.0,\"memoryPeakBeforeCommandInMb\":99.727,\"memoryPeakAfterCommandInMB\":99.727} {\"correlation_id\":\"684bdf68-76ee-42dd-9d97-62f414a33b1b\",\"trace_id\":\"284c98ef-5b7f-4572-996c-72c5bc2995f9\"}\n[2026-04-27 10:29:18] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage before starting command {\"command\":\"mailbox:batch:process\",\"memoryBeforeCommandInMb\":60.0,\"memoryPeakBeforeCommandInMb\":99.727} {\"correlation_id\":\"6e74b148-ae54-42ff-ab72-2ded18d10ff4\",\"trace_id\":\"d05bc9a0-450c-45b2-be3b-225a551ed909\"}\n[2026-04-27 10:29:18] local.INFO: [EmailSchedule] STARTING batch process {\"host\":\"docker_lamp_1\"} {\"correlation_id\":\"6e74b148-ae54-42ff-ab72-2ded18d10ff4\",\"trace_id\":\"d05bc9a0-450c-45b2-be3b-225a551ed909\"}\n[2026-04-27 10:29:18] local.INFO: [EmailSchedule] FINISHED batch process {\"host\":\"docker_lamp_1\",\"processed\":0} {\"correlation_id\":\"6e74b148-ae54-42ff-ab72-2ded18d10ff4\",\"trace_id\":\"d05bc9a0-450c-45b2-be3b-225a551ed909\"}\n[2026-04-27 10:29:18] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage for command {\"command\":\"mailbox:batch:process\",\"memoryBeforeCommandInMb\":60.0,\"memoryAfterCommandInMB\":62.0,\"memoryPeakBeforeCommandInMb\":99.727,\"memoryPeakAfterCommandInMB\":99.727} {\"correlation_id\":\"6e74b148-ae54-42ff-ab72-2ded18d10ff4\",\"trace_id\":\"d05bc9a0-450c-45b2-be3b-225a551ed909\"}\n[2026-04-27 10:30:08] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage before starting command {\"command\":\"meeting-bot:schedule-bot\",\"memoryBeforeCommandInMb\":60.0,\"memoryPeakBeforeCommandInMb\":99.727} {\"correlation_id\":\"24b807e0-b413-4ecc-a89b-365280be88ac\",\"trace_id\":\"542ac70c-6756-4409-8224-0844a7576d9c\"}\n[2026-04-27 10:30:08] local.INFO: [ScheduleBotCommand] Number of activities to be captured: 0 {\"correlation_id\":\"24b807e0-b413-4ecc-a89b-365280be88ac\",\"trace_id\":\"542ac70c-6756-4409-8224-0844a7576d9c\"}\n[2026-04-27 10:30:08] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage for command {\"command\":\"meeting-bot:schedule-bot\",\"memoryBeforeCommandInMb\":60.0,\"memoryAfterCommandInMB\":62.0,\"memoryPeakBeforeCommandInMb\":99.727,\"memoryPeakAfterCommandInMB\":99.727} {\"correlation_id\":\"24b807e0-b413-4ecc-a89b-365280be88ac\",\"trace_id\":\"542ac70c-6756-4409-8224-0844a7576d9c\"}\n[2026-04-27 10:30:10] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage before starting command {\"command\":\"dialers:monitor-activities\",\"memoryBeforeCommandInMb\":60.0,\"memoryPeakBeforeCommandInMb\":99.727} {\"correlation_id\":\"2711ca04-b4a7-4f3a-b0b2-5826fea4da3b\",\"trace_id\":\"2613ffca-f181-4d91-ad55-ec1bdd97ee7e\"}\n[2026-04-27 10:30:10] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage for command {\"command\":\"dialers:monitor-activities\",\"memoryBeforeCommandInMb\":60.0,\"memoryAfterCommandInMB\":62.0,\"memoryPeakBeforeCommandInMb\":99.727,\"memoryPeakAfterCommandInMB\":99.727} {\"correlation_id\":\"2711ca04-b4a7-4f3a-b0b2-5826fea4da3b\",\"trace_id\":\"2613ffca-f181-4d91-ad55-ec1bdd97ee7e\"}\n[2026-04-27 10:30:17] local.NOTICE: Monitoring start {\"correlation_id\":\"e1537475-8481-4144-b9f5-596ea8034b8a\",\"trace_id\":\"f5561d0a-0cb7-4c72-8bde-cce617183529\"}\n[2026-04-27 10:30:17] local.NOTICE: Monitoring end {\"correlation_id\":\"e1537475-8481-4144-b9f5-596ea8034b8a\",\"trace_id\":\"f5561d0a-0cb7-4c72-8bde-cce617183529\"}\n[2026-04-27 10:30:21] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage before starting command {\"command\":\"mailbox:skip-lists:refresh\",\"memoryBeforeCommandInMb\":60.0,\"memoryPeakBeforeCommandInMb\":99.727} {\"correlation_id\":\"8a911b60-16a0-416e-934e-3b7e0280e452\",\"trace_id\":\"3c2bbdd6-985d-4624-b42b-fd9cc2b4756f\"}\n[2026-04-27 10:30:21] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage for command {\"command\":\"mailbox:skip-lists:refresh\",\"memoryBeforeCommandInMb\":60.0,\"memoryAfterCommandInMB\":62.0,\"memoryPeakBeforeCommandInMb\":99.727,\"memoryPeakAfterCommandInMB\":99.727} {\"correlation_id\":\"8a911b60-16a0-416e-934e-3b7e0280e452\",\"trace_id\":\"3c2bbdd6-985d-4624-b42b-fd9cc2b4756f\"}\n[2026-04-27 10:30:25] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage before starting command {\"command\":\"mailbox:batch:process\",\"memoryBeforeCommandInMb\":60.0,\"memoryPeakBeforeCommandInMb\":99.727} {\"correlation_id\":\"f62efaa6-201f-43b2-bf45-9619982dffb1\",\"trace_id\":\"f1ad3d60-af4e-4427-9d77-6aa71cfed172\"}\n[2026-04-27 10:30:25] local.INFO: [EmailSchedule] STARTING batch process {\"host\":\"docker_lamp_1\"} {\"correlation_id\":\"f62efaa6-201f-43b2-bf45-9619982dffb1\",\"trace_id\":\"f1ad3d60-af4e-4427-9d77-6aa71cfed172\"}\n[2026-04-27 10:30:25] local.INFO: [EmailSchedule] FINISHED batch process {\"host\":\"docker_lamp_1\",\"processed\":0} {\"correlation_id\":\"f62efaa6-201f-43b2-bf45-9619982dffb1\",\"trace_id\":\"f1ad3d60-af4e-4427-9d77-6aa71cfed172\"}\n[2026-04-27 10:30:25] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage for command {\"command\":\"mailbox:batch:process\",\"memoryBeforeCommandInMb\":60.0,\"memoryAfterCommandInMB\":62.0,\"memoryPeakBeforeCommandInMb\":99.727,\"memoryPeakAfterCommandInMB\":99.727} {\"correlation_id\":\"f62efaa6-201f-43b2-bf45-9619982dffb1\",\"trace_id\":\"f1ad3d60-af4e-4427-9d77-6aa71cfed172\"}\n[2026-04-27 10:30:30] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage before starting command {\"command\":\"conference:monitor:count\",\"memoryBeforeCommandInMb\":60.0,\"memoryPeakBeforeCommandInMb\":99.727} {\"correlation_id\":\"cf6dc132-ecb8-4efc-bead-06bf1c96cae6\",\"trace_id\":\"9e2b073b-940d-498a-babe-115f633dd954\"}\n[2026-04-27 10:30:30] local.INFO: Running conference:monitor:count command for activities in (2026-04-27 10:28:00, 2026-04-27 10:30:00] {\"correlation_id\":\"cf6dc132-ecb8-4efc-bead-06bf1c96cae6\",\"trace_id\":\"9e2b073b-940d-498a-babe-115f633dd954\"}\n[2026-04-27 10:30:30] local.INFO: [conference:monitor:count] No activities found in (2026-04-27 10:28:00, 2026-04-27 10:30:00] {\"correlation_id\":\"cf6dc132-ecb8-4efc-bead-06bf1c96cae6\",\"trace_id\":\"9e2b073b-940d-498a-babe-115f633dd954\"}\n[2026-04-27 10:30:30] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage for command {\"command\":\"conference:monitor:count\",\"memoryBeforeCommandInMb\":60.0,\"memoryAfterCommandInMB\":62.0,\"memoryPeakBeforeCommandInMb\":99.727,\"memoryPeakAfterCommandInMB\":99.727} {\"correlation_id\":\"cf6dc132-ecb8-4efc-bead-06bf1c96cae6\",\"trace_id\":\"9e2b073b-940d-498a-babe-115f633dd954\"}\n[2026-04-27 10:30:32] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage before starting command {\"command\":\"activity:purge-stale\",\"memoryBeforeCommandInMb\":60.0,\"memoryPeakBeforeCommandInMb\":99.727} {\"correlation_id\":\"f455260d-d1c1-456f-8937-d4bb026d9078\",\"trace_id\":\"f8c2e585-0137-4103-9e16-1b4ce05df2ef\"}\n[2026-04-27 10:30:32] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage for command {\"command\":\"activity:purge-stale\",\"memoryBeforeCommandInMb\":60.0,\"memoryAfterCommandInMB\":62.0,\"memoryPeakBeforeCommandInMb\":99.727,\"memoryPeakAfterCommandInMB\":99.727} {\"correlation_id\":\"f455260d-d1c1-456f-8937-d4bb026d9078\",\"trace_id\":\"f8c2e585-0137-4103-9e16-1b4ce05df2ef\"}\n[2026-04-27 10:30:35] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage before starting command {\"command\":\"mailbox:text-relay:sync\",\"memoryBeforeCommandInMb\":60.0,\"memoryPeakBeforeCommandInMb\":99.727} {\"correlation_id\":\"be7d3c6a-664a-42e8-a4b3-cbd021296404\",\"trace_id\":\"0b250827-4c02-4237-8b34-e5ddd004eba1\"}\n[2026-04-27 10:30:35] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage for command {\"command\":\"mailbox:text-relay:sync\",\"memoryBeforeCommandInMb\":60.0,\"memoryAfterCommandInMB\":62.0,\"memoryPeakBeforeCommandInMb\":99.727,\"memoryPeakAfterCommandInMB\":99.727} {\"correlation_id\":\"be7d3c6a-664a-42e8-a4b3-cbd021296404\",\"trace_id\":\"0b250827-4c02-4237-8b34-e5ddd004eba1\"}\n[2026-04-27 10:30:37] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage before starting command {\"command\":\"conference:pre-meeting-notification\",\"memoryBeforeCommandInMb\":60.0,\"memoryPeakBeforeCommandInMb\":99.727} {\"correlation_id\":\"5d8fbdf6-2737-4d73-babe-699c88279e1f\",\"trace_id\":\"0882a963-b02f-4049-b670-12b92f80829a\"}\n[2026-04-27 10:30:37] local.INFO: Running pre-meeting notification command {\"correlation_id\":\"5d8fbdf6-2737-4d73-babe-699c88279e1f\",\"trace_id\":\"0882a963-b02f-4049-b670-12b92f80829a\"}\n[2026-04-27 10:30:37] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage for command {\"command\":\"conference:pre-meeting-notification\",\"memoryBeforeCommandInMb\":60.0,\"memoryAfterCommandInMB\":62.0,\"memoryPeakBeforeCommandInMb\":99.727,\"memoryPeakAfterCommandInMB\":99.727} {\"correlation_id\":\"5d8fbdf6-2737-4d73-babe-699c88279e1f\",\"trace_id\":\"0882a963-b02f-4049-b670-12b92f80829a\"}\n[2026-04-27 10:30:39] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage before starting command {\"command\":\"conference:monitor:start\",\"memoryBeforeCommandInMb\":60.0,\"memoryPeakBeforeCommandInMb\":99.727} {\"correlation_id\":\"e8968e47-9acd-4bd4-9e7d-3c415f46df05\",\"trace_id\":\"287fba85-f395-41ab-813d-49cb5d0ea06d\"}\n[2026-04-27 10:30:39] local.INFO: Running conference:monitor:start command for activities in (2026-04-27 10:20:00, 2026-04-27 10:25:00] {\"correlation_id\":\"e8968e47-9acd-4bd4-9e7d-3c415f46df05\",\"trace_id\":\"287fba85-f395-41ab-813d-49cb5d0ea06d\"}\n[2026-04-27 10:30:39] local.INFO: [conference:monitor:start] No activities found in (2026-04-27 10:20:00, 2026-04-27 10:25:00] {\"correlation_id\":\"e8968e47-9acd-4bd4-9e7d-3c415f46df05\",\"trace_id\":\"287fba85-f395-41ab-813d-49cb5d0ea06d\"}\n[2026-04-27 10:30:39] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage for command {\"command\":\"conference:monitor:start\",\"memoryBeforeCommandInMb\":60.0,\"memoryAfterCommandInMB\":62.0,\"memoryPeakBeforeCommandInMb\":99.727,\"memoryPeakAfterCommandInMB\":99.727} {\"correlation_id\":\"e8968e47-9acd-4bd4-9e7d-3c415f46df05\",\"trace_id\":\"287fba85-f395-41ab-813d-49cb5d0ea06d\"}\n[2026-04-27 10:30:43] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage before starting command {\"command\":\"conference:monitor:end\",\"memoryBeforeCommandInMb\":60.0,\"memoryPeakBeforeCommandInMb\":99.727} {\"correlation_id\":\"b87eb011-7108-46bd-aa90-975ebe41f483\",\"trace_id\":\"1f54c2a6-fad1-4804-87d8-4972a378e91b\"}\n[2026-04-27 10:30:43] local.INFO: conference:monitor:end:Jiminny\\Console\\Commands\\Activities\\MonitorMeetingEndCommand::logActivitiesEnded {\"from\":\"10:25\",\"to\":\"10:30\"} {\"correlation_id\":\"b87eb011-7108-46bd-aa90-975ebe41f483\",\"trace_id\":\"1f54c2a6-fad1-4804-87d8-4972a378e91b\"}\n[2026-04-27 10:30:44] local.INFO: conference:monitor:end:Jiminny\\Console\\Commands\\Activities\\MonitorMeetingEndCommand::logActivitiesWithUnfinishedSession {\"from\":\"00:20\",\"to\":\"00:25\"} {\"correlation_id\":\"b87eb011-7108-46bd-aa90-975ebe41f483\",\"trace_id\":\"1f54c2a6-fad1-4804-87d8-4972a378e91b\"}\n[2026-04-27 10:30:44] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage for command {\"command\":\"conference:monitor:end\",\"memoryBeforeCommandInMb\":60.0,\"memoryAfterCommandInMB\":62.0,\"memoryPeakBeforeCommandInMb\":99.727,\"memoryPeakAfterCommandInMB\":99.727} {\"correlation_id\":\"b87eb011-7108-46bd-aa90-975ebe41f483\",\"trace_id\":\"1f54c2a6-fad1-4804-87d8-4972a378e91b\"}\n[2026-04-27 10:30:51] local.NOTICE: Repairing HubSpot tokens start {\"correlation_id\":\"2e6dfa3d-f07d-4eee-8232-6138133e2fbb\",\"trace_id\":\"41dea86e-0ecc-414e-8303-e60d8ae09976\"}\n[2026-04-27 10:30:51] local.INFO: Trying to refresh HubSpot token {\"account_id\":59,\"updated_at\":\"2025-10-03 09:32:05\"} {\"correlation_id\":\"2e6dfa3d-f07d-4eee-8232-6138133e2fbb\",\"trace_id\":\"41dea86e-0ecc-414e-8303-e60d8ae09976\"}\n[2026-04-27 10:30:51] local.INFO: [EncryptedTokenManager] Generating access token. {\"mode\":\"legacy\"} {\"correlation_id\":\"2e6dfa3d-f07d-4eee-8232-6138133e2fbb\",\"trace_id\":\"41dea86e-0ecc-414e-8303-e60d8ae09976\"}\n[2026-04-27 10:30:51] local.INFO: [SocialAccountService] Refreshing token from provider {\"socialAccountId\":59,\"provider\":\"hubspot\",\"refreshToken\":\"97b78f6e2cc49965c00c2492b602b02708b1392551e6b3f113fbaa48992af90b\",\"state\":\"full-refresh\"} {\"correlation_id\":\"2e6dfa3d-f07d-4eee-8232-6138133e2fbb\",\"trace_id\":\"41dea86e-0ecc-414e-8303-e60d8ae09976\"}\n[2026-04-27 10:30:52] local.ERROR: Failed to refresh HubSpot token {\"account_id\":59,\"updated_at\":\"2025-10-03 09:32:05\",\"reason\":\"missing or invalid refresh token\",\"previous\":\"\"} {\"correlation_id\":\"2e6dfa3d-f07d-4eee-8232-6138133e2fbb\",\"trace_id\":\"41dea86e-0ecc-414e-8303-e60d8ae09976\"}\n[2026-04-27 10:30:52] local.INFO: Trying to refresh HubSpot token {\"account_id\":306,\"updated_at\":\"2023-11-27 09:30:03\"} {\"correlation_id\":\"2e6dfa3d-f07d-4eee-8232-6138133e2fbb\",\"trace_id\":\"41dea86e-0ecc-414e-8303-e60d8ae09976\"}\n[2026-04-27 10:30:52] local.INFO: [EncryptedTokenManager] Generating access token. {\"mode\":\"legacy\"} {\"correlation_id\":\"2e6dfa3d-f07d-4eee-8232-6138133e2fbb\",\"trace_id\":\"41dea86e-0ecc-414e-8303-e60d8ae09976\"}\n[2026-04-27 10:30:52] local.INFO: [SocialAccountService] Refreshing token from provider {\"socialAccountId\":306,\"provider\":\"hubspot\",\"refreshToken\":\"6fa6aa8cc641d131231acc3470f5c03cb3b07b2e580fb18f8acb3b1dbb72549b\",\"state\":\"full-refresh\"} {\"correlation_id\":\"2e6dfa3d-f07d-4eee-8232-6138133e2fbb\",\"trace_id\":\"41dea86e-0ecc-414e-8303-e60d8ae09976\"}\n[2026-04-27 10:30:52] local.ERROR: Failed to refresh HubSpot token {\"account_id\":306,\"updated_at\":\"2023-11-27 09:30:03\",\"reason\":\"missing or invalid refresh token\",\"previous\":\"\"} {\"correlation_id\":\"2e6dfa3d-f07d-4eee-8232-6138133e2fbb\",\"trace_id\":\"41dea86e-0ecc-414e-8303-e60d8ae09976\"}\n[2026-04-27 10:30:52] local.INFO: Trying to refresh HubSpot token {\"account_id\":1372,\"updated_at\":\"2025-10-02 14:47:06\"} {\"correlation_id\":\"2e6dfa3d-f07d-4eee-8232-6138133e2fbb\",\"trace_id\":\"41dea86e-0ecc-414e-8303-e60d8ae09976\"}\n[2026-04-27 10:30:52] local.INFO: [EncryptedTokenManager] Generating access token. {\"mode\":\"legacy\"} {\"correlation_id\":\"2e6dfa3d-f07d-4eee-8232-6138133e2fbb\",\"trace_id\":\"41dea86e-0ecc-414e-8303-e60d8ae09976\"}\n[2026-04-27 10:30:52] local.INFO: [SocialAccountService] Refreshing token from provider {\"socialAccountId\":1372,\"provider\":\"hubspot\",\"refreshToken\":\"9aa73948c761da29dce46c177cf9aee1fde483a44169ca38723f9f0597d7a8c4\",\"state\":\"full-refresh\"} {\"correlation_id\":\"2e6dfa3d-f07d-4eee-8232-6138133e2fbb\",\"trace_id\":\"41dea86e-0ecc-414e-8303-e60d8ae09976\"}\n[2026-04-27 10:30:52] local.ERROR: Failed to refresh HubSpot token {\"account_id\":1372,\"updated_at\":\"2025-10-02 14:47:06\",\"reason\":\"missing or invalid refresh token\",\"previous\":\"\"} {\"correlation_id\":\"2e6dfa3d-f07d-4eee-8232-6138133e2fbb\",\"trace_id\":\"41dea86e-0ecc-414e-8303-e60d8ae09976\"}\n[2026-04-27 10:30:52] local.NOTICE: Repairing HubSpot tokens end {\"total\":3,\"fixed\":0,\"failed\":3} {\"correlation_id\":\"2e6dfa3d-f07d-4eee-8232-6138133e2fbb\",\"trace_id\":\"41dea86e-0ecc-414e-8303-e60d8ae09976\"}\n[2026-04-27 10:30:56] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage before starting command {\"command\":\"jiminny:transcription:retry-failed\",\"memoryBeforeCommandInMb\":60.0,\"memoryPeakBeforeCommandInMb\":99.727} {\"correlation_id\":\"a6655a64-38ad-4c65-96f1-f5a294dbb343\",\"trace_id\":\"52cdca32-c0c8-4215-81a2-4263bbde2158\"}\n[2026-04-27 10:30:56] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage before starting command {\"command\":\"conference:pre-meeting-reminder\",\"memoryBeforeCommandInMb\":60.0,\"memoryPeakBeforeCommandInMb\":99.727} {\"correlation_id\":\"ec2d49dd-ab36-4f4e-8a60-7bf5ea384cac\",\"trace_id\":\"b3e89960-9a27-4bf7-8b55-95f5704f9a10\"}\n[2026-04-27 10:30:57] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage for command {\"command\":\"jiminny:transcription:retry-failed\",\"memoryBeforeCommandInMb\":60.0,\"memoryAfterCommandInMB\":62.0,\"memoryPeakBeforeCommandInMb\":99.727,\"memoryPeakAfterCommandInMB\":99.727} {\"correlation_id\":\"a6655a64-38ad-4c65-96f1-f5a294dbb343\",\"trace_id\":\"52cdca32-c0c8-4215-81a2-4263bbde2158\"}\n[2026-04-27 10:30:57] local.INFO: [HubSpot Journal Polling] Getting offset from database {\"offset\":\"\",\"jiminny_team_id\":1} {\"correlation_id\":\"1f928e93-b17e-4518-a1b9-a8280d1d95e8\",\"trace_id\":\"ba203ed6-fe98-4fd7-aa62-8de9fb8ffeca\"}\n[2026-04-27 10:30:57] local.INFO: [HubSpot Journal Command] Starting polling service {\"correlation_id\":\"1f928e93-b17e-4518-a1b9-a8280d1d95e8\",\"trace_id\":\"ba203ed6-fe98-4fd7-aa62-8de9fb8ffeca\"}\n[2026-04-27 10:30:57] local.INFO: [HubSpot Journal Polling] Service starting {\"memory_limit\":\"256M\",\"max_execution_time\":\"0\",\"initial_memory_mb\":62.0} {\"correlation_id\":\"1f928e93-b17e-4518-a1b9-a8280d1d95e8\",\"trace_id\":\"ba203ed6-fe98-4fd7-aa62-8de9fb8ffeca\"}\n[2026-04-27 10:30:57] local.INFO: [HubSpot Journal Polling] Acquired polling lock {\"expires_at\":\"2026-04-27T10:32:57.492763Z\"} {\"correlation_id\":\"1f928e93-b17e-4518-a1b9-a8280d1d95e8\",\"trace_id\":\"ba203ed6-fe98-4fd7-aa62-8de9fb8ffeca\"}\n[2026-04-27 10:30:57] local.INFO: [HubSpot Journal Polling] Getting offset from database {\"offset\":\"\",\"jiminny_team_id\":1} {\"correlation_id\":\"1f928e93-b17e-4518-a1b9-a8280d1d95e8\",\"trace_id\":\"ba203ed6-fe98-4fd7-aa62-8de9fb8ffeca\"}\n[2026-04-27 10:30:57] local.INFO: [HubSpot Journal API] Fetching latest journal entry {\"url\":\"https://api.hubapi.com/webhooks/v4/journal/latest\"} {\"correlation_id\":\"1f928e93-b17e-4518-a1b9-a8280d1d95e8\",\"trace_id\":\"ba203ed6-fe98-4fd7-aa62-8de9fb8ffeca\"}\n[2026-04-27 10:30:57] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage for command {\"command\":\"conference:pre-meeting-reminder\",\"memoryBeforeCommandInMb\":60.0,\"memoryAfterCommandInMB\":62.0,\"memoryPeakBeforeCommandInMb\":99.727,\"memoryPeakAfterCommandInMB\":99.727} {\"correlation_id\":\"ec2d49dd-ab36-4f4e-8a60-7bf5ea384cac\",\"trace_id\":\"b3e89960-9a27-4bf7-8b55-95f5704f9a10\"}\n[2026-04-27 10:30:58] local.INFO: [HubSpot Journal Polling] No data {\"correlation_id\":\"1f928e93-b17e-4518-a1b9-a8280d1d95e8\",\"trace_id\":\"ba203ed6-fe98-4fd7-aa62-8de9fb8ffeca\"}\n[2026-04-27 10:31:02] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage before starting command {\"command\":\"crm:reset-governor\",\"memoryBeforeCommandInMb\":60.0,\"memoryPeakBeforeCommandInMb\":99.727} {\"correlation_id\":\"f261cd75-c916-4f21-b4f4-856fe0ed315c\",\"trace_id\":\"6db8819d-ffaa-4fe8-8ccd-0dcc326b8cdc\"}\n[2026-04-27 10:31:02] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage for command {\"command\":\"crm:reset-governor\",\"memoryBeforeCommandInMb\":60.0,\"memoryAfterCommandInMB\":62.0,\"memoryPeakBeforeCommandInMb\":99.727,\"memoryPeakAfterCommandInMB\":99.727} {\"correlation_id\":\"f261cd75-c916-4f21-b4f4-856fe0ed315c\",\"trace_id\":\"6db8819d-ffaa-4fe8-8ccd-0dcc326b8cdc\"}\n[2026-04-27 10:31:03] local.INFO: [HubSpot Journal Polling] Getting offset from database {\"offset\":\"\",\"jiminny_team_id\":1} {\"correlation_id\":\"1f928e93-b17e-4518-a1b9-a8280d1d95e8\",\"trace_id\":\"ba203ed6-fe98-4fd7-aa62-8de9fb8ffeca\"}\n[2026-04-27 10:31:03] local.INFO: [HubSpot Journal API] Fetching latest journal entry {\"url\":\"https://api.hubapi.com/webhooks/v4/journal/latest\"} {\"correlation_id\":\"1f928e93-b17e-4518-a1b9-a8280d1d95e8\",\"trace_id\":\"ba203ed6-fe98-4fd7-aa62-8de9fb8ffeca\"}\n[2026-04-27 10:31:03] local.INFO: [HubSpot Journal Polling] No data {\"correlation_id\":\"1f928e93-b17e-4518-a1b9-a8280d1d95e8\",\"trace_id\":\"ba203ed6-fe98-4fd7-aa62-8de9fb8ffeca\"}\n[2026-04-27 10:31:06] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage before starting command {\"command\":\"activity:sync\",\"memoryBeforeCommandInMb\":60.0,\"memoryPeakBeforeCommandInMb\":99.727} {\"correlation_id\":\"f9151ed9-60aa-4b2b-b962-9eab5f75405b\",\"trace_id\":\"dba432b8-cb4b-4e76-8684-01d96f91e453\"}\n[2026-04-27 10:31:07] local.INFO: Dispatching activity sync job {\"import_id\":811739,\"provider\":\"twilio-flex\",\"team\":\"jiminny\"} {\"correlation_id\":\"f9151ed9-60aa-4b2b-b962-9eab5f75405b\",\"trace_id\":\"dba432b8-cb4b-4e76-8684-01d96f91e453\"}\n[2026-04-27 10:31:07] local.INFO: Dispatching activity sync job {\"import_id\":811740,\"provider\":\"xant\",\"team\":\"jiminny\"} {\"correlation_id\":\"f9151ed9-60aa-4b2b-b962-9eab5f75405b\",\"trace_id\":\"dba432b8-cb4b-4e76-8684-01d96f91e453\"}\n[2026-04-27 10:31:07] local.INFO: Dispatching activity sync job {\"import_id\":811741,\"provider\":\"apollo\",\"team\":\"jiminny\"} {\"correlation_id\":\"f9151ed9-60aa-4b2b-b962-9eab5f75405b\",\"trace_id\":\"dba432b8-cb4b-4e76-8684-01d96f91e453\"}\n[2026-04-27 10:31:07] local.INFO: Dispatching activity sync job {\"import_id\":811742,\"provider\":\"groove\",\"team\":\"jiminny\"} {\"correlation_id\":\"f9151ed9-60aa-4b2b-b962-9eab5f75405b\",\"trace_id\":\"dba432b8-cb4b-4e76-8684-01d96f91e453\"}\n[2026-04-27 10:31:07] local.INFO: Dispatching activity sync job {\"import_id\":811743,\"provider\":\"twilio-video\",\"team\":\"jiminny\"} {\"correlation_id\":\"f9151ed9-60aa-4b2b-b962-9eab5f75405b\",\"trace_id\":\"dba432b8-cb4b-4e76-8684-01d96f91e453\"}\n[2026-04-27 10:31:07] local.INFO: Dispatching activity sync job {\"import_id\":811744,\"provider\":\"hubspot\",\"team\":\"hubspot\"} {\"correlation_id\":\"f9151ed9-60aa-4b2b-b962-9eab5f75405b\",\"trace_id\":\"dba432b8-cb4b-4e76-8684-01d96f91e453\"}\n[2026-04-27 10:31:07] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage for command {\"command\":\"activity:sync\",\"memoryBeforeCommandInMb\":60.0,\"memoryAfterCommandInMB\":62.0,\"memoryPeakBeforeCommandInMb\":99.727,\"memoryPeakAfterCommandInMB\":99.727} {\"correlation_id\":\"f9151ed9-60aa-4b2b-b962-9eab5f75405b\",\"trace_id\":\"dba432b8-cb4b-4e76-8684-01d96f91e453\"}\n[2026-04-27 10:31:07] local.WARNING: [Salesforce] Account not connected for user {\"userId\":\"cdf8b554-d951-4758-bc2b-c1b85d1cd0b9\",\"account\":null} {\"correlation_id\":\"e7a521c5-fad4-48c2-b880-d572b5d237f4\",\"trace_id\":\"dba432b8-cb4b-4e76-8684-01d96f91e453\"}\n[2026-04-27 10:31:07] local.INFO: [CrmOwnerResolver] Integration owner is not connected, attempting team members {\"crm_provider\":\"salesforce\",\"crm_owner\":3,\"team_id\":1} {\"correlation_id\":\"e7a521c5-fad4-48c2-b880-d572b5d237f4\",\"trace_id\":\"dba432b8-cb4b-4e76-8684-01d96f91e453\"}\n[2026-04-27 10:31:07] local.INFO: [SocialAccountService] Fetching token {\"socialAccountId\":1500,\"provider\":\"salesforce\"} {\"correlation_id\":\"e7a521c5-fad4-48c2-b880-d572b5d237f4\",\"trace_id\":\"dba432b8-cb4b-4e76-8684-01d96f91e453\"}\n[2026-04-27 10:31:07] local.INFO: [SocialAccountService] Token retrieved {\"socialAccountId\":1500,\"provider\":\"salesforce\"} {\"correlation_id\":\"e7a521c5-fad4-48c2-b880-d572b5d237f4\",\"trace_id\":\"dba432b8-cb4b-4e76-8684-01d96f91e453\"}\n[2026-04-27 10:31:07] local.INFO: [EncryptedTokenManager] Generating access token. {\"mode\":\"legacy\"} {\"correlation_id\":\"e7a521c5-fad4-48c2-b880-d572b5d237f4\",\"trace_id\":\"dba432b8-cb4b-4e76-8684-01d96f91e453\"}\n[2026-04-27 10:31:07] local.INFO: [CrmOwnerResolver] TeamMember found with active crm connection {\"crm_provider\":\"salesforce\",\"crm_owner\":143,\"team_id\":1} {\"correlation_id\":\"e7a521c5-fad4-48c2-b880-d572b5d237f4\",\"trace_id\":\"dba432b8-cb4b-4e76-8684-01d96f91e453\"}\n[2026-04-27 10:31:07] local.INFO: [SocialAccountService] Fetching token {\"socialAccountId\":1194,\"provider\":\"twilio-flex\"} {\"correlation_id\":\"e7a521c5-fad4-48c2-b880-d572b5d237f4\",\"trace_id\":\"dba432b8-cb4b-4e76-8684-01d96f91e453\"}\n[2026-04-27 10:31:07] local.INFO: [SocialAccountService] Token retrieved {\"socialAccountId\":1194,\"provider\":\"twilio-flex\"} {\"correlation_id\":\"e7a521c5-fad4-48c2-b880-d572b5d237f4\",\"trace_id\":\"dba432b8-cb4b-4e76-8684-01d96f91e453\"}\n[2026-04-27 10:31:07] local.INFO: [EncryptedTokenManager] Generating access token. {\"mode\":\"legacy\"} {\"correlation_id\":\"e7a521c5-fad4-48c2-b880-d572b5d237f4\",\"trace_id\":\"dba432b8-cb4b-4e76-8684-01d96f91e453\"}\n[2026-04-27 10:31:07] local.INFO: [SyncActivity] Start {\"import_id\":811739,\"provider\":\"twilio-flex\",\"provider_id\":317,\"team\":\"jiminny\",\"team_id\":1} {\"correlation_id\":\"e7a521c5-fad4-48c2-b880-d572b5d237f4\",\"trace_id\":\"dba432b8-cb4b-4e76-8684-01d96f91e453\"}\n[2026-04-27 10:31:07] local.NOTICE: [TwilioFlex] Calls import start {\"correlation_id\":\"e7a521c5-fad4-48c2-b880-d572b5d237f4\",\"trace_id\":\"dba432b8-cb4b-4e76-8684-01d96f91e453\"}\n[2026-04-27 10:31:08] local.ALERT: [SyncActivity] Failed {\"import_id\":811739,\"provider\":\"twilio-flex\",\"provider_id\":317,\"team\":\"jiminny\",\"team_id\":1,\"reason\":\"[HTTP 401] Unable to fetch page: Authenticate\",\"file\":\"/home/jiminny/vendor/twilio/sdk/src/Twilio/Page.php\",\"line\":60} {\"correlation_id\":\"e7a521c5-fad4-48c2-b880-d572b5d237f4\",\"trace_id\":\"dba432b8-cb4b-4e76-8684-01d96f91e453\"}\n[2026-04-27 10:31:08] local.INFO: [HubSpot Journal Polling] Getting offset from database {\"offset\":\"\",\"jiminny_team_id\":1} {\"correlation_id\":\"1f928e93-b17e-4518-a1b9-a8280d1d95e8\",\"trace_id\":\"ba203ed6-fe98-4fd7-aa62-8de9fb8ffeca\"}\n[2026-04-27 10:31:08] local.INFO: [HubSpot Journal API] Fetching latest journal entry {\"url\":\"https://api.hubapi.com/webhooks/v4/journal/latest\"} {\"correlation_id\":\"1f928e93-b17e-4518-a1b9-a8280d1d95e8\",\"trace_id\":\"ba203ed6-fe98-4fd7-aa62-8de9fb8ffeca\"}\n[2026-04-27 10:31:09] local.INFO: [SocialAccountService] Fetching token {\"socialAccountId\":1500,\"provider\":\"salesforce\"} {\"correlation_id\":\"73848601-79bb-4161-91ae-37acf4385d24\",\"trace_id\":\"dba432b8-cb4b-4e76-8684-01d96f91e453\"}\n[2026-04-27 10:31:09] local.INFO: [SocialAccountService] Token retrieved {\"socialAccountId\":1500,\"provider\":\"salesforce\"} {\"correlation_id\":\"73848601-79bb-4161-91ae-37acf4385d24\",\"trace_id\":\"dba432b8-cb4b-4e76-8684-01d96f91e453\"}\n[2026-04-27 10:31:09] local.INFO: [EncryptedTokenManager] Generating access token. {\"mode\":\"legacy\"} {\"correlation_id\":\"73848601-79bb-4161-91ae-37acf4385d24\",\"trace_id\":\"dba432b8-cb4b-4e76-8684-01d96f91e453\"}\n[2026-04-27 10:31:09] local.INFO: [CrmOwnerResolver] Integration owner matched as CRM Owner {\"crm_provider\":\"salesforce\",\"crm_owner\":143,\"team_id\":1} {\"correlation_id\":\"73848601-79bb-4161-91ae-37acf4385d24\",\"trace_id\":\"dba432b8-cb4b-4e76-8684-01d96f91e453\"}\n[2026-04-27 10:31:09] local.INFO: [SocialAccountService] Fetching token {\"socialAccountId\":1500,\"provider\":\"salesforce\"} {\"correlation_id\":\"73848601-79bb-4161-91ae-37acf4385d24\",\"trace_id\":\"dba432b8-cb4b-4e76-8684-01d96f91e453\"}\n[2026-04-27 10:31:09] local.INFO: [HubSpot Journal Polling] No data {\"correlation_id\":\"1f928e93-b17e-4518-a1b9-a8280d1d95e8\",\"trace_id\":\"ba203ed6-fe98-4fd7-aa62-8de9fb8ffeca\"}\n[2026-04-27 10:31:09] local.INFO: [SocialAccountService] Token retrieved {\"socialAccountId\":1500,\"provider\":\"salesforce\"} {\"correlation_id\":\"73848601-79bb-4161-91ae-37acf4385d24\",\"trace_id\":\"dba432b8-cb4b-4e76-8684-01d96f91e453\"}\n[2026-04-27 10:31:09] local.INFO: [EncryptedTokenManager] Generating access token. {\"mode\":\"legacy\"} {\"correlation_id\":\"73848601-79bb-4161-91ae-37acf4385d24\",\"trace_id\":\"dba432b8-cb4b-4e76-8684-01d96f91e453\"}\n[2026-04-27 10:31:09] local.INFO: [SyncActivity] Start {\"import_id\":811740,\"provider\":\"xant\",\"provider_id\":161,\"team\":\"jiminny\",\"team_id\":1} {\"correlation_id\":\"73848601-79bb-4161-91ae-37acf4385d24\",\"trace_id\":\"dba432b8-cb4b-4e76-8684-01d96f91e453\"}\n[2026-04-27 10:31:09] local.INFO: [Salesforce] Performing query {\"query\":\"\n SELECT Playbooks_Call_Date__c,Playbooks_Call_Recording__c,CreatedDate,TaskSubtype,CallType,CallDurationInSeconds,Id,OwnerId,WhoId,WhatId,Priority,ActivityDate,Subject,Description,Status,Type\n FROM Task\n WHERE IsDeleted = false\n AND LastModifiedDate >= :from\n AND LastModifiedDate <= :to\n ORDER BY LastModifiedDate ASC\n LIMIT :limit\",\"params\":{\"from\":\"2026-04-27T10:14:00Z\",\"to\":\"2026-04-27T10:30:00Z\",\"ownerId\":null,\"subType\":null,\"limit\":5000}} {\"correlation_id\":\"73848601-79bb-4161-91ae-37acf4385d24\",\"trace_id\":\"dba432b8-cb4b-4e76-8684-01d96f91e453\"}\n[2026-04-27 10:31:09] local.INFO: [Salesforce] Sending request {\"endpoint\":\"https://jiminny--stagingenv.sandbox.my.salesforce.com/services/data/v50.0/query/?q=%0A++++++++++++SELECT+Playbooks_Call_Date__c%2CPlaybooks_Call_Recording__c%2CCreatedDate%2CTaskSubtype%2CCallType%2CCallDurationInSeconds%2CId%2COwnerId%2CWhoId%2CWhatId%2CPriority%2CActivityDate%2CSubject%2CDescription%2CStatus%2CType%0A++++++++++++++FROM+Task%0A+++++++++++++WHERE+IsDeleted+%3D+false%0A+++++++++++++++AND+LastModifiedDate+%3E%3D+2026-04-27T10%3A14%3A00Z%0A+++++++++++++++AND+LastModifiedDate+%3C%3D+2026-04-27T10%3A30%3A00Z%0A++++++++++ORDER+BY+LastModifiedDate+ASC%0A+++++++++++++LIMIT+5000 GET\",\"team_id\":1} {\"correlation_id\":\"73848601-79bb-4161-91ae-37acf4385d24\",\"trace_id\":\"dba432b8-cb4b-4e76-8684-01d96f91e453\"}\n[2026-04-27 10:31:09] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage before starting command {\"command\":\"mailbox:batch:fail-stalled\",\"memoryBeforeCommandInMb\":60.0,\"memoryPeakBeforeCommandInMb\":99.727} {\"correlation_id\":\"0d30446d-c469-4ade-80dc-f57f997ea610\",\"trace_id\":\"61eea326-3f40-4a50-a5e6-bddd7fa25d27\"}\n[2026-04-27 10:31:09] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage for command {\"command\":\"mailbox:batch:fail-stalled\",\"memoryBeforeCommandInMb\":60.0,\"memoryAfterCommandInMB\":62.0,\"memoryPeakBeforeCommandInMb\":99.727,\"memoryPeakAfterCommandInMB\":99.727} {\"correlation_id\":\"0d30446d-c469-4ade-80dc-f57f997ea610\",\"trace_id\":\"61eea326-3f40-4a50-a5e6-bddd7fa25d27\"}\n[2026-04-27 10:31:09] local.INFO: [Xant (InsideSales)] No calls found. {\"correlation_id\":\"73848601-79bb-4161-91ae-37acf4385d24\",\"trace_id\":\"dba432b8-cb4b-4e76-8684-01d96f91e453\"}\n[2026-04-27 10:31:09] local.INFO: [SyncActivity] End {\"import_id\":811740,\"provider\":\"xant\",\"provider_id\":161,\"team\":\"jiminny\",\"team_id\":1} {\"correlation_id\":\"73848601-79bb-4161-91ae-37acf4385d24\",\"trace_id\":\"dba432b8-cb4b-4e76-8684-01d96f91e453\"}\n[2026-04-27 10:31:09] local.INFO: [SyncActivity] Memory usage {\"import_id\":811740,\"provider\":\"xant\",\"provider_id\":161,\"team\":\"jiminny\",\"team_id\":1,\"memory_usage\":25149472,\"memory_real_usage\":65011712,\"pid\":13616} {\"correlation_id\":\"73848601-79bb-4161-91ae-37acf4385d24\",\"trace_id\":\"dba432b8-cb4b-4e76-8684-01d96f91e453\"}\n[2026-04-27 10:31:09] local.INFO: [SocialAccountService] Fetching token {\"socialAccountId\":1500,\"provider\":\"salesforce\"} {\"correlation_id\":\"11c977fb-9603-4dde-a4d7-f821d47fcdc9\",\"trace_id\":\"dba432b8-cb4b-4e76-8684-01d96f91e453\"}\n[2026-04-27 10:31:09] local.INFO: [SocialAccountService] Token retrieved {\"socialAccountId\":1500,\"provider\":\"salesforce\"} {\"correlation_id\":\"11c977fb-9603-4dde-a4d7-f821d47fcdc9\",\"trace_id\":\"dba432b8-cb4b-4e76-8684-01d96f91e453\"}\n[2026-04-27 10:31:09] local.INFO: [EncryptedTokenManager] Generating access token. {\"mode\":\"legacy\"} {\"correlation_id\":\"11c977fb-9603-4dde-a4d7-f821d47fcdc9\",\"trace_id\":\"dba432b8-cb4b-4e76-8684-01d96f91e453\"}\n[2026-04-27 10:31:09] local.INFO: [CrmOwnerResolver] Integration owner matched as CRM Owner {\"crm_provider\":\"salesforce\",\"crm_owner\":143,\"team_id\":1} {\"correlation_id\":\"11c977fb-9603-4dde-a4d7-f821d47fcdc9\",\"trace_id\":\"dba432b8-cb4b-4e76-8684-01d96f91e453\"}\n[2026-04-27 10:31:09] local.INFO: [SocialAccountService] Fetching token {\"socialAccountId\":1500,\"provider\":\"salesforce\"} {\"correlation_id\":\"11c977fb-9603-4dde-a4d7-f821d47fcdc9\",\"trace_id\":\"dba432b8-cb4b-4e76-8684-01d96f91e453\"}\n[2026-04-27 10:31:09] local.INFO: [SocialAccountService] Token retrieved {\"socialAccountId\":1500,\"provider\":\"salesforce\"} {\"correlation_id\":\"11c977fb-9603-4dde-a4d7-f821d47fcdc9\",\"trace_id\":\"dba432b8-cb4b-4e76-8684-01d96f91e453\"}\n[2026-04-27 10:31:09] local.INFO: [EncryptedTokenManager] Generating access token. {\"mode\":\"legacy\"} {\"correlation_id\":\"11c977fb-9603-4dde-a4d7-f821d47fcdc9\",\"trace_id\":\"dba432b8-cb4b-4e76-8684-01d96f91e453\"}\n[2026-04-27 10:31:09] local.INFO: [SyncActivity] Start {\"import_id\":811741,\"provider\":\"apollo\",\"provider_id\":441,\"team\":\"jiminny\",\"team_id\":1} {\"correlation_id\":\"11c977fb-9603-4dde-a4d7-f821d47fcdc9\",\"trace_id\":\"dba432b8-cb4b-4e76-8684-01d96f91e453\"}\n[2026-04-27 10:31:09] local.INFO: [Salesforce] Performing query {\"query\":\"\n SELECT AccountId,CreatedDate,TaskSubtype,CallType,Id,OwnerId,WhoId,WhatId,Priority,ActivityDate,Subject,Description,Status,Type\n FROM Task\n WHERE IsDeleted = false\n AND LastModifiedDate >= :from\n AND LastModifiedDate <= :to\n ORDER BY LastModifiedDate ASC\n LIMIT :limit\",\"params\":{\"from\":\"2026-04-27T10:14:00Z\",\"to\":\"2026-04-27T10:30:00Z\",\"ownerId\":null,\"subType\":null,\"limit\":5000}} {\"correlation_id\":\"11c977fb-9603-4dde-a4d7-f821d47fcdc9\",\"trace_id\":\"dba432b8-cb4b-4e76-8684-01d96f91e453\"}\n[2026-04-27 10:31:09] local.INFO: [Salesforce] Sending request {\"endpoint\":\"https://jiminny--stagingenv.sandbox.my.salesforce.com/services/data/v50.0/query/?q=%0A++++++++++++SELECT+AccountId%2CCreatedDate%2CTaskSubtype%2CCallType%2CId%2COwnerId%2CWhoId%2CWhatId%2CPriority%2CActivityDate%2CSubject%2CDescription%2CStatus%2CType%0A++++++++++++++FROM+Task%0A+++++++++++++WHERE+IsDeleted+%3D+false%0A+++++++++++++++AND+LastModifiedDate+%3E%3D+2026-04-27T10%3A14%3A00Z%0A+++++++++++++++AND+LastModifiedDate+%3C%3D+2026-04-27T10%3A30%3A00Z%0A++++++++++ORDER+BY+LastModifiedDate+ASC%0A+++++++++++++LIMIT+5000 GET\",\"team_id\":1} {\"correlation_id\":\"11c977fb-9603-4dde-a4d7-f821d47fcdc9\",\"trace_id\":\"dba432b8-cb4b-4e76-8684-01d96f91e453\"}\n[2026-04-27 10:31:10] local.INFO: [Apollo] No calls found. {\"correlation_id\":\"11c977fb-9603-4dde-a4d7-f821d47fcdc9\",\"trace_id\":\"dba432b8-cb4b-4e76-8684-01d96f91e453\"}\n[2026-04-27 10:31:10] local.INFO: [SyncActivity] End {\"import_id\":811741,\"provider\":\"apollo\",\"provider_id\":441,\"team\":\"jiminny\",\"team_id\":1} {\"correlation_id\":\"11c977fb-9603-4dde-a4d7-f821d47fcdc9\",\"trace_id\":\"dba432b8-cb4b-4e76-8684-01d96f91e453\"}\n[2026-04-27 10:31:10] local.INFO: [SyncActivity] Memory usage {\"import_id\":811741,\"provider\":\"apollo\",\"provider_id\":441,\"team\":\"jiminny\",\"team_id\":1,\"memory_usage\":25527752,\"memory_real_usage\":65011712,\"pid\":13616} {\"correlation_id\":\"11c977fb-9603-4dde-a4d7-f821d47fcdc9\",\"trace_id\":\"dba432b8-cb4b-4e76-8684-01d96f91e453\"}\n[2026-04-27 10:31:10] local.INFO: [SocialAccountService] Fetching token {\"socialAccountId\":1500,\"provider\":\"salesforce\"} {\"correlation_id\":\"856f8092-8c5e-409d-8aa6-ffb8efd8bfa1\",\"trace_id\":\"dba432b8-cb4b-4e76-8684-01d96f91e453\"}\n[2026-04-27 10:31:10] local.INFO: [SocialAccountService] Token retrieved {\"socialAccountId\":1500,\"provider\":\"salesforce\"} {\"correlation_id\":\"856f8092-8c5e-409d-8aa6-ffb8efd8bfa1\",\"trace_id\":\"dba432b8-cb4b-4e76-8684-01d96f91e453\"}\n[2026-04-27 10:31:10] local.INFO: [EncryptedTokenManager] Generating access token. {\"mode\":\"legacy\"} {\"correlation_id\":\"856f8092-8c5e-409d-8aa6-ffb8efd8bfa1\",\"trace_id\":\"dba432b8-cb4b-4e76-8684-01d96f91e453\"}\n[2026-04-27 10:31:10] local.INFO: [CrmOwnerResolver] Integration owner matched as CRM Owner {\"crm_provider\":\"salesforce\",\"crm_owner\":143,\"team_id\":1} {\"correlation_id\":\"856f8092-8c5e-409d-8aa6-ffb8efd8bfa1\",\"trace_id\":\"dba432b8-cb4b-4e76-8684-01d96f91e453\"}\n[2026-04-27 10:31:10] local.INFO: [SocialAccountService] Fetching token {\"socialAccountId\":1500,\"provider\":\"salesforce\"} {\"correlation_id\":\"856f8092-8c5e-409d-8aa6-ffb8efd8bfa1\",\"trace_id\":\"dba432b8-cb4b-4e76-8684-01d96f91e453\"}\n[2026-04-27 10:31:10] local.INFO: [SocialAccountService] Token retrieved {\"socialAccountId\":1500,\"provider\":\"salesforce\"} {\"correlation_id\":\"856f8092-8c5e-409d-8aa6-ffb8efd8bfa1\",\"trace_id\":\"dba432b8-cb4b-4e76-8684-01d96f91e453\"}\n[2026-04-27 10:31:10] local.INFO: [EncryptedTokenManager] Generating access token. {\"mode\":\"legacy\"} {\"correlation_id\":\"856f8092-8c5e-409d-8aa6-ffb8efd8bfa1\",\"trace_id\":\"dba432b8-cb4b-4e76-8684-01d96f91e453\"}\n[2026-04-27 10:31:10] local.INFO: [SyncActivity] Start {\"import_id\":811742,\"provider\":\"groove\",\"provider_id\":228,\"team\":\"jiminny\",\"team_id\":1} {\"correlation_id\":\"856f8092-8c5e-409d-8aa6-ffb8efd8bfa1\",\"trace_id\":\"dba432b8-cb4b-4e76-8684-01d96f91e453\"}\n[2026-04-27 10:31:10] local.INFO: [Salesforce] Performing query {\"query\":\"\n SELECT call_recording_url__c,TaskSubtype,CreatedDate,CallType,CallDurationInSeconds,Id,OwnerId,WhoId,WhatId,Priority,ActivityDate,Subject,Description,Status,Type\n FROM Task\n WHERE IsDeleted = false\n AND LastModifiedDate >= :from\n AND LastModifiedDate <= :to\n ORDER BY LastModifiedDate ASC\n LIMIT :limit\",\"params\":{\"from\":\"2026-04-27T10:14:00Z\",\"to\":\"2026-04-27T10:30:00Z\",\"ownerId\":null,\"subType\":null,\"limit\":5000}} {\"correlation_id\":\"856f8092-8c5e-409d-8aa6-ffb8efd8bfa1\",\"trace_id\":\"dba432b8-cb4b-4e76-8684-01d96f91e453\"}\n[2026-04-27 10:31:10] local.INFO: [Salesforce] Sending request {\"endpoint\":\"https://jiminny--stagingenv.sandbox.my.salesforce.com/services/data/v50.0/query/?q=%0A++++++++++++SELECT+call_recording_url__c%2CTaskSubtype%2CCreatedDate%2CCallType%2CCallDurationInSeconds%2CId%2COwnerId%2CWhoId%2CWhatId%2CPriority%2CActivityDate%2CSubject%2CDescription%2CStatus%2CType%0A++++++++++++++FROM+Task%0A+++++++++++++WHERE+IsDeleted+%3D+false%0A+++++++++++++++AND+LastModifiedDate+%3E%3D+2026-04-27T10%3A14%3A00Z%0A+++++++++++++++AND+LastModifiedDate+%3C%3D+2026-04-27T10%3A30%3A00Z%0A++++++++++ORDER+BY+LastModifiedDate+ASC%0A+++++++++++++LIMIT+5000 GET\",\"team_id\":1} {\"correlation_id\":\"856f8092-8c5e-409d-8aa6-ffb8efd8bfa1\",\"trace_id\":\"dba432b8-cb4b-4e76-8684-01d96f91e453\"}\n[2026-04-27 10:31:10] local.ERROR: [Salesforce] Request exception [400] \nSELECT call_recording_url__c,TaskSubtype\n ^\nERROR at Row:1:Column:8\nNo such column 'call_recording_url__c' on entity 'Task'. If you are attempting to use a custom field, be sure to append the '__c' after the custom field name. Please reference your WSDL or the describe call for the appropriate names. {\"url\":\"https://jiminny--stagingenv.sandbox.my.salesforce.com/services/data/v50.0/query/?q=%0A++++++++++++SELECT+call_recording_url__c%2CTaskSubtype%2CCreatedDate%2CCallType%2CCallDurationInSeconds%2CId%2COwnerId%2CWhoId%2CWhatId%2CPriority%2CActivityDate%2CSubject%2CDescription%2CStatus%2CType%0A++++++++++++++FROM+Task%0A+++++++++++++WHERE+IsDeleted+%3D+false%0A+++++++++++++++AND+LastModifiedDate+%3E%3D+2026-04-27T10%3A14%3A00Z%0A+++++++++++++++AND+LastModifiedDate+%3C%3D+2026-04-27T10%3A30%3A00Z%0A++++++++++ORDER+BY+LastModifiedDate+ASC%0A+++++++++++++LIMIT+5000\",\"data\":{\"headers\":{\"Authorization\":\"Bearer 00D2g0000008hH4!AQEAQC41CLa_xP1bAbi9E2OUbAOpW..Wx961OtqTwJ3oWpOKs6yPdm47B4yKRBHbRmmJ.Lvd34sjr0gnAkV8fLb8IZWzIW93\"}},\"response\":{\"GuzzleHttp\\\\Psr7\\\\Stream\":\"[{\\\"message\\\":\\\"\\\\nSELECT call_recording_url__c,TaskSubtype\\\\n ^\\\\nERROR at Row:1:Column:8\\\\nNo such column 'call_recording_url__c' on entity 'Task'. If you are attempting to use a custom field, be sure to append the '__c' after the custom field name. Please reference your WSDL or the describe call for the appropriate names.\\\",\\\"errorCode\\\":\\\"INVALID_FIELD\\\"}]\"},\"fields\":[]} {\"correlation_id\":\"856f8092-8c5e-409d-8aa6-ffb8efd8bfa1\",\"trace_id\":\"dba432b8-cb4b-4e76-8684-01d96f91e453\"}\n[2026-04-27 10:31:10] local.ALERT: [SyncActivity] Failed {\"import_id\":811742,\"provider\":\"groove\",\"provider_id\":228,\"team\":\"jiminny\",\"team_id\":1,\"reason\":\"\nSELECT call_recording_url__c,TaskSubtype\n ^\nERROR at Row:1:Column:8\nNo such column 'call_recording_url__c' on entity 'Task'. If you are attempting to use a custom field, be sure to append the '__c' after the custom field name. Please reference your WSDL or the describe call for the appropriate names.\",\"file\":\"/home/jiminny/app/Services/Crm/Salesforce/Client.php\",\"line\":564} {\"correlation_id\":\"856f8092-8c5e-409d-8aa6-ffb8efd8bfa1\",\"trace_id\":\"dba432b8-cb4b-4e76-8684-01d96f91e453\"}\n[2026-04-27 10:31:10] local.INFO: [SocialAccountService] Fetching token {\"socialAccountId\":1500,\"provider\":\"salesforce\"} {\"correlation_id\":\"0ec6aef6-345c-4602-b6a1-646b95e481c0\",\"trace_id\":\"dba432b8-cb4b-4e76-8684-01d96f91e453\"}\n[2026-04-27 10:31:10] local.INFO: [SocialAccountService] Token retrieved {\"socialAccountId\":1500,\"provider\":\"salesforce\"} {\"correlation_id\":\"0ec6aef6-345c-4602-b6a1-646b95e481c0\",\"trace_id\":\"dba432b8-cb4b-4e76-8684-01d96f91e453\"}\n[2026-04-27 10:31:10] local.INFO: [EncryptedTokenManager] Generating access token. {\"mode\":\"legacy\"} {\"correlation_id\":\"0ec6aef6-345c-4602-b6a1-646b95e481c0\",\"trace_id\":\"dba432b8-cb4b-4e76-8684-01d96f91e453\"}\n[2026-04-27 10:31:10] local.INFO: [CrmOwnerResolver] Integration owner matched as CRM Owner {\"crm_provider\":\"salesforce\",\"crm_owner\":143,\"team_id\":1} {\"correlation_id\":\"0ec6aef6-345c-4602-b6a1-646b95e481c0\",\"trace_id\":\"dba432b8-cb4b-4e76-8684-01d96f91e453\"}\n[2026-04-27 10:31:10] local.INFO: [SocialAccountService] Fetching token {\"socialAccountId\":1500,\"provider\":\"salesforce\"} {\"correlation_id\":\"0ec6aef6-345c-4602-b6a1-646b95e481c0\",\"trace_id\":\"dba432b8-cb4b-4e76-8684-01d96f91e453\"}\n[2026-04-27 10:31:10] local.INFO: [SocialAccountService] Token retrieved {\"socialAccountId\":1500,\"provider\":\"salesforce\"} {\"correlation_id\":\"0ec6aef6-345c-4602-b6a1-646b95e481c0\",\"trace_id\":\"dba432b8-cb4b-4e76-8684-01d96f91e453\"}\n[2026-04-27 10:31:10] local.INFO: [EncryptedTokenManager] Generating access token. {\"mode\":\"legacy\"} {\"correlation_id\":\"0ec6aef6-345c-4602-b6a1-646b95e481c0\",\"trace_id\":\"dba432b8-cb4b-4e76-8684-01d96f91e453\"}\n[2026-04-27 10:31:10] local.INFO: [SyncActivity] Start {\"import_id\":811743,\"provider\":\"twilio-video\",\"provider_id\":243,\"team\":\"jiminny\",\"team_id\":1} {\"correlation_id\":\"0ec6aef6-345c-4602-b6a1-646b95e481c0\",\"trace_id\":\"dba432b8-cb4b-4e76-8684-01d96f91e453\"}\n[2026-04-27 10:31:10] local.INFO: [Salesforce] Performing query {\"query\":\"SELECT Id,OwnerId,WhoId,WhatId,Priority,ActivityDate,Subject,Description,Status,Type,twilio_call_sid__c,Lead_UUID__c,Opportunity__c\n FROM Task\n WHERE Type = 'Video'\n AND isClosed = true\n AND IsDeleted = false\n AND LastModifiedDate >= :from\n AND twilio_call_sid__c != NULL AND LastModifiedDate <= :to ORDER BY LastModifiedDate ASC\n LIMIT :limit\",\"params\":{\"from\":\"2026-04-27T10:14:00Z\",\"to\":\"2026-04-27T10:30:00Z\",\"ownerId\":null,\"subType\":null,\"limit\":5000}} {\"correlation_id\":\"0ec6aef6-345c-4602-b6a1-646b95e481c0\",\"trace_id\":\"dba432b8-cb4b-4e76-8684-01d96f91e453\"}\n[2026-04-27 10:31:10] local.INFO: [Salesforce] Sending request {\"endpoint\":\"https://jiminny--stagingenv.sandbox.my.salesforce.com/services/data/v50.0/query/?q=SELECT+Id%2COwnerId%2CWhoId%2CWhatId%2CPriority%2CActivityDate%2CSubject%2CDescription%2CStatus%2CType%2Ctwilio_call_sid__c%2CLead_UUID__c%2COpportunity__c%0A++++++++++++++FROM+Task%0A++++++++++++WHERE+Type+%3D+%27Video%27%0A++++++++++++++AND+isClosed+%3D+true%0A++++++++++++++AND+IsDeleted+%3D+false%0A++++++++++++++AND+LastModifiedDate+%3E%3D+2026-04-27T10%3A14%3A00Z%0A++++++++++++++AND+twilio_call_sid__c+%21%3D+NULL+AND+LastModifiedDate+%3C%3D+2026-04-27T10%3A30%3A00Z+ORDER+BY+LastModifiedDate+ASC%0A+++++++++++++LIMIT+5000 GET\",\"team_id\":1} {\"correlation_id\":\"0ec6aef6-345c-4602-b6a1-646b95e481c0\",\"trace_id\":\"dba432b8-cb4b-4e76-8684-01d96f91e453\"}\n[2026-04-27 10:31:11] local.INFO: [Twilio Video] No calls found. {\"correlation_id\":\"0ec6aef6-345c-4602-b6a1-646b95e481c0\",\"trace_id\":\"dba432b8-cb4b-4e76-8684-01d96f91e453\"}\n[2026-04-27 10:31:11] local.INFO: [SyncActivity] End {\"import_id\":811743,\"provider\":\"twilio-video\",\"provider_id\":243,\"team\":\"jiminny\",\"team_id\":1} {\"correlation_id\":\"0ec6aef6-345c-4602-b6a1-646b95e481c0\",\"trace_id\":\"dba432b8-cb4b-4e76-8684-01d96f91e453\"}\n[2026-04-27 10:31:11] local.INFO: [SyncActivity] Memory usage {\"import_id\":811743,\"provider\":\"twilio-video\",\"provider_id\":243,\"team\":\"jiminny\",\"team_id\":1,\"memory_usage\":25679680,\"memory_real_usage\":65011712,\"pid\":13616} {\"correlation_id\":\"0ec6aef6-345c-4602-b6a1-646b95e481c0\",\"trace_id\":\"dba432b8-cb4b-4e76-8684-01d96f91e453\"}\n[2026-04-27 10:31:11] local.INFO: [SocialAccountService] Fetching token {\"socialAccountId\":1499,\"provider\":\"hubspot\"} {\"correlation_id\":\"3b9d7e85-b8c2-4ae2-863e-c343c277afdc\",\"trace_id\":\"dba432b8-cb4b-4e76-8684-01d96f91e453\"}\n[2026-04-27 10:31:11] local.INFO: [SocialAccountService] Token retrieved {\"socialAccountId\":1499,\"provider\":\"hubspot\"} {\"correlation_id\":\"3b9d7e85-b8c2-4ae2-863e-c343c277afdc\",\"trace_id\":\"dba432b8-cb4b-4e76-8684-01d96f91e453\"}\n[2026-04-27 10:31:11] local.INFO: [EncryptedTokenManager] Generating access token. {\"mode\":\"legacy\"} {\"correlation_id\":\"3b9d7e85-b8c2-4ae2-863e-c343c277afdc\",\"trace_id\":\"dba432b8-cb4b-4e76-8684-01d96f91e453\"}\n[2026-04-27 10:31:11] local.INFO: [CrmOwnerResolver] Integration owner matched as CRM Owner {\"crm_provider\":\"hubspot\",\"crm_owner\":89,\"team_id\":2} {\"correlation_id\":\"3b9d7e85-b8c2-4ae2-863e-c343c277afdc\",\"trace_id\":\"dba432b8-cb4b-4e76-8684-01d96f91e453\"}\n[2026-04-27 10:31:11] local.INFO: [SocialAccountService] Fetching token {\"socialAccountId\":408,\"provider\":\"hubspot\"} {\"correlation_id\":\"3b9d7e85-b8c2-4ae2-863e-c343c277afdc\",\"trace_id\":\"dba432b8-cb4b-4e76-8684-01d96f91e453\"}\n[2026-04-27 10:31:11] local.INFO: [SocialAccountService] Token needs refreshing {\"socialAccountId\":408,\"provider\":\"hubspot\"} {\"correlation_id\":\"3b9d7e85-b8c2-4ae2-863e-c343c277afdc\",\"trace_id\":\"dba432b8-cb4b-4e76-8684-01d96f91e453\"}\n[2026-04-27 10:31:11] local.INFO: [EncryptedTokenManager] Generating access token. {\"mode\":\"legacy\"} {\"correlation_id\":\"3b9d7e85-b8c2-4ae2-863e-c343c277afdc\",\"trace_id\":\"dba432b8-cb4b-4e76-8684-01d96f91e453\"}\n[2026-04-27 10:31:11] local.INFO: [SocialAccountService] Refreshing token from provider {\"socialAccountId\":408,\"provider\":\"hubspot\",\"refreshToken\":\"de4e47eb985578f4218833e763e31059e88b562e87e10749b3389be2328f0aa7\",\"state\":\"connected\"} {\"correlation_id\":\"3b9d7e85-b8c2-4ae2-863e-c343c277afdc\",\"trace_id\":\"dba432b8-cb4b-4e76-8684-01d96f91e453\"}\n[2026-04-27 10:31:11] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage before starting command {\"command\":\"crm:bullhorn:ping\",\"memoryBeforeCommandInMb\":60.0,\"memoryPeakBeforeCommandInMb\":99.727} {\"correlation_id\":\"a2085168-092a-44b0-8dd2-c8845bc36f86\",\"trace_id\":\"9445425b-6aaf-4a23-9323-ecf85939f967\"}\n[2026-04-27 10:31:11] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage for command {\"command\":\"crm:bullhorn:ping\",\"memoryBeforeCommandInMb\":60.0,\"memoryAfterCommandInMB\":62.0,\"memoryPeakBeforeCommandInMb\":99.727,\"memoryPeakAfterCommandInMB\":99.727} {\"correlation_id\":\"a2085168-092a-44b0-8dd2-c8845bc36f86\",\"trace_id\":\"9445425b-6aaf-4a23-9323-ecf85939f967\"}\n[2026-04-27 10:31:12] local.INFO: [SocialAccountObserver] Saving model {\"correlation_id\":\"3b9d7e85-b8c2-4ae2-863e-c343c277afdc\",\"trace_id\":\"dba432b8-cb4b-4e76-8684-01d96f91e453\"}","depth":4,"bounds":{"left":0.52859044,"top":0.0726257,"width":0.45977393,"height":0.9273743},"on_screen":true,"value":"[2026-04-27 10:26:25] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage before starting command {\"command\":\"mailbox:sync\",\"memoryBeforeCommandInMb\":60.0,\"memoryPeakBeforeCommandInMb\":99.727} {\"correlation_id\":\"7b1ea2fb-8f36-4897-9970-91b4abba2cc7\",\"trace_id\":\"265be7c0-bf8f-4037-a0d2-57df3ef1d7e1\"}\n[2026-04-27 10:26:25] local.INFO: [EmailSchedule] STARTING Inbox Sync {\"host\":\"docker_lamp_1\"} {\"correlation_id\":\"7b1ea2fb-8f36-4897-9970-91b4abba2cc7\",\"trace_id\":\"265be7c0-bf8f-4037-a0d2-57df3ef1d7e1\"}\n[2026-04-27 10:26:25] local.INFO: [EmailSchedule] FINISHED Inbox Sync {\"host\":\"docker_lamp_1\",\"events\":2} {\"correlation_id\":\"7b1ea2fb-8f36-4897-9970-91b4abba2cc7\",\"trace_id\":\"265be7c0-bf8f-4037-a0d2-57df3ef1d7e1\"}\n[2026-04-27 10:26:25] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage for command {\"command\":\"mailbox:sync\",\"memoryBeforeCommandInMb\":60.0,\"memoryAfterCommandInMB\":62.0,\"memoryPeakBeforeCommandInMb\":99.727,\"memoryPeakAfterCommandInMB\":99.727} {\"correlation_id\":\"7b1ea2fb-8f36-4897-9970-91b4abba2cc7\",\"trace_id\":\"265be7c0-bf8f-4037-a0d2-57df3ef1d7e1\"}\n[2026-04-27 10:26:26] local.INFO: [automated-reports] Started {\"correlation_id\":\"d3f79034-397b-4936-9305-bdd99b23f2dd\",\"trace_id\":\"aa0f8702-73eb-4947-a765-2e89244599c3\"}\n[2026-04-27 10:26:26] local.INFO: [automated-reports] Checking conditions {\"isMonday\":true,\"isWeekend\":false,\"isFirstDayOfMonth\":false,\"currentMonth\":4,\"isQuarterlyMonth\":true} {\"correlation_id\":\"d3f79034-397b-4936-9305-bdd99b23f2dd\",\"trace_id\":\"aa0f8702-73eb-4947-a765-2e89244599c3\"}\n[2026-04-27 10:26:26] local.INFO: [automated-reports] Processing daily reports {\"correlation_id\":\"d3f79034-397b-4936-9305-bdd99b23f2dd\",\"trace_id\":\"aa0f8702-73eb-4947-a765-2e89244599c3\"}\n[2026-04-27 10:26:26] local.INFO: [automated-reports] Found 1 daily reports to process {\"correlation_id\":\"d3f79034-397b-4936-9305-bdd99b23f2dd\",\"trace_id\":\"aa0f8702-73eb-4947-a765-2e89244599c3\"}\n[2026-04-27 10:26:26] local.INFO: [automated-reports] Dispatching Generate Report job for report {\"reportUuid\":\"4f6ca2b5-1993-48aa-99ad-b66f19f15d43\",\"teamId\":1,\"frequency\":\"weekly\",\"type\":\"ask_jiminny\"} {\"correlation_id\":\"d3f79034-397b-4936-9305-bdd99b23f2dd\",\"trace_id\":\"aa0f8702-73eb-4947-a765-2e89244599c3\"}\n[2026-04-27 10:26:26] local.INFO: [automated-reports] Processing weekly reports {\"correlation_id\":\"d3f79034-397b-4936-9305-bdd99b23f2dd\",\"trace_id\":\"aa0f8702-73eb-4947-a765-2e89244599c3\"}\n[2026-04-27 10:26:26] local.INFO: [automated-reports] Found 1 weekly reports to process {\"correlation_id\":\"d3f79034-397b-4936-9305-bdd99b23f2dd\",\"trace_id\":\"aa0f8702-73eb-4947-a765-2e89244599c3\"}\n[2026-04-27 10:26:26] local.INFO: [automated-reports] Dispatching Generate Report job for report {\"reportUuid\":\"4f6ca2b5-1993-48aa-99ad-b66f19f15d43\",\"teamId\":1,\"frequency\":\"weekly\",\"type\":\"ask_jiminny\"} {\"correlation_id\":\"d3f79034-397b-4936-9305-bdd99b23f2dd\",\"trace_id\":\"aa0f8702-73eb-4947-a765-2e89244599c3\"}\n[2026-04-27 10:26:26] local.INFO: [automated-reports] Completed {\"correlation_id\":\"d3f79034-397b-4936-9305-bdd99b23f2dd\",\"trace_id\":\"aa0f8702-73eb-4947-a765-2e89244599c3\"}\n[2026-04-27 10:26:26] local.INFO: [HubSpot Journal Polling] Getting offset from database {\"offset\":\"\",\"jiminny_team_id\":1} {\"correlation_id\":\"b453f0f4-0d2c-491c-a935-14c60265ec18\",\"trace_id\":\"a50202d6-5601-46b4-a02e-cf5ac8d061fa\"}\n[2026-04-27 10:26:26] local.INFO: [HubSpot Journal API] Fetching latest journal entry {\"url\":\"https://api.hubapi.com/webhooks/v4/journal/latest\"} {\"correlation_id\":\"b453f0f4-0d2c-491c-a935-14c60265ec18\",\"trace_id\":\"a50202d6-5601-46b4-a02e-cf5ac8d061fa\"}\n[2026-04-27 10:26:26] local.INFO: [AskJiminnyReport:Generate] Started {\"automatedReportUuid\":\"4f6ca2b5-1993-48aa-99ad-b66f19f15d43\"} {\"correlation_id\":\"c6277ddd-09a6-4b04-8d42-bd395fc07d0a\",\"trace_id\":\"aa0f8702-73eb-4947-a765-2e89244599c3\"}\n[2026-04-27 10:26:26] local.INFO: [HubSpot Journal Polling] No data {\"correlation_id\":\"b453f0f4-0d2c-491c-a935-14c60265ec18\",\"trace_id\":\"a50202d6-5601-46b4-a02e-cf5ac8d061fa\"}\n[2026-04-27 10:26:26] local.WARNING: [HubSpot Journal Polling] Maximum empty results reached, stopping {\"empty_results\":5,\"max_empty_results\":5} {\"correlation_id\":\"b453f0f4-0d2c-491c-a935-14c60265ec18\",\"trace_id\":\"a50202d6-5601-46b4-a02e-cf5ac8d061fa\"}\n[2026-04-27 10:26:26] local.WARNING: [HubSpot Journal Polling] Maximum empty results reached, stopping {\"empty_results\":5,\"max_empty_results\":5} {\"correlation_id\":\"b453f0f4-0d2c-491c-a935-14c60265ec18\",\"trace_id\":\"a50202d6-5601-46b4-a02e-cf5ac8d061fa\"}\n[2026-04-27 10:26:26] local.INFO: [HubSpot Journal Polling] Service ending {\"runtime_seconds\":56,\"total_cycles\":5,\"files_downloaded\":0,\"empty_files\":0,\"other_portal_skipped\":0,\"total_events\":0,\"events_per_file\":0,\"avg_api_ms\":273.7,\"avg_download_ms\":0.0,\"avg_transform_ms\":0.0,\"avg_process_ms\":0.0,\"peak_memory_mb\":99.73} {\"correlation_id\":\"b453f0f4-0d2c-491c-a935-14c60265ec18\",\"trace_id\":\"a50202d6-5601-46b4-a02e-cf5ac8d061fa\"}\n[2026-04-27 10:26:26] local.INFO: [HubSpot Journal Polling] Released polling lock {\"correlation_id\":\"b453f0f4-0d2c-491c-a935-14c60265ec18\",\"trace_id\":\"a50202d6-5601-46b4-a02e-cf5ac8d061fa\"}\n[2026-04-27 10:26:27] local.INFO: [AskJiminnyReport] Fetched activity IDs for saved search {\"saved_search_id\":1977,\"user_id\":143,\"activity_count\":0} {\"correlation_id\":\"c6277ddd-09a6-4b04-8d42-bd395fc07d0a\",\"trace_id\":\"aa0f8702-73eb-4947-a765-2e89244599c3\"}\n[2026-04-27 10:26:27] local.INFO: [AskJiminnyReport:Generate] Fetched activity IDs {\"automatedReportUuid\":\"4f6ca2b5-1993-48aa-99ad-b66f19f15d43\",\"activityCount\":0} {\"correlation_id\":\"c6277ddd-09a6-4b04-8d42-bd395fc07d0a\",\"trace_id\":\"aa0f8702-73eb-4947-a765-2e89244599c3\"}\n[2026-04-27 10:26:27] local.INFO: [AskJiminnyReport:Generate] Not enough activities, skipped {\"automatedReportUuid\":\"4f6ca2b5-1993-48aa-99ad-b66f19f15d43\",\"activityCount\":0} {\"correlation_id\":\"c6277ddd-09a6-4b04-8d42-bd395fc07d0a\",\"trace_id\":\"aa0f8702-73eb-4947-a765-2e89244599c3\"}\n[2026-04-27 10:26:27] local.INFO: [AskJiminnyReport:Generate] Dispatched not-generated notifications {\"automatedReportUuid\":\"4f6ca2b5-1993-48aa-99ad-b66f19f15d43\",\"recipientsCount\":1} {\"correlation_id\":\"c6277ddd-09a6-4b04-8d42-bd395fc07d0a\",\"trace_id\":\"aa0f8702-73eb-4947-a765-2e89244599c3\"}\n[2026-04-27 10:26:27] local.INFO: [AskJiminnyReport:Generate] Started {\"automatedReportUuid\":\"4f6ca2b5-1993-48aa-99ad-b66f19f15d43\"} {\"correlation_id\":\"f1d3e4df-9591-487f-ac2a-a43ddb89a466\",\"trace_id\":\"aa0f8702-73eb-4947-a765-2e89244599c3\"}\n[2026-04-27 10:26:27] local.INFO: [AskJiminnyReport] Fetched activity IDs for saved search {\"saved_search_id\":1977,\"user_id\":143,\"activity_count\":0} {\"correlation_id\":\"f1d3e4df-9591-487f-ac2a-a43ddb89a466\",\"trace_id\":\"aa0f8702-73eb-4947-a765-2e89244599c3\"}\n[2026-04-27 10:26:27] local.INFO: [AskJiminnyReport:Generate] Fetched activity IDs {\"automatedReportUuid\":\"4f6ca2b5-1993-48aa-99ad-b66f19f15d43\",\"activityCount\":0} {\"correlation_id\":\"f1d3e4df-9591-487f-ac2a-a43ddb89a466\",\"trace_id\":\"aa0f8702-73eb-4947-a765-2e89244599c3\"}\n[2026-04-27 10:26:27] local.INFO: [AskJiminnyReport:Generate] Not enough activities, skipped {\"automatedReportUuid\":\"4f6ca2b5-1993-48aa-99ad-b66f19f15d43\",\"activityCount\":0} {\"correlation_id\":\"f1d3e4df-9591-487f-ac2a-a43ddb89a466\",\"trace_id\":\"aa0f8702-73eb-4947-a765-2e89244599c3\"}\n[2026-04-27 10:26:27] local.INFO: [AskJiminnyReport:Generate] Dispatched not-generated notifications {\"automatedReportUuid\":\"4f6ca2b5-1993-48aa-99ad-b66f19f15d43\",\"recipientsCount\":1} {\"correlation_id\":\"f1d3e4df-9591-487f-ac2a-a43ddb89a466\",\"trace_id\":\"aa0f8702-73eb-4947-a765-2e89244599c3\"}\n[2026-04-27 10:26:27] local.INFO: [Send Report Not Generated Mail] Email sent {\"uuid\":\"cd5b5081-22f8-4d1a-9d6f-f82aca1121f6\",\"email\":\"lukas.kovalik@jiminny.com\"} {\"correlation_id\":\"4255983f-07a4-4872-9ea6-5491fa32bdd5\",\"trace_id\":\"aa0f8702-73eb-4947-a765-2e89244599c3\"}\n[2026-04-27 10:26:28] local.INFO: [Sync Mailbox] Sync start {\"inbox_id\":59} {\"correlation_id\":\"280d5bf6-c34c-45ea-bea7-05d9c028e9d3\",\"trace_id\":\"265be7c0-bf8f-4037-a0d2-57df3ef1d7e1\"}\n[2026-04-27 10:26:28] local.INFO: [Inbox service] Skipping METADATA SYNC for inbox 59 due to unauthorized access to the mailbox {\"correlation_id\":\"280d5bf6-c34c-45ea-bea7-05d9c028e9d3\",\"trace_id\":\"265be7c0-bf8f-4037-a0d2-57df3ef1d7e1\"}\n[2026-04-27 10:26:28] local.INFO: [Sync Mailbox] Sync complete {\"inbox_id\":59} {\"correlation_id\":\"280d5bf6-c34c-45ea-bea7-05d9c028e9d3\",\"trace_id\":\"265be7c0-bf8f-4037-a0d2-57df3ef1d7e1\"}\n[2026-04-27 10:26:28] local.INFO: [Sync Mailbox] Sync start {\"inbox_id\":212} {\"correlation_id\":\"d3e3eb3d-5c83-4d6e-9592-aaa38c1843a4\",\"trace_id\":\"265be7c0-bf8f-4037-a0d2-57df3ef1d7e1\"}\n[2026-04-27 10:26:28] local.INFO: [SocialAccountService] Fetching token {\"socialAccountId\":1500,\"provider\":\"salesforce\"} {\"correlation_id\":\"d3e3eb3d-5c83-4d6e-9592-aaa38c1843a4\",\"trace_id\":\"265be7c0-bf8f-4037-a0d2-57df3ef1d7e1\"}\n[2026-04-27 10:26:28] local.INFO: [SocialAccountService] Token retrieved {\"socialAccountId\":1500,\"provider\":\"salesforce\"} {\"correlation_id\":\"d3e3eb3d-5c83-4d6e-9592-aaa38c1843a4\",\"trace_id\":\"265be7c0-bf8f-4037-a0d2-57df3ef1d7e1\"}\n[2026-04-27 10:26:28] local.INFO: [EncryptedTokenManager] Generating access token. {\"mode\":\"legacy\"} {\"correlation_id\":\"d3e3eb3d-5c83-4d6e-9592-aaa38c1843a4\",\"trace_id\":\"265be7c0-bf8f-4037-a0d2-57df3ef1d7e1\"}\n[2026-04-27 10:26:28] local.INFO: [CrmOwnerResolver] Integration owner matched as CRM Owner {\"crm_provider\":\"salesforce\",\"crm_owner\":143,\"team_id\":1} {\"correlation_id\":\"d3e3eb3d-5c83-4d6e-9592-aaa38c1843a4\",\"trace_id\":\"265be7c0-bf8f-4037-a0d2-57df3ef1d7e1\"}\n[2026-04-27 10:26:28] local.INFO: [SocialAccountService] Fetching token {\"socialAccountId\":1354,\"provider\":\"google\"} {\"correlation_id\":\"d3e3eb3d-5c83-4d6e-9592-aaa38c1843a4\",\"trace_id\":\"265be7c0-bf8f-4037-a0d2-57df3ef1d7e1\"}\n[2026-04-27 10:26:28] local.INFO: [SocialAccountService] Token retrieved {\"socialAccountId\":1354,\"provider\":\"google\"} {\"correlation_id\":\"d3e3eb3d-5c83-4d6e-9592-aaa38c1843a4\",\"trace_id\":\"265be7c0-bf8f-4037-a0d2-57df3ef1d7e1\"}\n[2026-04-27 10:26:28] local.INFO: [EncryptedTokenManager] Generating access token. {\"mode\":\"legacy\"} {\"correlation_id\":\"d3e3eb3d-5c83-4d6e-9592-aaa38c1843a4\",\"trace_id\":\"265be7c0-bf8f-4037-a0d2-57df3ef1d7e1\"}\n[2026-04-27 10:26:28] local.INFO: [Gmail] Performing incremental sync for inbox 212 using history ID: @1777284288 {\"correlation_id\":\"d3e3eb3d-5c83-4d6e-9592-aaa38c1843a4\",\"trace_id\":\"265be7c0-bf8f-4037-a0d2-57df3ef1d7e1\"}\n[2026-04-27 10:26:28] local.INFO: [Gmail] imported 7 emails via full sync workflow for inbox 212 {\"correlation_id\":\"d3e3eb3d-5c83-4d6e-9592-aaa38c1843a4\",\"trace_id\":\"265be7c0-bf8f-4037-a0d2-57df3ef1d7e1\"}\n[2026-04-27 10:26:28] local.INFO: [Gmail] seeding inbox 212 with last message time : 2026-04-27 10:24:57 {\"correlation_id\":\"d3e3eb3d-5c83-4d6e-9592-aaa38c1843a4\",\"trace_id\":\"265be7c0-bf8f-4037-a0d2-57df3ef1d7e1\"}\n[2026-04-27 10:26:28] local.INFO: [Sync Mailbox] Sync complete {\"inbox_id\":212} {\"correlation_id\":\"d3e3eb3d-5c83-4d6e-9592-aaa38c1843a4\",\"trace_id\":\"265be7c0-bf8f-4037-a0d2-57df3ef1d7e1\"}\n[2026-04-27 10:27:04] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage before starting command {\"command\":\"meeting-bot:schedule-bot\",\"memoryBeforeCommandInMb\":60.0,\"memoryPeakBeforeCommandInMb\":99.727} {\"correlation_id\":\"f55fbf77-7e5f-4c87-accd-011697ca0fa3\",\"trace_id\":\"9bd1c431-26de-4e31-83d8-3129ed9b3b79\"}\n[2026-04-27 10:27:04] local.INFO: [ScheduleBotCommand] Number of activities to be captured: 0 {\"correlation_id\":\"f55fbf77-7e5f-4c87-accd-011697ca0fa3\",\"trace_id\":\"9bd1c431-26de-4e31-83d8-3129ed9b3b79\"}\n[2026-04-27 10:27:04] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage for command {\"command\":\"meeting-bot:schedule-bot\",\"memoryBeforeCommandInMb\":60.0,\"memoryAfterCommandInMB\":62.0,\"memoryPeakBeforeCommandInMb\":99.727,\"memoryPeakAfterCommandInMB\":99.727} {\"correlation_id\":\"f55fbf77-7e5f-4c87-accd-011697ca0fa3\",\"trace_id\":\"9bd1c431-26de-4e31-83d8-3129ed9b3b79\"}\n[2026-04-27 10:27:05] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage before starting command {\"command\":\"dialers:monitor-activities\",\"memoryBeforeCommandInMb\":60.0,\"memoryPeakBeforeCommandInMb\":99.727} {\"correlation_id\":\"da241053-5f3b-4d02-9504-4ef2987cdbd7\",\"trace_id\":\"364d3fc7-0f11-40c6-8697-d18eff2c4cb6\"}\n[2026-04-27 10:27:05] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage for command {\"command\":\"dialers:monitor-activities\",\"memoryBeforeCommandInMb\":60.0,\"memoryAfterCommandInMB\":62.0,\"memoryPeakBeforeCommandInMb\":99.727,\"memoryPeakAfterCommandInMB\":99.727} {\"correlation_id\":\"da241053-5f3b-4d02-9504-4ef2987cdbd7\",\"trace_id\":\"364d3fc7-0f11-40c6-8697-d18eff2c4cb6\"}\n[2026-04-27 10:27:06] local.NOTICE: Monitoring start {\"correlation_id\":\"ad0d871a-a1fd-4725-b2f7-ac8f0fc1857e\",\"trace_id\":\"cb504e84-d924-43a0-82d5-faf1840134ba\"}\n[2026-04-27 10:27:06] local.NOTICE: Monitoring end {\"correlation_id\":\"ad0d871a-a1fd-4725-b2f7-ac8f0fc1857e\",\"trace_id\":\"cb504e84-d924-43a0-82d5-faf1840134ba\"}\n[2026-04-27 10:27:08] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage before starting command {\"command\":\"mailbox:skip-lists:refresh\",\"memoryBeforeCommandInMb\":60.0,\"memoryPeakBeforeCommandInMb\":99.727} {\"correlation_id\":\"b8652f0d-1cee-44ae-a0ea-3d3137a882c1\",\"trace_id\":\"7fe19940-0813-4199-a2a8-958de09bd0ee\"}\n[2026-04-27 10:27:08] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage for command {\"command\":\"mailbox:skip-lists:refresh\",\"memoryBeforeCommandInMb\":60.0,\"memoryAfterCommandInMB\":62.0,\"memoryPeakBeforeCommandInMb\":99.727,\"memoryPeakAfterCommandInMB\":99.727} {\"correlation_id\":\"b8652f0d-1cee-44ae-a0ea-3d3137a882c1\",\"trace_id\":\"7fe19940-0813-4199-a2a8-958de09bd0ee\"}\n[2026-04-27 10:27:09] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage before starting command {\"command\":\"mailbox:batch:process\",\"memoryBeforeCommandInMb\":60.0,\"memoryPeakBeforeCommandInMb\":99.727} {\"correlation_id\":\"06190462-0f47-403f-8e95-ad5d05bff1fe\",\"trace_id\":\"4aa6bed6-05a1-4661-a9c9-edadc61d84ac\"}\n[2026-04-27 10:27:09] local.INFO: [EmailSchedule] STARTING batch process {\"host\":\"docker_lamp_1\"} {\"correlation_id\":\"06190462-0f47-403f-8e95-ad5d05bff1fe\",\"trace_id\":\"4aa6bed6-05a1-4661-a9c9-edadc61d84ac\"}\n[2026-04-27 10:27:09] local.INFO: [EmailSchedule] FINISHED batch process {\"host\":\"docker_lamp_1\",\"processed\":0} {\"correlation_id\":\"06190462-0f47-403f-8e95-ad5d05bff1fe\",\"trace_id\":\"4aa6bed6-05a1-4661-a9c9-edadc61d84ac\"}\n[2026-04-27 10:27:09] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage for command {\"command\":\"mailbox:batch:process\",\"memoryBeforeCommandInMb\":60.0,\"memoryAfterCommandInMB\":62.0,\"memoryPeakBeforeCommandInMb\":99.727,\"memoryPeakAfterCommandInMB\":99.727} {\"correlation_id\":\"06190462-0f47-403f-8e95-ad5d05bff1fe\",\"trace_id\":\"4aa6bed6-05a1-4661-a9c9-edadc61d84ac\"}\n[2026-04-27 10:27:11] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage before starting command {\"command\":\"mailbox:batch:create\",\"memoryBeforeCommandInMb\":60.0,\"memoryPeakBeforeCommandInMb\":99.727} {\"correlation_id\":\"b561c5d5-e6e3-4091-9517-b0458190a360\",\"trace_id\":\"bf9679fb-b785-40cb-b814-04a1dc385a1c\"}\n[2026-04-27 10:27:11] local.INFO: [EmailSchedule] STARTING batch create {\"host\":\"docker_lamp_1\"} {\"correlation_id\":\"b561c5d5-e6e3-4091-9517-b0458190a360\",\"trace_id\":\"bf9679fb-b785-40cb-b814-04a1dc385a1c\"}\n[2026-04-27 10:27:11] local.INFO: [EmailSchedule] FINISHED batch create {\"host\":\"docker_lamp_1\"} {\"correlation_id\":\"b561c5d5-e6e3-4091-9517-b0458190a360\",\"trace_id\":\"bf9679fb-b785-40cb-b814-04a1dc385a1c\"}\n[2026-04-27 10:27:11] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage for command {\"command\":\"mailbox:batch:create\",\"memoryBeforeCommandInMb\":60.0,\"memoryAfterCommandInMB\":62.0,\"memoryPeakBeforeCommandInMb\":99.727,\"memoryPeakAfterCommandInMB\":99.727} {\"correlation_id\":\"b561c5d5-e6e3-4091-9517-b0458190a360\",\"trace_id\":\"bf9679fb-b785-40cb-b814-04a1dc385a1c\"}\n[2026-04-27 10:27:14] local.INFO: [Jiminny\\Jobs\\Mailbox\\CreateBatches] processed 2 inboxes and created 1 batches {\"userId\":null,\"batchSize\":30,\"maxBatches\":1000} {\"correlation_id\":\"98416296-43c2-4834-927e-4a0e86dc99cf\",\"trace_id\":\"bf9679fb-b785-40cb-b814-04a1dc385a1c\"}\n[2026-04-27 10:28:04] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage before starting command {\"command\":\"meeting-bot:schedule-bot\",\"memoryBeforeCommandInMb\":60.0,\"memoryPeakBeforeCommandInMb\":99.727} {\"correlation_id\":\"5852bcf1-af92-4d24-8a2d-08a9d4a5cfac\",\"trace_id\":\"25af5e8c-a148-493e-ac9f-4d53f1960177\"}\n[2026-04-27 10:28:04] local.INFO: [ScheduleBotCommand] Number of activities to be captured: 0 {\"correlation_id\":\"5852bcf1-af92-4d24-8a2d-08a9d4a5cfac\",\"trace_id\":\"25af5e8c-a148-493e-ac9f-4d53f1960177\"}\n[2026-04-27 10:28:04] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage for command {\"command\":\"meeting-bot:schedule-bot\",\"memoryBeforeCommandInMb\":60.0,\"memoryAfterCommandInMB\":62.0,\"memoryPeakBeforeCommandInMb\":99.727,\"memoryPeakAfterCommandInMB\":99.727} {\"correlation_id\":\"5852bcf1-af92-4d24-8a2d-08a9d4a5cfac\",\"trace_id\":\"25af5e8c-a148-493e-ac9f-4d53f1960177\"}\n[2026-04-27 10:28:06] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage before starting command {\"command\":\"dialers:monitor-activities\",\"memoryBeforeCommandInMb\":60.0,\"memoryPeakBeforeCommandInMb\":99.727} {\"correlation_id\":\"cc1cff1f-71b4-4bb1-800f-66e27c0f2b3a\",\"trace_id\":\"c03103a3-9ab8-480b-9753-7c7c6488f246\"}\n[2026-04-27 10:28:06] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage for command {\"command\":\"dialers:monitor-activities\",\"memoryBeforeCommandInMb\":60.0,\"memoryAfterCommandInMB\":62.0,\"memoryPeakBeforeCommandInMb\":99.727,\"memoryPeakAfterCommandInMB\":99.727} {\"correlation_id\":\"cc1cff1f-71b4-4bb1-800f-66e27c0f2b3a\",\"trace_id\":\"c03103a3-9ab8-480b-9753-7c7c6488f246\"}\n[2026-04-27 10:28:09] local.ERROR: Target class [Jiminny\\Repositories\\AjReportsRepository] does not exist. {\"exception\":\"[object] (Illuminate\\\\Contracts\\\\Container\\\\BindingResolutionException(code: 0): Target class [Jiminny\\\\Repositories\\\\AjReportsRepository] does not exist. at /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php:1124)\n[stacktrace]\n#0 /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php(933): Illuminate\\\\Container\\\\Container->build('Jiminny\\\\\\\\Reposit...')\n#1 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Application.php(1078): Illuminate\\\\Container\\\\Container->resolve('Jiminny\\\\\\\\Reposit...', Array, true)\n#2 /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php(864): Illuminate\\\\Foundation\\\\Application->resolve('Jiminny\\\\\\\\Reposit...', Array)\n#3 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Application.php(1058): Illuminate\\\\Container\\\\Container->make('Jiminny\\\\\\\\Reposit...', Array)\n#4 /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php(1336): Illuminate\\\\Foundation\\\\Application->make('Jiminny\\\\\\\\Reposit...')\n#5 /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php(1237): Illuminate\\\\Container\\\\Container->resolveClass(Object(ReflectionParameter))\n#6 /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php(1162): Illuminate\\\\Container\\\\Container->resolveDependencies(Array)\n#7 /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php(933): Illuminate\\\\Container\\\\Container->build('Jiminny\\\\\\\\Http\\\\\\\\Co...')\n#8 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Application.php(1078): Illuminate\\\\Container\\\\Container->resolve('Jiminny\\\\\\\\Http\\\\\\\\Co...', Array, true)\n#9 /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php(864): Illuminate\\\\Foundation\\\\Application->resolve('Jiminny\\\\\\\\Http\\\\\\\\Co...', Array)\n#10 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Application.php(1058): Illuminate\\\\Container\\\\Container->make('Jiminny\\\\\\\\Http\\\\\\\\Co...', Array)\n#11 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Route.php(286): Illuminate\\\\Foundation\\\\Application->make('Jiminny\\\\\\\\Http\\\\\\\\Co...')\n#12 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Route.php(1133): Illuminate\\\\Routing\\\\Route->getController()\n#13 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Route.php(1062): Illuminate\\\\Routing\\\\Route->controllerMiddleware()\n#14 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Router.php(834): Illuminate\\\\Routing\\\\Route->gatherMiddleware()\n#15 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Router.php(816): Illuminate\\\\Routing\\\\Router->gatherRouteMiddleware(Object(Illuminate\\\\Routing\\\\Route))\n#16 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Router.php(800): Illuminate\\\\Routing\\\\Router->runRouteWithinStack(Object(Illuminate\\\\Routing\\\\Route), Object(Illuminate\\\\Http\\\\Request))\n#17 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Router.php(764): Illuminate\\\\Routing\\\\Router->runRoute(Object(Illuminate\\\\Http\\\\Request), Object(Illuminate\\\\Routing\\\\Route))\n#18 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Router.php(753): Illuminate\\\\Routing\\\\Router->dispatchToRoute(Object(Illuminate\\\\Http\\\\Request))\n#19 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Http/Kernel.php(200): Illuminate\\\\Routing\\\\Router->dispatch(Object(Illuminate\\\\Http\\\\Request))\n#20 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(180): Illuminate\\\\Foundation\\\\Http\\\\Kernel->Illuminate\\\\Foundation\\\\Http\\\\{closure}(Object(Illuminate\\\\Http\\\\Request))\n#21 /home/jiminny/vendor/barryvdh/laravel-debugbar/src/Middleware/InjectDebugbar.php(59): Illuminate\\\\Pipeline\\\\Pipeline->Illuminate\\\\Pipeline\\\\{closure}(Object(Illuminate\\\\Http\\\\Request))\n#22 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(219): Barryvdh\\\\Debugbar\\\\Middleware\\\\InjectDebugbar->handle(Object(Illuminate\\\\Http\\\\Request), Object(Closure))\n#23 /home/jiminny/vendor/laravel/framework/src/Illuminate/Http/Middleware/HandleCors.php(74): Illuminate\\\\Pipeline\\\\Pipeline->Illuminate\\\\Pipeline\\\\{closure}(Object(Illuminate\\\\Http\\\\Request))\n#24 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(219): Illuminate\\\\Http\\\\Middleware\\\\HandleCors->handle(Object(Illuminate\\\\Http\\\\Request), Object(Closure))\n#25 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Http/Middleware/TransformsRequest.php(21): Illuminate\\\\Pipeline\\\\Pipeline->Illuminate\\\\Pipeline\\\\{closure}(Object(Illuminate\\\\Http\\\\Request))\n#26 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Http/Middleware/TrimStrings.php(51): Illuminate\\\\Foundation\\\\Http\\\\Middleware\\\\TransformsRequest->handle(Object(Illuminate\\\\Http\\\\Request), Object(Closure))\n#27 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(219): Illuminate\\\\Foundation\\\\Http\\\\Middleware\\\\TrimStrings->handle(Object(Illuminate\\\\Http\\\\Request), Object(Closure))\n#28 /home/jiminny/vendor/bepsvpt/secure-headers/src/SecureHeadersMiddleware.php(18): Illuminate\\\\Pipeline\\\\Pipeline->Illuminate\\\\Pipeline\\\\{closure}(Object(Illuminate\\\\Http\\\\Request))\n#29 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(219): Bepsvpt\\\\SecureHeaders\\\\SecureHeadersMiddleware->handle(Object(Illuminate\\\\Http\\\\Request), Object(Closure))\n#30 /home/jiminny/app/Http/Middleware/SentryContext.php(60): Illuminate\\\\Pipeline\\\\Pipeline->Illuminate\\\\Pipeline\\\\{closure}(Object(Illuminate\\\\Http\\\\Request))\n#31 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(219): Jiminny\\\\Http\\\\Middleware\\\\SentryContext->handle(Object(Illuminate\\\\Http\\\\Request), Object(Closure))\n#32 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Http/Middleware/PreventRequestsDuringMaintenance.php(109): Illuminate\\\\Pipeline\\\\Pipeline->Illuminate\\\\Pipeline\\\\{closure}(Object(Illuminate\\\\Http\\\\Request))\n#33 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(219): Illuminate\\\\Foundation\\\\Http\\\\Middleware\\\\PreventRequestsDuringMaintenance->handle(Object(Illuminate\\\\Http\\\\Request), Object(Closure))\n#34 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Http/Middleware/InvokeDeferredCallbacks.php(22): Illuminate\\\\Pipeline\\\\Pipeline->Illuminate\\\\Pipeline\\\\{closure}(Object(Illuminate\\\\Http\\\\Request))\n#35 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(219): Illuminate\\\\Foundation\\\\Http\\\\Middleware\\\\InvokeDeferredCallbacks->handle(Object(Illuminate\\\\Http\\\\Request), Object(Closure))\n#36 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(137): Illuminate\\\\Pipeline\\\\Pipeline->Illuminate\\\\Pipeline\\\\{closure}(Object(Illuminate\\\\Http\\\\Request))\n#37 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Http/Kernel.php(175): Illuminate\\\\Pipeline\\\\Pipeline->then(Object(Closure))\n#38 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Http/Kernel.php(144): Illuminate\\\\Foundation\\\\Http\\\\Kernel->sendRequestThroughRouter(Object(Illuminate\\\\Http\\\\Request))\n#39 /home/jiminny/public/index.php(51): Illuminate\\\\Foundation\\\\Http\\\\Kernel->handle(Object(Illuminate\\\\Http\\\\Request))\n#40 {main}\n\n[previous exception] [object] (ReflectionException(code: -1): Class \\\"Jiminny\\\\Repositories\\\\AjReportsRepository\\\" does not exist at /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php:1122)\n[stacktrace]\n#0 /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php(1122): ReflectionClass->__construct('Jiminny\\\\\\\\Reposit...')\n#1 /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php(933): Illuminate\\\\Container\\\\Container->build('Jiminny\\\\\\\\Reposit...')\n#2 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Application.php(1078): Illuminate\\\\Container\\\\Container->resolve('Jiminny\\\\\\\\Reposit...', Array, true)\n#3 /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php(864): Illuminate\\\\Foundation\\\\Application->resolve('Jiminny\\\\\\\\Reposit...', Array)\n#4 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Application.php(1058): Illuminate\\\\Container\\\\Container->make('Jiminny\\\\\\\\Reposit...', Array)\n#5 /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php(1336): Illuminate\\\\Foundation\\\\Application->make('Jiminny\\\\\\\\Reposit...')\n#6 /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php(1237): Illuminate\\\\Container\\\\Container->resolveClass(Object(ReflectionParameter))\n#7 /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php(1162): Illuminate\\\\Container\\\\Container->resolveDependencies(Array)\n#8 /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php(933): Illuminate\\\\Container\\\\Container->build('Jiminny\\\\\\\\Http\\\\\\\\Co...')\n#9 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Application.php(1078): Illuminate\\\\Container\\\\Container->resolve('Jiminny\\\\\\\\Http\\\\\\\\Co...', Array, true)\n#10 /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php(864): Illuminate\\\\Foundation\\\\Application->resolve('Jiminny\\\\\\\\Http\\\\\\\\Co...', Array)\n#11 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Application.php(1058): Illuminate\\\\Container\\\\Container->make('Jiminny\\\\\\\\Http\\\\\\\\Co...', Array)\n#12 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Route.php(286): Illuminate\\\\Foundation\\\\Application->make('Jiminny\\\\\\\\Http\\\\\\\\Co...')\n#13 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Route.php(1133): Illuminate\\\\Routing\\\\Route->getController()\n#14 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Route.php(1062): Illuminate\\\\Routing\\\\Route->controllerMiddleware()\n#15 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Router.php(834): Illuminate\\\\Routing\\\\Route->gatherMiddleware()\n#16 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Router.php(816): Illuminate\\\\Routing\\\\Router->gatherRouteMiddleware(Object(Illuminate\\\\Routing\\\\Route))\n#17 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Router.php(800): Illuminate\\\\Routing\\\\Router->runRouteWithinStack(Object(Illuminate\\\\Routing\\\\Route), Object(Illuminate\\\\Http\\\\Request))\n#18 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Router.php(764): Illuminate\\\\Routing\\\\Router->runRoute(Object(Illuminate\\\\Http\\\\Request), Object(Illuminate\\\\Routing\\\\Route))\n#19 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Router.php(753): Illuminate\\\\Routing\\\\Router->dispatchToRoute(Object(Illuminate\\\\Http\\\\Request))\n#20 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Http/Kernel.php(200): Illuminate\\\\Routing\\\\Router->dispatch(Object(Illuminate\\\\Http\\\\Request))\n#21 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(180): Illuminate\\\\Foundation\\\\Http\\\\Kernel->Illuminate\\\\Foundation\\\\Http\\\\{closure}(Object(Illuminate\\\\Http\\\\Request))\n#22 /home/jiminny/vendor/barryvdh/laravel-debugbar/src/Middleware/InjectDebugbar.php(59): Illuminate\\\\Pipeline\\\\Pipeline->Illuminate\\\\Pipeline\\\\{closure}(Object(Illuminate\\\\Http\\\\Request))\n#23 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(219): Barryvdh\\\\Debugbar\\\\Middleware\\\\InjectDebugbar->handle(Object(Illuminate\\\\Http\\\\Request), Object(Closure))\n#24 /home/jiminny/vendor/laravel/framework/src/Illuminate/Http/Middleware/HandleCors.php(74): Illuminate\\\\Pipeline\\\\Pipeline->Illuminate\\\\Pipeline\\\\{closure}(Object(Illuminate\\\\Http\\\\Request))\n#25 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(219): Illuminate\\\\Http\\\\Middleware\\\\HandleCors->handle(Object(Illuminate\\\\Http\\\\Request), Object(Closure))\n#26 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Http/Middleware/TransformsRequest.php(21): Illuminate\\\\Pipeline\\\\Pipeline->Illuminate\\\\Pipeline\\\\{closure}(Object(Illuminate\\\\Http\\\\Request))\n#27 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Http/Middleware/TrimStrings.php(51): Illuminate\\\\Foundation\\\\Http\\\\Middleware\\\\TransformsRequest->handle(Object(Illuminate\\\\Http\\\\Request), Object(Closure))\n#28 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(219): Illuminate\\\\Foundation\\\\Http\\\\Middleware\\\\TrimStrings->handle(Object(Illuminate\\\\Http\\\\Request), Object(Closure))\n#29 /home/jiminny/vendor/bepsvpt/secure-headers/src/SecureHeadersMiddleware.php(18): Illuminate\\\\Pipeline\\\\Pipeline->Illuminate\\\\Pipeline\\\\{closure}(Object(Illuminate\\\\Http\\\\Request))\n#30 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(219): Bepsvpt\\\\SecureHeaders\\\\SecureHeadersMiddleware->handle(Object(Illuminate\\\\Http\\\\Request), Object(Closure))\n#31 /home/jiminny/app/Http/Middleware/SentryContext.php(60): Illuminate\\\\Pipeline\\\\Pipeline->Illuminate\\\\Pipeline\\\\{closure}(Object(Illuminate\\\\Http\\\\Request))\n#32 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(219): Jiminny\\\\Http\\\\Middleware\\\\SentryContext->handle(Object(Illuminate\\\\Http\\\\Request), Object(Closure))\n#33 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Http/Middleware/PreventRequestsDuringMaintenance.php(109): Illuminate\\\\Pipeline\\\\Pipeline->Illuminate\\\\Pipeline\\\\{closure}(Object(Illuminate\\\\Http\\\\Request))\n#34 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(219): Illuminate\\\\Foundation\\\\Http\\\\Middleware\\\\PreventRequestsDuringMaintenance->handle(Object(Illuminate\\\\Http\\\\Request), Object(Closure))\n#35 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Http/Middleware/InvokeDeferredCallbacks.php(22): Illuminate\\\\Pipeline\\\\Pipeline->Illuminate\\\\Pipeline\\\\{closure}(Object(Illuminate\\\\Http\\\\Request))\n#36 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(219): Illuminate\\\\Foundation\\\\Http\\\\Middleware\\\\InvokeDeferredCallbacks->handle(Object(Illuminate\\\\Http\\\\Request), Object(Closure))\n#37 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(137): Illuminate\\\\Pipeline\\\\Pipeline->Illuminate\\\\Pipeline\\\\{closure}(Object(Illuminate\\\\Http\\\\Request))\n#38 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Http/Kernel.php(175): Illuminate\\\\Pipeline\\\\Pipeline->then(Object(Closure))\n#39 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Http/Kernel.php(144): Illuminate\\\\Foundation\\\\Http\\\\Kernel->sendRequestThroughRouter(Object(Illuminate\\\\Http\\\\Request))\n#40 /home/jiminny/public/index.php(51): Illuminate\\\\Foundation\\\\Http\\\\Kernel->handle(Object(Illuminate\\\\Http\\\\Request))\n#41 {main}\n\"} {\"correlation_id\":\"dfad8817-1dde-4622-8320-f5fc1616a8fe\",\"trace_id\":\"d21232b2-60fb-4ca0-838c-ba42e61593f9\"}\n[2026-04-27 10:28:09] local.NOTICE: Monitoring start {\"correlation_id\":\"93744c67-4444-41ff-aaad-6aa9cafe294e\",\"trace_id\":\"80e396b0-b1ac-4ec8-838c-00021f98072c\"}\n[2026-04-27 10:28:09] local.NOTICE: Monitoring end {\"correlation_id\":\"93744c67-4444-41ff-aaad-6aa9cafe294e\",\"trace_id\":\"80e396b0-b1ac-4ec8-838c-00021f98072c\"}\n[2026-04-27 10:28:10] local.ERROR: Target class [Jiminny\\Repositories\\AjReportsRepository] does not exist. {\"exception\":\"[object] (Illuminate\\\\Contracts\\\\Container\\\\BindingResolutionException(code: 0): Target class [Jiminny\\\\Repositories\\\\AjReportsRepository] does not exist. at /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php:1124)\n[stacktrace]\n#0 /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php(933): Illuminate\\\\Container\\\\Container->build('Jiminny\\\\\\\\Reposit...')\n#1 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Application.php(1078): Illuminate\\\\Container\\\\Container->resolve('Jiminny\\\\\\\\Reposit...', Array, true)\n#2 /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php(864): Illuminate\\\\Foundation\\\\Application->resolve('Jiminny\\\\\\\\Reposit...', Array)\n#3 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Application.php(1058): Illuminate\\\\Container\\\\Container->make('Jiminny\\\\\\\\Reposit...', Array)\n#4 /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php(1336): Illuminate\\\\Foundation\\\\Application->make('Jiminny\\\\\\\\Reposit...')\n#5 /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php(1237): Illuminate\\\\Container\\\\Container->resolveClass(Object(ReflectionParameter))\n#6 /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php(1162): Illuminate\\\\Container\\\\Container->resolveDependencies(Array)\n#7 /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php(933): Illuminate\\\\Container\\\\Container->build('Jiminny\\\\\\\\Http\\\\\\\\Co...')\n#8 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Application.php(1078): Illuminate\\\\Container\\\\Container->resolve('Jiminny\\\\\\\\Http\\\\\\\\Co...', Array, true)\n#9 /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php(864): Illuminate\\\\Foundation\\\\Application->resolve('Jiminny\\\\\\\\Http\\\\\\\\Co...', Array)\n#10 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Application.php(1058): Illuminate\\\\Container\\\\Container->make('Jiminny\\\\\\\\Http\\\\\\\\Co...', Array)\n#11 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Route.php(286): Illuminate\\\\Foundation\\\\Application->make('Jiminny\\\\\\\\Http\\\\\\\\Co...')\n#12 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Route.php(1133): Illuminate\\\\Routing\\\\Route->getController()\n#13 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Route.php(1062): Illuminate\\\\Routing\\\\Route->controllerMiddleware()\n#14 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Router.php(834): Illuminate\\\\Routing\\\\Route->gatherMiddleware()\n#15 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Router.php(816): Illuminate\\\\Routing\\\\Router->gatherRouteMiddleware(Object(Illuminate\\\\Routing\\\\Route))\n#16 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Router.php(800): Illuminate\\\\Routing\\\\Router->runRouteWithinStack(Object(Illuminate\\\\Routing\\\\Route), Object(Illuminate\\\\Http\\\\Request))\n#17 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Router.php(764): Illuminate\\\\Routing\\\\Router->runRoute(Object(Illuminate\\\\Http\\\\Request), Object(Illuminate\\\\Routing\\\\Route))\n#18 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Router.php(753): Illuminate\\\\Routing\\\\Router->dispatchToRoute(Object(Illuminate\\\\Http\\\\Request))\n#19 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Http/Kernel.php(200): Illuminate\\\\Routing\\\\Router->dispatch(Object(Illuminate\\\\Http\\\\Request))\n#20 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(180): Illuminate\\\\Foundation\\\\Http\\\\Kernel->Illuminate\\\\Foundation\\\\Http\\\\{closure}(Object(Illuminate\\\\Http\\\\Request))\n#21 /home/jiminny/vendor/barryvdh/laravel-debugbar/src/Middleware/InjectDebugbar.php(59): Illuminate\\\\Pipeline\\\\Pipeline->Illuminate\\\\Pipeline\\\\{closure}(Object(Illuminate\\\\Http\\\\Request))\n#22 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(219): Barryvdh\\\\Debugbar\\\\Middleware\\\\InjectDebugbar->handle(Object(Illuminate\\\\Http\\\\Request), Object(Closure))\n#23 /home/jiminny/vendor/laravel/framework/src/Illuminate/Http/Middleware/HandleCors.php(74): Illuminate\\\\Pipeline\\\\Pipeline->Illuminate\\\\Pipeline\\\\{closure}(Object(Illuminate\\\\Http\\\\Request))\n#24 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(219): Illuminate\\\\Http\\\\Middleware\\\\HandleCors->handle(Object(Illuminate\\\\Http\\\\Request), Object(Closure))\n#25 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Http/Middleware/TransformsRequest.php(21): Illuminate\\\\Pipeline\\\\Pipeline->Illuminate\\\\Pipeline\\\\{closure}(Object(Illuminate\\\\Http\\\\Request))\n#26 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Http/Middleware/TrimStrings.php(51): Illuminate\\\\Foundation\\\\Http\\\\Middleware\\\\TransformsRequest->handle(Object(Illuminate\\\\Http\\\\Request), Object(Closure))\n#27 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(219): Illuminate\\\\Foundation\\\\Http\\\\Middleware\\\\TrimStrings->handle(Object(Illuminate\\\\Http\\\\Request), Object(Closure))\n#28 /home/jiminny/vendor/bepsvpt/secure-headers/src/SecureHeadersMiddleware.php(18): Illuminate\\\\Pipeline\\\\Pipeline->Illuminate\\\\Pipeline\\\\{closure}(Object(Illuminate\\\\Http\\\\Request))\n#29 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(219): Bepsvpt\\\\SecureHeaders\\\\SecureHeadersMiddleware->handle(Object(Illuminate\\\\Http\\\\Request), Object(Closure))\n#30 /home/jiminny/app/Http/Middleware/SentryContext.php(60): Illuminate\\\\Pipeline\\\\Pipeline->Illuminate\\\\Pipeline\\\\{closure}(Object(Illuminate\\\\Http\\\\Request))\n#31 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(219): Jiminny\\\\Http\\\\Middleware\\\\SentryContext->handle(Object(Illuminate\\\\Http\\\\Request), Object(Closure))\n#32 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Http/Middleware/PreventRequestsDuringMaintenance.php(109): Illuminate\\\\Pipeline\\\\Pipeline->Illuminate\\\\Pipeline\\\\{closure}(Object(Illuminate\\\\Http\\\\Request))\n#33 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(219): Illuminate\\\\Foundation\\\\Http\\\\Middleware\\\\PreventRequestsDuringMaintenance->handle(Object(Illuminate\\\\Http\\\\Request), Object(Closure))\n#34 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Http/Middleware/InvokeDeferredCallbacks.php(22): Illuminate\\\\Pipeline\\\\Pipeline->Illuminate\\\\Pipeline\\\\{closure}(Object(Illuminate\\\\Http\\\\Request))\n#35 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(219): Illuminate\\\\Foundation\\\\Http\\\\Middleware\\\\InvokeDeferredCallbacks->handle(Object(Illuminate\\\\Http\\\\Request), Object(Closure))\n#36 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(137): Illuminate\\\\Pipeline\\\\Pipeline->Illuminate\\\\Pipeline\\\\{closure}(Object(Illuminate\\\\Http\\\\Request))\n#37 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Http/Kernel.php(175): Illuminate\\\\Pipeline\\\\Pipeline->then(Object(Closure))\n#38 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Http/Kernel.php(144): Illuminate\\\\Foundation\\\\Http\\\\Kernel->sendRequestThroughRouter(Object(Illuminate\\\\Http\\\\Request))\n#39 /home/jiminny/public/index.php(51): Illuminate\\\\Foundation\\\\Http\\\\Kernel->handle(Object(Illuminate\\\\Http\\\\Request))\n#40 {main}\n\n[previous exception] [object] (ReflectionException(code: -1): Class \\\"Jiminny\\\\Repositories\\\\AjReportsRepository\\\" does not exist at /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php:1122)\n[stacktrace]\n#0 /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php(1122): ReflectionClass->__construct('Jiminny\\\\\\\\Reposit...')\n#1 /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php(933): Illuminate\\\\Container\\\\Container->build('Jiminny\\\\\\\\Reposit...')\n#2 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Application.php(1078): Illuminate\\\\Container\\\\Container->resolve('Jiminny\\\\\\\\Reposit...', Array, true)\n#3 /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php(864): Illuminate\\\\Foundation\\\\Application->resolve('Jiminny\\\\\\\\Reposit...', Array)\n#4 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Application.php(1058): Illuminate\\\\Container\\\\Container->make('Jiminny\\\\\\\\Reposit...', Array)\n#5 /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php(1336): Illuminate\\\\Foundation\\\\Application->make('Jiminny\\\\\\\\Reposit...')\n#6 /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php(1237): Illuminate\\\\Container\\\\Container->resolveClass(Object(ReflectionParameter))\n#7 /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php(1162): Illuminate\\\\Container\\\\Container->resolveDependencies(Array)\n#8 /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php(933): Illuminate\\\\Container\\\\Container->build('Jiminny\\\\\\\\Http\\\\\\\\Co...')\n#9 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Application.php(1078): Illuminate\\\\Container\\\\Container->resolve('Jiminny\\\\\\\\Http\\\\\\\\Co...', Array, true)\n#10 /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php(864): Illuminate\\\\Foundation\\\\Application->resolve('Jiminny\\\\\\\\Http\\\\\\\\Co...', Array)\n#11 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Application.php(1058): Illuminate\\\\Container\\\\Container->make('Jiminny\\\\\\\\Http\\\\\\\\Co...', Array)\n#12 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Route.php(286): Illuminate\\\\Foundation\\\\Application->make('Jiminny\\\\\\\\Http\\\\\\\\Co...')\n#13 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Route.php(1133): Illuminate\\\\Routing\\\\Route->getController()\n#14 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Route.php(1062): Illuminate\\\\Routing\\\\Route->controllerMiddleware()\n#15 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Router.php(834): Illuminate\\\\Routing\\\\Route->gatherMiddleware()\n#16 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Router.php(816): Illuminate\\\\Routing\\\\Router->gatherRouteMiddleware(Object(Illuminate\\\\Routing\\\\Route))\n#17 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Router.php(800): Illuminate\\\\Routing\\\\Router->runRouteWithinStack(Object(Illuminate\\\\Routing\\\\Route), Object(Illuminate\\\\Http\\\\Request))\n#18 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Router.php(764): Illuminate\\\\Routing\\\\Router->runRoute(Object(Illuminate\\\\Http\\\\Request), Object(Illuminate\\\\Routing\\\\Route))\n#19 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Router.php(753): Illuminate\\\\Routing\\\\Router->dispatchToRoute(Object(Illuminate\\\\Http\\\\Request))\n#20 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Http/Kernel.php(200): Illuminate\\\\Routing\\\\Router->dispatch(Object(Illuminate\\\\Http\\\\Request))\n#21 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(180): Illuminate\\\\Foundation\\\\Http\\\\Kernel->Illuminate\\\\Foundation\\\\Http\\\\{closure}(Object(Illuminate\\\\Http\\\\Request))\n#22 /home/jiminny/vendor/barryvdh/laravel-debugbar/src/Middleware/InjectDebugbar.php(59): Illuminate\\\\Pipeline\\\\Pipeline->Illuminate\\\\Pipeline\\\\{closure}(Object(Illuminate\\\\Http\\\\Request))\n#23 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(219): Barryvdh\\\\Debugbar\\\\Middleware\\\\InjectDebugbar->handle(Object(Illuminate\\\\Http\\\\Request), Object(Closure))\n#24 /home/jiminny/vendor/laravel/framework/src/Illuminate/Http/Middleware/HandleCors.php(74): Illuminate\\\\Pipeline\\\\Pipeline->Illuminate\\\\Pipeline\\\\{closure}(Object(Illuminate\\\\Http\\\\Request))\n#25 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(219): Illuminate\\\\Http\\\\Middleware\\\\HandleCors->handle(Object(Illuminate\\\\Http\\\\Request), Object(Closure))\n#26 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Http/Middleware/TransformsRequest.php(21): Illuminate\\\\Pipeline\\\\Pipeline->Illuminate\\\\Pipeline\\\\{closure}(Object(Illuminate\\\\Http\\\\Request))\n#27 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Http/Middleware/TrimStrings.php(51): Illuminate\\\\Foundation\\\\Http\\\\Middleware\\\\TransformsRequest->handle(Object(Illuminate\\\\Http\\\\Request), Object(Closure))\n#28 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(219): Illuminate\\\\Foundation\\\\Http\\\\Middleware\\\\TrimStrings->handle(Object(Illuminate\\\\Http\\\\Request), Object(Closure))\n#29 /home/jiminny/vendor/bepsvpt/secure-headers/src/SecureHeadersMiddleware.php(18): Illuminate\\\\Pipeline\\\\Pipeline->Illuminate\\\\Pipeline\\\\{closure}(Object(Illuminate\\\\Http\\\\Request))\n#30 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(219): Bepsvpt\\\\SecureHeaders\\\\SecureHeadersMiddleware->handle(Object(Illuminate\\\\Http\\\\Request), Object(Closure))\n#31 /home/jiminny/app/Http/Middleware/SentryContext.php(60): Illuminate\\\\Pipeline\\\\Pipeline->Illuminate\\\\Pipeline\\\\{closure}(Object(Illuminate\\\\Http\\\\Request))\n#32 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(219): Jiminny\\\\Http\\\\Middleware\\\\SentryContext->handle(Object(Illuminate\\\\Http\\\\Request), Object(Closure))\n#33 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Http/Middleware/PreventRequestsDuringMaintenance.php(109): Illuminate\\\\Pipeline\\\\Pipeline->Illuminate\\\\Pipeline\\\\{closure}(Object(Illuminate\\\\Http\\\\Request))\n#34 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(219): Illuminate\\\\Foundation\\\\Http\\\\Middleware\\\\PreventRequestsDuringMaintenance->handle(Object(Illuminate\\\\Http\\\\Request), Object(Closure))\n#35 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Http/Middleware/InvokeDeferredCallbacks.php(22): Illuminate\\\\Pipeline\\\\Pipeline->Illuminate\\\\Pipeline\\\\{closure}(Object(Illuminate\\\\Http\\\\Request))\n#36 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(219): Illuminate\\\\Foundation\\\\Http\\\\Middleware\\\\InvokeDeferredCallbacks->handle(Object(Illuminate\\\\Http\\\\Request), Object(Closure))\n#37 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(137): Illuminate\\\\Pipeline\\\\Pipeline->Illuminate\\\\Pipeline\\\\{closure}(Object(Illuminate\\\\Http\\\\Request))\n#38 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Http/Kernel.php(175): Illuminate\\\\Pipeline\\\\Pipeline->then(Object(Closure))\n#39 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Http/Kernel.php(144): Illuminate\\\\Foundation\\\\Http\\\\Kernel->sendRequestThroughRouter(Object(Illuminate\\\\Http\\\\Request))\n#40 /home/jiminny/public/index.php(51): Illuminate\\\\Foundation\\\\Http\\\\Kernel->handle(Object(Illuminate\\\\Http\\\\Request))\n#41 {main}\n\"} {\"correlation_id\":\"de768781-5c05-418c-baf3-79f77a8c8694\",\"trace_id\":\"fec74062-3f6d-4cfc-968a-1bb285496db3\"}\n[2026-04-27 10:28:12] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage before starting command {\"command\":\"mailbox:skip-lists:refresh\",\"memoryBeforeCommandInMb\":60.0,\"memoryPeakBeforeCommandInMb\":99.727} {\"correlation_id\":\"907d15ed-8c84-41a1-a473-590eebb55dbc\",\"trace_id\":\"ec7bd353-516e-491d-89dd-de44136c1672\"}\n[2026-04-27 10:28:12] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage for command {\"command\":\"mailbox:skip-lists:refresh\",\"memoryBeforeCommandInMb\":60.0,\"memoryAfterCommandInMB\":62.0,\"memoryPeakBeforeCommandInMb\":99.727,\"memoryPeakAfterCommandInMB\":99.727} {\"correlation_id\":\"907d15ed-8c84-41a1-a473-590eebb55dbc\",\"trace_id\":\"ec7bd353-516e-491d-89dd-de44136c1672\"}\n[2026-04-27 10:28:13] local.ERROR: Target class [Jiminny\\Repositories\\AjReportsRepository] does not exist. {\"exception\":\"[object] (Illuminate\\\\Contracts\\\\Container\\\\BindingResolutionException(code: 0): Target class [Jiminny\\\\Repositories\\\\AjReportsRepository] does not exist. at /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php:1124)\n[stacktrace]\n#0 /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php(933): Illuminate\\\\Container\\\\Container->build('Jiminny\\\\\\\\Reposit...')\n#1 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Application.php(1078): Illuminate\\\\Container\\\\Container->resolve('Jiminny\\\\\\\\Reposit...', Array, true)\n#2 /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php(864): Illuminate\\\\Foundation\\\\Application->resolve('Jiminny\\\\\\\\Reposit...', Array)\n#3 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Application.php(1058): Illuminate\\\\Container\\\\Container->make('Jiminny\\\\\\\\Reposit...', Array)\n#4 /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php(1336): Illuminate\\\\Foundation\\\\Application->make('Jiminny\\\\\\\\Reposit...')\n#5 /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php(1237): Illuminate\\\\Container\\\\Container->resolveClass(Object(ReflectionParameter))\n#6 /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php(1162): Illuminate\\\\Container\\\\Container->resolveDependencies(Array)\n#7 /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php(933): Illuminate\\\\Container\\\\Container->build('Jiminny\\\\\\\\Http\\\\\\\\Co...')\n#8 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Application.php(1078): Illuminate\\\\Container\\\\Container->resolve('Jiminny\\\\\\\\Http\\\\\\\\Co...', Array, true)\n#9 /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php(864): Illuminate\\\\Foundation\\\\Application->resolve('Jiminny\\\\\\\\Http\\\\\\\\Co...', Array)\n#10 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Application.php(1058): Illuminate\\\\Container\\\\Container->make('Jiminny\\\\\\\\Http\\\\\\\\Co...', Array)\n#11 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Route.php(286): Illuminate\\\\Foundation\\\\Application->make('Jiminny\\\\\\\\Http\\\\\\\\Co...')\n#12 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Route.php(1133): Illuminate\\\\Routing\\\\Route->getController()\n#13 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Route.php(1062): Illuminate\\\\Routing\\\\Route->controllerMiddleware()\n#14 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Router.php(834): Illuminate\\\\Routing\\\\Route->gatherMiddleware()\n#15 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Router.php(816): Illuminate\\\\Routing\\\\Router->gatherRouteMiddleware(Object(Illuminate\\\\Routing\\\\Route))\n#16 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Router.php(800): Illuminate\\\\Routing\\\\Router->runRouteWithinStack(Object(Illuminate\\\\Routing\\\\Route), Object(Illuminate\\\\Http\\\\Request))\n#17 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Router.php(764): Illuminate\\\\Routing\\\\Router->runRoute(Object(Illuminate\\\\Http\\\\Request), Object(Illuminate\\\\Routing\\\\Route))\n#18 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Router.php(753): Illuminate\\\\Routing\\\\Router->dispatchToRoute(Object(Illuminate\\\\Http\\\\Request))\n#19 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Http/Kernel.php(200): Illuminate\\\\Routing\\\\Router->dispatch(Object(Illuminate\\\\Http\\\\Request))\n#20 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(180): Illuminate\\\\Foundation\\\\Http\\\\Kernel->Illuminate\\\\Foundation\\\\Http\\\\{closure}(Object(Illuminate\\\\Http\\\\Request))\n#21 /home/jiminny/vendor/barryvdh/laravel-debugbar/src/Middleware/InjectDebugbar.php(59): Illuminate\\\\Pipeline\\\\Pipeline->Illuminate\\\\Pipeline\\\\{closure}(Object(Illuminate\\\\Http\\\\Request))\n#22 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(219): Barryvdh\\\\Debugbar\\\\Middleware\\\\InjectDebugbar->handle(Object(Illuminate\\\\Http\\\\Request), Object(Closure))\n#23 /home/jiminny/vendor/laravel/framework/src/Illuminate/Http/Middleware/HandleCors.php(74): Illuminate\\\\Pipeline\\\\Pipeline->Illuminate\\\\Pipeline\\\\{closure}(Object(Illuminate\\\\Http\\\\Request))\n#24 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(219): Illuminate\\\\Http\\\\Middleware\\\\HandleCors->handle(Object(Illuminate\\\\Http\\\\Request), Object(Closure))\n#25 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Http/Middleware/TransformsRequest.php(21): Illuminate\\\\Pipeline\\\\Pipeline->Illuminate\\\\Pipeline\\\\{closure}(Object(Illuminate\\\\Http\\\\Request))\n#26 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Http/Middleware/TrimStrings.php(51): Illuminate\\\\Foundation\\\\Http\\\\Middleware\\\\TransformsRequest->handle(Object(Illuminate\\\\Http\\\\Request), Object(Closure))\n#27 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(219): Illuminate\\\\Foundation\\\\Http\\\\Middleware\\\\TrimStrings->handle(Object(Illuminate\\\\Http\\\\Request), Object(Closure))\n#28 /home/jiminny/vendor/bepsvpt/secure-headers/src/SecureHeadersMiddleware.php(18): Illuminate\\\\Pipeline\\\\Pipeline->Illuminate\\\\Pipeline\\\\{closure}(Object(Illuminate\\\\Http\\\\Request))\n#29 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(219): Bepsvpt\\\\SecureHeaders\\\\SecureHeadersMiddleware->handle(Object(Illuminate\\\\Http\\\\Request), Object(Closure))\n#30 /home/jiminny/app/Http/Middleware/SentryContext.php(60): Illuminate\\\\Pipeline\\\\Pipeline->Illuminate\\\\Pipeline\\\\{closure}(Object(Illuminate\\\\Http\\\\Request))\n#31 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(219): Jiminny\\\\Http\\\\Middleware\\\\SentryContext->handle(Object(Illuminate\\\\Http\\\\Request), Object(Closure))\n#32 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Http/Middleware/PreventRequestsDuringMaintenance.php(109): Illuminate\\\\Pipeline\\\\Pipeline->Illuminate\\\\Pipeline\\\\{closure}(Object(Illuminate\\\\Http\\\\Request))\n#33 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(219): Illuminate\\\\Foundation\\\\Http\\\\Middleware\\\\PreventRequestsDuringMaintenance->handle(Object(Illuminate\\\\Http\\\\Request), Object(Closure))\n#34 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Http/Middleware/InvokeDeferredCallbacks.php(22): Illuminate\\\\Pipeline\\\\Pipeline->Illuminate\\\\Pipeline\\\\{closure}(Object(Illuminate\\\\Http\\\\Request))\n#35 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(219): Illuminate\\\\Foundation\\\\Http\\\\Middleware\\\\InvokeDeferredCallbacks->handle(Object(Illuminate\\\\Http\\\\Request), Object(Closure))\n#36 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(137): Illuminate\\\\Pipeline\\\\Pipeline->Illuminate\\\\Pipeline\\\\{closure}(Object(Illuminate\\\\Http\\\\Request))\n#37 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Http/Kernel.php(175): Illuminate\\\\Pipeline\\\\Pipeline->then(Object(Closure))\n#38 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Http/Kernel.php(144): Illuminate\\\\Foundation\\\\Http\\\\Kernel->sendRequestThroughRouter(Object(Illuminate\\\\Http\\\\Request))\n#39 /home/jiminny/public/index.php(51): Illuminate\\\\Foundation\\\\Http\\\\Kernel->handle(Object(Illuminate\\\\Http\\\\Request))\n#40 {main}\n\n[previous exception] [object] (ReflectionException(code: -1): Class \\\"Jiminny\\\\Repositories\\\\AjReportsRepository\\\" does not exist at /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php:1122)\n[stacktrace]\n#0 /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php(1122): ReflectionClass->__construct('Jiminny\\\\\\\\Reposit...')\n#1 /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php(933): Illuminate\\\\Container\\\\Container->build('Jiminny\\\\\\\\Reposit...')\n#2 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Application.php(1078): Illuminate\\\\Container\\\\Container->resolve('Jiminny\\\\\\\\Reposit...', Array, true)\n#3 /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php(864): Illuminate\\\\Foundation\\\\Application->resolve('Jiminny\\\\\\\\Reposit...', Array)\n#4 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Application.php(1058): Illuminate\\\\Container\\\\Container->make('Jiminny\\\\\\\\Reposit...', Array)\n#5 /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php(1336): Illuminate\\\\Foundation\\\\Application->make('Jiminny\\\\\\\\Reposit...')\n#6 /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php(1237): Illuminate\\\\Container\\\\Container->resolveClass(Object(ReflectionParameter))\n#7 /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php(1162): Illuminate\\\\Container\\\\Container->resolveDependencies(Array)\n#8 /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php(933): Illuminate\\\\Container\\\\Container->build('Jiminny\\\\\\\\Http\\\\\\\\Co...')\n#9 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Application.php(1078): Illuminate\\\\Container\\\\Container->resolve('Jiminny\\\\\\\\Http\\\\\\\\Co...', Array, true)\n#10 /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php(864): Illuminate\\\\Foundation\\\\Application->resolve('Jiminny\\\\\\\\Http\\\\\\\\Co...', Array)\n#11 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Application.php(1058): Illuminate\\\\Container\\\\Container->make('Jiminny\\\\\\\\Http\\\\\\\\Co...', Array)\n#12 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Route.php(286): Illuminate\\\\Foundation\\\\Application->make('Jiminny\\\\\\\\Http\\\\\\\\Co...')\n#13 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Route.php(1133): Illuminate\\\\Routing\\\\Route->getController()\n#14 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Route.php(1062): Illuminate\\\\Routing\\\\Route->controllerMiddleware()\n#15 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Router.php(834): Illuminate\\\\Routing\\\\Route->gatherMiddleware()\n#16 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Router.php(816): Illuminate\\\\Routing\\\\Router->gatherRouteMiddleware(Object(Illuminate\\\\Routing\\\\Route))\n#17 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Router.php(800): Illuminate\\\\Routing\\\\Router->runRouteWithinStack(Object(Illuminate\\\\Routing\\\\Route), Object(Illuminate\\\\Http\\\\Request))\n#18 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Router.php(764): Illuminate\\\\Routing\\\\Router->runRoute(Object(Illuminate\\\\Http\\\\Request), Object(Illuminate\\\\Routing\\\\Route))\n#19 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Router.php(753): Illuminate\\\\Routing\\\\Router->dispatchToRoute(Object(Illuminate\\\\Http\\\\Request))\n#20 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Http/Kernel.php(200): Illuminate\\\\Routing\\\\Router->dispatch(Object(Illuminate\\\\Http\\\\Request))\n#21 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(180): Illuminate\\\\Foundation\\\\Http\\\\Kernel->Illuminate\\\\Foundation\\\\Http\\\\{closure}(Object(Illuminate\\\\Http\\\\Request))\n#22 /home/jiminny/vendor/barryvdh/laravel-debugbar/src/Middleware/InjectDebugbar.php(59): Illuminate\\\\Pipeline\\\\Pipeline->Illuminate\\\\Pipeline\\\\{closure}(Object(Illuminate\\\\Http\\\\Request))\n#23 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(219): Barryvdh\\\\Debugbar\\\\Middleware\\\\InjectDebugbar->handle(Object(Illuminate\\\\Http\\\\Request), Object(Closure))\n#24 /home/jiminny/vendor/laravel/framework/src/Illuminate/Http/Middleware/HandleCors.php(74): Illuminate\\\\Pipeline\\\\Pipeline->Illuminate\\\\Pipeline\\\\{closure}(Object(Illuminate\\\\Http\\\\Request))\n#25 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(219): Illuminate\\\\Http\\\\Middleware\\\\HandleCors->handle(Object(Illuminate\\\\Http\\\\Request), Object(Closure))\n#26 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Http/Middleware/TransformsRequest.php(21): Illuminate\\\\Pipeline\\\\Pipeline->Illuminate\\\\Pipeline\\\\{closure}(Object(Illuminate\\\\Http\\\\Request))\n#27 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Http/Middleware/TrimStrings.php(51): Illuminate\\\\Foundation\\\\Http\\\\Middleware\\\\TransformsRequest->handle(Object(Illuminate\\\\Http\\\\Request), Object(Closure))\n#28 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(219): Illuminate\\\\Foundation\\\\Http\\\\Middleware\\\\TrimStrings->handle(Object(Illuminate\\\\Http\\\\Request), Object(Closure))\n#29 /home/jiminny/vendor/bepsvpt/secure-headers/src/SecureHeadersMiddleware.php(18): Illuminate\\\\Pipeline\\\\Pipeline->Illuminate\\\\Pipeline\\\\{closure}(Object(Illuminate\\\\Http\\\\Request))\n#30 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(219): Bepsvpt\\\\SecureHeaders\\\\SecureHeadersMiddleware->handle(Object(Illuminate\\\\Http\\\\Request), Object(Closure))\n#31 /home/jiminny/app/Http/Middleware/SentryContext.php(60): Illuminate\\\\Pipeline\\\\Pipeline->Illuminate\\\\Pipeline\\\\{closure}(Object(Illuminate\\\\Http\\\\Request))\n#32 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(219): Jiminny\\\\Http\\\\Middleware\\\\SentryContext->handle(Object(Illuminate\\\\Http\\\\Request), Object(Closure))\n#33 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Http/Middleware/PreventRequestsDuringMaintenance.php(109): Illuminate\\\\Pipeline\\\\Pipeline->Illuminate\\\\Pipeline\\\\{closure}(Object(Illuminate\\\\Http\\\\Request))\n#34 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(219): Illuminate\\\\Foundation\\\\Http\\\\Middleware\\\\PreventRequestsDuringMaintenance->handle(Object(Illuminate\\\\Http\\\\Request), Object(Closure))\n#35 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Http/Middleware/InvokeDeferredCallbacks.php(22): Illuminate\\\\Pipeline\\\\Pipeline->Illuminate\\\\Pipeline\\\\{closure}(Object(Illuminate\\\\Http\\\\Request))\n#36 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(219): Illuminate\\\\Foundation\\\\Http\\\\Middleware\\\\InvokeDeferredCallbacks->handle(Object(Illuminate\\\\Http\\\\Request), Object(Closure))\n#37 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(137): Illuminate\\\\Pipeline\\\\Pipeline->Illuminate\\\\Pipeline\\\\{closure}(Object(Illuminate\\\\Http\\\\Request))\n#38 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Http/Kernel.php(175): Illuminate\\\\Pipeline\\\\Pipeline->then(Object(Closure))\n#39 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Http/Kernel.php(144): Illuminate\\\\Foundation\\\\Http\\\\Kernel->sendRequestThroughRouter(Object(Illuminate\\\\Http\\\\Request))\n#40 /home/jiminny/public/index.php(51): Illuminate\\\\Foundation\\\\Http\\\\Kernel->handle(Object(Illuminate\\\\Http\\\\Request))\n#41 {main}\n\"} {\"correlation_id\":\"d8709bb9-0f8c-4798-a8c7-e68e24ab6265\",\"trace_id\":\"c5bea1cc-53fe-4bda-a8e2-bec002030bc2\"}\n[2026-04-27 10:28:14] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage before starting command {\"command\":\"mailbox:batch:process\",\"memoryBeforeCommandInMb\":60.0,\"memoryPeakBeforeCommandInMb\":99.727} {\"correlation_id\":\"5fb3c8c8-3160-4964-ac56-02d6e2cb998c\",\"trace_id\":\"3a6d5e64-ecf2-4700-98cd-3edd56a1dc34\"}\n[2026-04-27 10:28:14] local.INFO: [EmailSchedule] STARTING batch process {\"host\":\"docker_lamp_1\"} {\"correlation_id\":\"5fb3c8c8-3160-4964-ac56-02d6e2cb998c\",\"trace_id\":\"3a6d5e64-ecf2-4700-98cd-3edd56a1dc34\"}\n[2026-04-27 10:28:14] local.INFO: Processing email batch 98446 for inbox 212 {\"correlation_id\":\"5fb3c8c8-3160-4964-ac56-02d6e2cb998c\",\"trace_id\":\"3a6d5e64-ecf2-4700-98cd-3edd56a1dc34\"}\n[2026-04-27 10:28:14] local.INFO: [SocialAccountService] Fetching token {\"socialAccountId\":1500,\"provider\":\"salesforce\"} {\"correlation_id\":\"5fb3c8c8-3160-4964-ac56-02d6e2cb998c\",\"trace_id\":\"3a6d5e64-ecf2-4700-98cd-3edd56a1dc34\"}\n[2026-04-27 10:28:14] local.INFO: [SocialAccountService] Token retrieved {\"socialAccountId\":1500,\"provider\":\"salesforce\"} {\"correlation_id\":\"5fb3c8c8-3160-4964-ac56-02d6e2cb998c\",\"trace_id\":\"3a6d5e64-ecf2-4700-98cd-3edd56a1dc34\"}\n[2026-04-27 10:28:14] local.INFO: [EncryptedTokenManager] Generating access token. {\"mode\":\"legacy\"} {\"correlation_id\":\"5fb3c8c8-3160-4964-ac56-02d6e2cb998c\",\"trace_id\":\"3a6d5e64-ecf2-4700-98cd-3edd56a1dc34\"}\n[2026-04-27 10:28:14] local.INFO: [CrmOwnerResolver] Integration owner matched as CRM Owner {\"crm_provider\":\"salesforce\",\"crm_owner\":143,\"team_id\":1} {\"correlation_id\":\"5fb3c8c8-3160-4964-ac56-02d6e2cb998c\",\"trace_id\":\"3a6d5e64-ecf2-4700-98cd-3edd56a1dc34\"}\n[2026-04-27 10:28:14] local.INFO: [SocialAccountService] Fetching token {\"socialAccountId\":1354,\"provider\":\"google\"} {\"correlation_id\":\"5fb3c8c8-3160-4964-ac56-02d6e2cb998c\",\"trace_id\":\"3a6d5e64-ecf2-4700-98cd-3edd56a1dc34\"}\n[2026-04-27 10:28:14] local.INFO: [SocialAccountService] Token retrieved {\"socialAccountId\":1354,\"provider\":\"google\"} {\"correlation_id\":\"5fb3c8c8-3160-4964-ac56-02d6e2cb998c\",\"trace_id\":\"3a6d5e64-ecf2-4700-98cd-3edd56a1dc34\"}\n[2026-04-27 10:28:14] local.INFO: [EncryptedTokenManager] Generating access token. {\"mode\":\"legacy\"} {\"correlation_id\":\"5fb3c8c8-3160-4964-ac56-02d6e2cb998c\",\"trace_id\":\"3a6d5e64-ecf2-4700-98cd-3edd56a1dc34\"}\n[2026-04-27 10:28:15] local.INFO: Processing an email from inbox batch {\"batch\":98446,\"inbox_id\":212,\"email\":\"lukas.kovalik@jiminny.com\",\"email_id\":\"19dce788ac964bbf\",\"from\":\"Nikolay Yankov <notifications@github.com>\",\"to\":\"\\\"jiminny/app\\\" <app@noreply.github.com>\",\"cc\":\"Push <push@noreply.github.com>\"} {\"correlation_id\":\"5fb3c8c8-3160-4964-ac56-02d6e2cb998c\",\"trace_id\":\"3a6d5e64-ecf2-4700-98cd-3edd56a1dc34\"}\n[2026-04-27 10:28:15] local.INFO: [SocialAccountService] Fetching token {\"socialAccountId\":1500,\"provider\":\"salesforce\"} {\"correlation_id\":\"5fb3c8c8-3160-4964-ac56-02d6e2cb998c\",\"trace_id\":\"3a6d5e64-ecf2-4700-98cd-3edd56a1dc34\"}\n[2026-04-27 10:28:15] local.INFO: [SocialAccountService] Token retrieved {\"socialAccountId\":1500,\"provider\":\"salesforce\"} {\"correlation_id\":\"5fb3c8c8-3160-4964-ac56-02d6e2cb998c\",\"trace_id\":\"3a6d5e64-ecf2-4700-98cd-3edd56a1dc34\"}\n[2026-04-27 10:28:15] local.INFO: [EncryptedTokenManager] Generating access token. {\"mode\":\"legacy\"} {\"correlation_id\":\"5fb3c8c8-3160-4964-ac56-02d6e2cb998c\",\"trace_id\":\"3a6d5e64-ecf2-4700-98cd-3edd56a1dc34\"}\n[2026-04-27 10:28:15] local.INFO: [CrmOwnerResolver] Integration owner matched as CRM Owner {\"crm_provider\":\"salesforce\",\"crm_owner\":143,\"team_id\":1} {\"correlation_id\":\"5fb3c8c8-3160-4964-ac56-02d6e2cb998c\",\"trace_id\":\"3a6d5e64-ecf2-4700-98cd-3edd56a1dc34\"}\n[2026-04-27 10:28:15] local.INFO: [EmailImport\\ParticipantsResolver] The sender email is blacklisted, skipping {\"email\":\"notifications@github.com\",\"inbox_id\":212,\"message_provider_id\":\"19dce788ac964bbf\",\"team_id\":1} {\"correlation_id\":\"5fb3c8c8-3160-4964-ac56-02d6e2cb998c\",\"trace_id\":\"3a6d5e64-ecf2-4700-98cd-3edd56a1dc34\"}\n[2026-04-27 10:28:15] local.INFO: [EmailImport\\ParticipantsValidator] Email participants are less than 2 {\"inbox_id\":212,\"message_provider_id\":\"19dce788ac964bbf\",\"message_id\":\"<jiminny/app/pull/11998/before/2b8a97d6be8b053441d5a845b72dd04803cbb53c/after/8a68a94d39cc76ae546e19f354e8b0dabd577314@github.com>\"} {\"correlation_id\":\"5fb3c8c8-3160-4964-ac56-02d6e2cb998c\",\"trace_id\":\"3a6d5e64-ecf2-4700-98cd-3edd56a1dc34\"}\n[2026-04-27 10:28:15] local.INFO: Processing an email from inbox batch {\"batch\":98446,\"inbox_id\":212,\"email\":\"lukas.kovalik@jiminny.com\",\"email_id\":\"19dce7883f2fea19\",\"from\":\"The Jiminny Team <no-reply@dev.jiminny.com>\",\"to\":\"lukas.kovalik@jiminny.com\",\"cc\":null} {\"correlation_id\":\"5fb3c8c8-3160-4964-ac56-02d6e2cb998c\",\"trace_id\":\"3a6d5e64-ecf2-4700-98cd-3edd56a1dc34\"}\n[2026-04-27 10:28:15] local.INFO: [SocialAccountService] Fetching token {\"socialAccountId\":1500,\"provider\":\"salesforce\"} {\"correlation_id\":\"5fb3c8c8-3160-4964-ac56-02d6e2cb998c\",\"trace_id\":\"3a6d5e64-ecf2-4700-98cd-3edd56a1dc34\"}\n[2026-04-27 10:28:15] local.INFO: [SocialAccountService] Token retrieved {\"socialAccountId\":1500,\"provider\":\"salesforce\"} {\"correlation_id\":\"5fb3c8c8-3160-4964-ac56-02d6e2cb998c\",\"trace_id\":\"3a6d5e64-ecf2-4700-98cd-3edd56a1dc34\"}\n[2026-04-27 10:28:15] local.INFO: [EncryptedTokenManager] Generating access token. {\"mode\":\"legacy\"} {\"correlation_id\":\"5fb3c8c8-3160-4964-ac56-02d6e2cb998c\",\"trace_id\":\"3a6d5e64-ecf2-4700-98cd-3edd56a1dc34\"}\n[2026-04-27 10:28:15] local.INFO: [CrmOwnerResolver] Integration owner matched as CRM Owner {\"crm_provider\":\"salesforce\",\"crm_owner\":143,\"team_id\":1} {\"correlation_id\":\"5fb3c8c8-3160-4964-ac56-02d6e2cb998c\",\"trace_id\":\"3a6d5e64-ecf2-4700-98cd-3edd56a1dc34\"}\n[2026-04-27 10:28:15] local.INFO: [EmailImport\\ParticipantsResolver] The sender email is blacklisted, skipping {\"email\":\"no-reply@dev.jiminny.com\",\"inbox_id\":212,\"message_provider_id\":\"19dce7883f2fea19\",\"team_id\":1} {\"correlation_id\":\"5fb3c8c8-3160-4964-ac56-02d6e2cb998c\",\"trace_id\":\"3a6d5e64-ecf2-4700-98cd-3edd56a1dc34\"}\n[2026-04-27 10:28:15] local.INFO: [EmailImport\\ParticipantsValidator] Email participants are less than 2 {\"inbox_id\":212,\"message_provider_id\":\"19dce7883f2fea19\",\"message_id\":\"<d70689e8-7b14-42ac-a3bf-5f1e81da943e@mtasv.net>\"} {\"correlation_id\":\"5fb3c8c8-3160-4964-ac56-02d6e2cb998c\",\"trace_id\":\"3a6d5e64-ecf2-4700-98cd-3edd56a1dc34\"}\n[2026-04-27 10:28:15] local.INFO: Processing an email from inbox batch {\"batch\":98446,\"inbox_id\":212,\"email\":\"lukas.kovalik@jiminny.com\",\"email_id\":\"19dce761cb049fba\",\"from\":\"The Jiminny Team <no-reply@dev.jiminny.com>\",\"to\":\"lukas.kovalik@jiminny.com\",\"cc\":null} {\"correlation_id\":\"5fb3c8c8-3160-4964-ac56-02d6e2cb998c\",\"trace_id\":\"3a6d5e64-ecf2-4700-98cd-3edd56a1dc34\"}\n[2026-04-27 10:28:15] local.INFO: [SocialAccountService] Fetching token {\"socialAccountId\":1500,\"provider\":\"salesforce\"} {\"correlation_id\":\"5fb3c8c8-3160-4964-ac56-02d6e2cb998c\",\"trace_id\":\"3a6d5e64-ecf2-4700-98cd-3edd56a1dc34\"}\n[2026-04-27 10:28:15] local.INFO: [SocialAccountService] Token retrieved {\"socialAccountId\":1500,\"provider\":\"salesforce\"} {\"correlation_id\":\"5fb3c8c8-3160-4964-ac56-02d6e2cb998c\",\"trace_id\":\"3a6d5e64-ecf2-4700-98cd-3edd56a1dc34\"}\n[2026-04-27 10:28:15] local.INFO: [EncryptedTokenManager] Generating access token. {\"mode\":\"legacy\"} {\"correlation_id\":\"5fb3c8c8-3160-4964-ac56-02d6e2cb998c\",\"trace_id\":\"3a6d5e64-ecf2-4700-98cd-3edd56a1dc34\"}\n[2026-04-27 10:28:15] local.INFO: [CrmOwnerResolver] Integration owner matched as CRM Owner {\"crm_provider\":\"salesforce\",\"crm_owner\":143,\"team_id\":1} {\"correlation_id\":\"5fb3c8c8-3160-4964-ac56-02d6e2cb998c\",\"trace_id\":\"3a6d5e64-ecf2-4700-98cd-3edd56a1dc34\"}\n[2026-04-27 10:28:15] local.INFO: [EmailImport\\ParticipantsResolver] The sender email is blacklisted, skipping {\"email\":\"no-reply@dev.jiminny.com\",\"inbox_id\":212,\"message_provider_id\":\"19dce761cb049fba\",\"team_id\":1} {\"correlation_id\":\"5fb3c8c8-3160-4964-ac56-02d6e2cb998c\",\"trace_id\":\"3a6d5e64-ecf2-4700-98cd-3edd56a1dc34\"}\n[2026-04-27 10:28:15] local.INFO: [EmailImport\\ParticipantsValidator] Email participants are less than 2 {\"inbox_id\":212,\"message_provider_id\":\"19dce761cb049fba\",\"message_id\":\"<017f0e66-9989-446e-9788-7ae83a99137e@mtasv.net>\"} {\"correlation_id\":\"5fb3c8c8-3160-4964-ac56-02d6e2cb998c\",\"trace_id\":\"3a6d5e64-ecf2-4700-98cd-3edd56a1dc34\"}\n[2026-04-27 10:28:15] local.INFO: Processing an email from inbox batch {\"batch\":98446,\"inbox_id\":212,\"email\":\"lukas.kovalik@jiminny.com\",\"email_id\":\"19dce74ed398d2c1\",\"from\":\"\\\"sonarqubecloud[bot]\\\" <notifications@github.com>\",\"to\":\"\\\"jiminny/app\\\" <app@noreply.github.com>\",\"cc\":\"Lukas Kovalik <kovaliklukas@gmail.com>, Author <author@noreply.github.com>\"} {\"correlation_id\":\"5fb3c8c8-3160-4964-ac56-02d6e2cb998c\",\"trace_id\":\"3a6d5e64-ecf2-4700-98cd-3edd56a1dc34\"}\n[2026-04-27 10:28:15] local.INFO: [SocialAccountService] Fetching token {\"socialAccountId\":1500,\"provider\":\"salesforce\"} {\"correlation_id\":\"5fb3c8c8-3160-4964-ac56-02d6e2cb998c\",\"trace_id\":\"3a6d5e64-ecf2-4700-98cd-3edd56a1dc34\"}\n[2026-04-27 10:28:15] local.INFO: [SocialAccountService] Token retrieved {\"socialAccountId\":1500,\"provider\":\"salesforce\"} {\"correlation_id\":\"5fb3c8c8-3160-4964-ac56-02d6e2cb998c\",\"trace_id\":\"3a6d5e64-ecf2-4700-98cd-3edd56a1dc34\"}\n[2026-04-27 10:28:15] local.INFO: [EncryptedTokenManager] Generating access token. {\"mode\":\"legacy\"} {\"correlation_id\":\"5fb3c8c8-3160-4964-ac56-02d6e2cb998c\",\"trace_id\":\"3a6d5e64-ecf2-4700-98cd-3edd56a1dc34\"}\n[2026-04-27 10:28:15] local.INFO: [CrmOwnerResolver] Integration owner matched as CRM Owner {\"crm_provider\":\"salesforce\",\"crm_owner\":143,\"team_id\":1} {\"correlation_id\":\"5fb3c8c8-3160-4964-ac56-02d6e2cb998c\",\"trace_id\":\"3a6d5e64-ecf2-4700-98cd-3edd56a1dc34\"}\n[2026-04-27 10:28:15] local.INFO: [EmailImport\\ParticipantsResolver] The sender email is blacklisted, skipping {\"email\":\"notifications@github.com\",\"inbox_id\":212,\"message_provider_id\":\"19dce74ed398d2c1\",\"team_id\":1} {\"correlation_id\":\"5fb3c8c8-3160-4964-ac56-02d6e2cb998c\",\"trace_id\":\"3a6d5e64-ecf2-4700-98cd-3edd56a1dc34\"}\n[2026-04-27 10:28:15] local.INFO: [EmailImport\\ParticipantsValidator] Email participants are less than 2 {\"inbox_id\":212,\"message_provider_id\":\"19dce74ed398d2c1\",\"message_id\":\"<jiminny/app/pull/12016/c4326142661@github.com>\"} {\"correlation_id\":\"5fb3c8c8-3160-4964-ac56-02d6e2cb998c\",\"trace_id\":\"3a6d5e64-ecf2-4700-98cd-3edd56a1dc34\"}\n[2026-04-27 10:28:15] local.INFO: Processing an email from inbox batch {\"batch\":98446,\"inbox_id\":212,\"email\":\"lukas.kovalik@jiminny.com\",\"email_id\":\"19dce713dbecf41f\",\"from\":\"\\\"sonarqubecloud[bot]\\\" <notifications@github.com>\",\"to\":\"\\\"jiminny/infrastructure\\\" <infrastructure@noreply.github.com>\",\"cc\":\"Subscribed <subscribed@noreply.github.com>\"} {\"correlation_id\":\"5fb3c8c8-3160-4964-ac56-02d6e2cb998c\",\"trace_id\":\"3a6d5e64-ecf2-4700-98cd-3edd56a1dc34\"}\n[2026-04-27 10:28:15] local.INFO: [SocialAccountService] Fetching token {\"socialAccountId\":1500,\"provider\":\"salesforce\"} {\"correlation_id\":\"5fb3c8c8-3160-4964-ac56-02d6e2cb998c\",\"trace_id\":\"3a6d5e64-ecf2-4700-98cd-3edd56a1dc34\"}\n[2026-04-27 10:28:15] local.INFO: [SocialAccountService] Token retrieved {\"socialAccountId\":1500,\"provider\":\"salesforce\"} {\"correlation_id\":\"5fb3c8c8-3160-4964-ac56-02d6e2cb998c\",\"trace_id\":\"3a6d5e64-ecf2-4700-98cd-3edd56a1dc34\"}\n[2026-04-27 10:28:15] local.INFO: [EncryptedTokenManager] Generating access token. {\"mode\":\"legacy\"} {\"correlation_id\":\"5fb3c8c8-3160-4964-ac56-02d6e2cb998c\",\"trace_id\":\"3a6d5e64-ecf2-4700-98cd-3edd56a1dc34\"}\n[2026-04-27 10:28:15] local.INFO: [CrmOwnerResolver] Integration owner matched as CRM Owner {\"crm_provider\":\"salesforce\",\"crm_owner\":143,\"team_id\":1} {\"correlation_id\":\"5fb3c8c8-3160-4964-ac56-02d6e2cb998c\",\"trace_id\":\"3a6d5e64-ecf2-4700-98cd-3edd56a1dc34\"}\n[2026-04-27 10:28:15] local.INFO: [EmailImport\\ParticipantsResolver] The sender email is blacklisted, skipping {\"email\":\"notifications@github.com\",\"inbox_id\":212,\"message_provider_id\":\"19dce713dbecf41f\",\"team_id\":1} {\"correlation_id\":\"5fb3c8c8-3160-4964-ac56-02d6e2cb998c\",\"trace_id\":\"3a6d5e64-ecf2-4700-98cd-3edd56a1dc34\"}\n[2026-04-27 10:28:15] local.INFO: [EmailImport\\ParticipantsValidator] Email participants are less than 2 {\"inbox_id\":212,\"message_provider_id\":\"19dce713dbecf41f\",\"message_id\":\"<jiminny/infrastructure/pull/725/c4326108250@github.com>\"} {\"correlation_id\":\"5fb3c8c8-3160-4964-ac56-02d6e2cb998c\",\"trace_id\":\"3a6d5e64-ecf2-4700-98cd-3edd56a1dc34\"}\n[2026-04-27 10:28:15] local.INFO: Processing an email from inbox batch {\"batch\":98446,\"inbox_id\":212,\"email\":\"lukas.kovalik@jiminny.com\",\"email_id\":\"19dce708ed8b5acb\",\"from\":\"Veselin Kulov <notifications@github.com>\",\"to\":\"\\\"jiminny/infrastructure\\\" <infrastructure@noreply.github.com>\",\"cc\":\"Push <push@noreply.github.com>\"} {\"correlation_id\":\"5fb3c8c8-3160-4964-ac56-02d6e2cb998c\",\"trace_id\":\"3a6d5e64-ecf2-4700-98cd-3edd56a1dc34\"}\n[2026-04-27 10:28:15] local.INFO: [SocialAccountService] Fetching token {\"socialAccountId\":1500,\"provider\":\"salesforce\"} {\"correlation_id\":\"5fb3c8c8-3160-4964-ac56-02d6e2cb998c\",\"trace_id\":\"3a6d5e64-ecf2-4700-98cd-3edd56a1dc34\"}\n[2026-04-27 10:28:15] local.INFO: [SocialAccountService] Token retrieved {\"socialAccountId\":1500,\"provider\":\"salesforce\"} {\"correlation_id\":\"5fb3c8c8-3160-4964-ac56-02d6e2cb998c\",\"trace_id\":\"3a6d5e64-ecf2-4700-98cd-3edd56a1dc34\"}\n[2026-04-27 10:28:15] local.INFO: [EncryptedTokenManager] Generating access token. {\"mode\":\"legacy\"} {\"correlation_id\":\"5fb3c8c8-3160-4964-ac56-02d6e2cb998c\",\"trace_id\":\"3a6d5e64-ecf2-4700-98cd-3edd56a1dc34\"}\n[2026-04-27 10:28:15] local.INFO: [CrmOwnerResolver] Integration owner matched as CRM Owner {\"crm_provider\":\"salesforce\",\"crm_owner\":143,\"team_id\":1} {\"correlation_id\":\"5fb3c8c8-3160-4964-ac56-02d6e2cb998c\",\"trace_id\":\"3a6d5e64-ecf2-4700-98cd-3edd56a1dc34\"}\n[2026-04-27 10:28:15] local.INFO: [EmailImport\\ParticipantsResolver] The sender email is blacklisted, skipping {\"email\":\"notifications@github.com\",\"inbox_id\":212,\"message_provider_id\":\"19dce708ed8b5acb\",\"team_id\":1} {\"correlation_id\":\"5fb3c8c8-3160-4964-ac56-02d6e2cb998c\",\"trace_id\":\"3a6d5e64-ecf2-4700-98cd-3edd56a1dc34\"}\n[2026-04-27 10:28:15] local.INFO: [EmailImport\\ParticipantsValidator] Email participants are less than 2 {\"inbox_id\":212,\"message_provider_id\":\"19dce708ed8b5acb\",\"message_id\":\"<jiminny/infrastructure/pull/725/before/186952cc584428fcca17aa364f6cc535d65e804a/after/078246a06b25fb93ad61c7f14472ecb755483f4d@github.com>\"} {\"correlation_id\":\"5fb3c8c8-3160-4964-ac56-02d6e2cb998c\",\"trace_id\":\"3a6d5e64-ecf2-4700-98cd-3edd56a1dc34\"}\n[2026-04-27 10:28:15] local.INFO: Processing an email from inbox batch {\"batch\":98446,\"inbox_id\":212,\"email\":\"lukas.kovalik@jiminny.com\",\"email_id\":\"19dce6eea657f2fa\",\"from\":\"\\\"sonarqubecloud[bot]\\\" <notifications@github.com>\",\"to\":\"\\\"jiminny/app\\\" <app@noreply.github.com>\",\"cc\":\"Subscribed <subscribed@noreply.github.com>\"} {\"correlation_id\":\"5fb3c8c8-3160-4964-ac56-02d6e2cb998c\",\"trace_id\":\"3a6d5e64-ecf2-4700-98cd-3edd56a1dc34\"}\n[2026-04-27 10:28:15] local.INFO: [SocialAccountService] Fetching token {\"socialAccountId\":1500,\"provider\":\"salesforce\"} {\"correlation_id\":\"5fb3c8c8-3160-4964-ac56-02d6e2cb998c\",\"trace_id\":\"3a6d5e64-ecf2-4700-98cd-3edd56a1dc34\"}\n[2026-04-27 10:28:15] local.INFO: [SocialAccountService] Token retrieved {\"socialAccountId\":1500,\"provider\":\"salesforce\"} {\"correlation_id\":\"5fb3c8c8-3160-4964-ac56-02d6e2cb998c\",\"trace_id\":\"3a6d5e64-ecf2-4700-98cd-3edd56a1dc34\"}\n[2026-04-27 10:28:15] local.INFO: [EncryptedTokenManager] Generating access token. {\"mode\":\"legacy\"} {\"correlation_id\":\"5fb3c8c8-3160-4964-ac56-02d6e2cb998c\",\"trace_id\":\"3a6d5e64-ecf2-4700-98cd-3edd56a1dc34\"}\n[2026-04-27 10:28:15] local.INFO: [CrmOwnerResolver] Integration owner matched as CRM Owner {\"crm_provider\":\"salesforce\",\"crm_owner\":143,\"team_id\":1} {\"correlation_id\":\"5fb3c8c8-3160-4964-ac56-02d6e2cb998c\",\"trace_id\":\"3a6d5e64-ecf2-4700-98cd-3edd56a1dc34\"}\n[2026-04-27 10:28:15] local.INFO: [EmailImport\\ParticipantsResolver] The sender email is blacklisted, skipping {\"email\":\"notifications@github.com\",\"inbox_id\":212,\"message_provider_id\":\"19dce6eea657f2fa\",\"team_id\":1} {\"correlation_id\":\"5fb3c8c8-3160-4964-ac56-02d6e2cb998c\",\"trace_id\":\"3a6d5e64-ecf2-4700-98cd-3edd56a1dc34\"}\n[2026-04-27 10:28:15] local.INFO: [EmailImport\\ParticipantsValidator] Email participants are less than 2 {\"inbox_id\":212,\"message_provider_id\":\"19dce6eea657f2fa\",\"message_id\":\"<jiminny/app/pull/11998/c4326088534@github.com>\"} {\"correlation_id\":\"5fb3c8c8-3160-4964-ac56-02d6e2cb998c\",\"trace_id\":\"3a6d5e64-ecf2-4700-98cd-3edd56a1dc34\"}\n[2026-04-27 10:28:15] local.INFO: Deleting successfully processed batch 98446 for inbox 212 {\"correlation_id\":\"5fb3c8c8-3160-4964-ac56-02d6e2cb998c\",\"trace_id\":\"3a6d5e64-ecf2-4700-98cd-3edd56a1dc34\"}\n[2026-04-27 10:28:15] local.INFO: [EmailSchedule] FINISHED batch process {\"host\":\"docker_lamp_1\",\"processed\":1} {\"correlation_id\":\"5fb3c8c8-3160-4964-ac56-02d6e2cb998c\",\"trace_id\":\"3a6d5e64-ecf2-4700-98cd-3edd56a1dc34\"}\n[2026-04-27 10:28:15] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage for command {\"command\":\"mailbox:batch:process\",\"memoryBeforeCommandInMb\":60.0,\"memoryAfterCommandInMB\":62.0,\"memoryPeakBeforeCommandInMb\":99.727,\"memoryPeakAfterCommandInMB\":99.727} {\"correlation_id\":\"5fb3c8c8-3160-4964-ac56-02d6e2cb998c\",\"trace_id\":\"3a6d5e64-ecf2-4700-98cd-3edd56a1dc34\"}\n[2026-04-27 10:28:16] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage before starting command {\"command\":\"conference:monitor:count\",\"memoryBeforeCommandInMb\":60.0,\"memoryPeakBeforeCommandInMb\":99.727} {\"correlation_id\":\"ef5b3e1a-a06a-4527-9120-b2adfe4e1846\",\"trace_id\":\"d932d64a-3f63-4aca-8abf-bb13e0892cba\"}\n[2026-04-27 10:28:16] local.INFO: Running conference:monitor:count command for activities in (2026-04-27 10:26:00, 2026-04-27 10:28:00] {\"correlation_id\":\"ef5b3e1a-a06a-4527-9120-b2adfe4e1846\",\"trace_id\":\"d932d64a-3f63-4aca-8abf-bb13e0892cba\"}\n[2026-04-27 10:28:16] local.INFO: [conference:monitor:count] No activities found in (2026-04-27 10:26:00, 2026-04-27 10:28:00] {\"correlation_id\":\"ef5b3e1a-a06a-4527-9120-b2adfe4e1846\",\"trace_id\":\"d932d64a-3f63-4aca-8abf-bb13e0892cba\"}\n[2026-04-27 10:28:16] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage for command {\"command\":\"conference:monitor:count\",\"memoryBeforeCommandInMb\":60.0,\"memoryAfterCommandInMB\":62.0,\"memoryPeakBeforeCommandInMb\":99.727,\"memoryPeakAfterCommandInMB\":99.727} {\"correlation_id\":\"ef5b3e1a-a06a-4527-9120-b2adfe4e1846\",\"trace_id\":\"d932d64a-3f63-4aca-8abf-bb13e0892cba\"}\n[2026-04-27 10:28:17] local.ERROR: Target class [Jiminny\\Repositories\\AjReportsRepository] does not exist. {\"exception\":\"[object] (Illuminate\\\\Contracts\\\\Container\\\\BindingResolutionException(code: 0): Target class [Jiminny\\\\Repositories\\\\AjReportsRepository] does not exist. at /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php:1124)\n[stacktrace]\n#0 /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php(933): Illuminate\\\\Container\\\\Container->build('Jiminny\\\\\\\\Reposit...')\n#1 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Application.php(1078): Illuminate\\\\Container\\\\Container->resolve('Jiminny\\\\\\\\Reposit...', Array, true)\n#2 /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php(864): Illuminate\\\\Foundation\\\\Application->resolve('Jiminny\\\\\\\\Reposit...', Array)\n#3 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Application.php(1058): Illuminate\\\\Container\\\\Container->make('Jiminny\\\\\\\\Reposit...', Array)\n#4 /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php(1336): Illuminate\\\\Foundation\\\\Application->make('Jiminny\\\\\\\\Reposit...')\n#5 /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php(1237): Illuminate\\\\Container\\\\Container->resolveClass(Object(ReflectionParameter))\n#6 /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php(1162): Illuminate\\\\Container\\\\Container->resolveDependencies(Array)\n#7 /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php(933): Illuminate\\\\Container\\\\Container->build('Jiminny\\\\\\\\Http\\\\\\\\Co...')\n#8 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Application.php(1078): Illuminate\\\\Container\\\\Container->resolve('Jiminny\\\\\\\\Http\\\\\\\\Co...', Array, true)\n#9 /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php(864): Illuminate\\\\Foundation\\\\Application->resolve('Jiminny\\\\\\\\Http\\\\\\\\Co...', Array)\n#10 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Application.php(1058): Illuminate\\\\Container\\\\Container->make('Jiminny\\\\\\\\Http\\\\\\\\Co...', Array)\n#11 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Route.php(286): Illuminate\\\\Foundation\\\\Application->make('Jiminny\\\\\\\\Http\\\\\\\\Co...')\n#12 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Route.php(1133): Illuminate\\\\Routing\\\\Route->getController()\n#13 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Route.php(1062): Illuminate\\\\Routing\\\\Route->controllerMiddleware()\n#14 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Router.php(834): Illuminate\\\\Routing\\\\Route->gatherMiddleware()\n#15 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Router.php(816): Illuminate\\\\Routing\\\\Router->gatherRouteMiddleware(Object(Illuminate\\\\Routing\\\\Route))\n#16 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Router.php(800): Illuminate\\\\Routing\\\\Router->runRouteWithinStack(Object(Illuminate\\\\Routing\\\\Route), Object(Illuminate\\\\Http\\\\Request))\n#17 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Router.php(764): Illuminate\\\\Routing\\\\Router->runRoute(Object(Illuminate\\\\Http\\\\Request), Object(Illuminate\\\\Routing\\\\Route))\n#18 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Router.php(753): Illuminate\\\\Routing\\\\Router->dispatchToRoute(Object(Illuminate\\\\Http\\\\Request))\n#19 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Http/Kernel.php(200): Illuminate\\\\Routing\\\\Router->dispatch(Object(Illuminate\\\\Http\\\\Request))\n#20 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(180): Illuminate\\\\Foundation\\\\Http\\\\Kernel->Illuminate\\\\Foundation\\\\Http\\\\{closure}(Object(Illuminate\\\\Http\\\\Request))\n#21 /home/jiminny/vendor/barryvdh/laravel-debugbar/src/Middleware/InjectDebugbar.php(59): Illuminate\\\\Pipeline\\\\Pipeline->Illuminate\\\\Pipeline\\\\{closure}(Object(Illuminate\\\\Http\\\\Request))\n#22 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(219): Barryvdh\\\\Debugbar\\\\Middleware\\\\InjectDebugbar->handle(Object(Illuminate\\\\Http\\\\Request), Object(Closure))\n#23 /home/jiminny/vendor/laravel/framework/src/Illuminate/Http/Middleware/HandleCors.php(74): Illuminate\\\\Pipeline\\\\Pipeline->Illuminate\\\\Pipeline\\\\{closure}(Object(Illuminate\\\\Http\\\\Request))\n#24 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(219): Illuminate\\\\Http\\\\Middleware\\\\HandleCors->handle(Object(Illuminate\\\\Http\\\\Request), Object(Closure))\n#25 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Http/Middleware/TransformsRequest.php(21): Illuminate\\\\Pipeline\\\\Pipeline->Illuminate\\\\Pipeline\\\\{closure}(Object(Illuminate\\\\Http\\\\Request))\n#26 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Http/Middleware/TrimStrings.php(51): Illuminate\\\\Foundation\\\\Http\\\\Middleware\\\\TransformsRequest->handle(Object(Illuminate\\\\Http\\\\Request), Object(Closure))\n#27 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(219): Illuminate\\\\Foundation\\\\Http\\\\Middleware\\\\TrimStrings->handle(Object(Illuminate\\\\Http\\\\Request), Object(Closure))\n#28 /home/jiminny/vendor/bepsvpt/secure-headers/src/SecureHeadersMiddleware.php(18): Illuminate\\\\Pipeline\\\\Pipeline->Illuminate\\\\Pipeline\\\\{closure}(Object(Illuminate\\\\Http\\\\Request))\n#29 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(219): Bepsvpt\\\\SecureHeaders\\\\SecureHeadersMiddleware->handle(Object(Illuminate\\\\Http\\\\Request), Object(Closure))\n#30 /home/jiminny/app/Http/Middleware/SentryContext.php(60): Illuminate\\\\Pipeline\\\\Pipeline->Illuminate\\\\Pipeline\\\\{closure}(Object(Illuminate\\\\Http\\\\Request))\n#31 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(219): Jiminny\\\\Http\\\\Middleware\\\\SentryContext->handle(Object(Illuminate\\\\Http\\\\Request), Object(Closure))\n#32 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Http/Middleware/PreventRequestsDuringMaintenance.php(109): Illuminate\\\\Pipeline\\\\Pipeline->Illuminate\\\\Pipeline\\\\{closure}(Object(Illuminate\\\\Http\\\\Request))\n#33 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(219): Illuminate\\\\Foundation\\\\Http\\\\Middleware\\\\PreventRequestsDuringMaintenance->handle(Object(Illuminate\\\\Http\\\\Request), Object(Closure))\n#34 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Http/Middleware/InvokeDeferredCallbacks.php(22): Illuminate\\\\Pipeline\\\\Pipeline->Illuminate\\\\Pipeline\\\\{closure}(Object(Illuminate\\\\Http\\\\Request))\n#35 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(219): Illuminate\\\\Foundation\\\\Http\\\\Middleware\\\\InvokeDeferredCallbacks->handle(Object(Illuminate\\\\Http\\\\Request), Object(Closure))\n#36 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(137): Illuminate\\\\Pipeline\\\\Pipeline->Illuminate\\\\Pipeline\\\\{closure}(Object(Illuminate\\\\Http\\\\Request))\n#37 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Http/Kernel.php(175): Illuminate\\\\Pipeline\\\\Pipeline->then(Object(Closure))\n#38 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Http/Kernel.php(144): Illuminate\\\\Foundation\\\\Http\\\\Kernel->sendRequestThroughRouter(Object(Illuminate\\\\Http\\\\Request))\n#39 /home/jiminny/public/index.php(51): Illuminate\\\\Foundation\\\\Http\\\\Kernel->handle(Object(Illuminate\\\\Http\\\\Request))\n#40 {main}\n\n[previous exception] [object] (ReflectionException(code: -1): Class \\\"Jiminny\\\\Repositories\\\\AjReportsRepository\\\" does not exist at /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php:1122)\n[stacktrace]\n#0 /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php(1122): ReflectionClass->__construct('Jiminny\\\\\\\\Reposit...')\n#1 /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php(933): Illuminate\\\\Container\\\\Container->build('Jiminny\\\\\\\\Reposit...')\n#2 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Application.php(1078): Illuminate\\\\Container\\\\Container->resolve('Jiminny\\\\\\\\Reposit...', Array, true)\n#3 /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php(864): Illuminate\\\\Foundation\\\\Application->resolve('Jiminny\\\\\\\\Reposit...', Array)\n#4 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Application.php(1058): Illuminate\\\\Container\\\\Container->make('Jiminny\\\\\\\\Reposit...', Array)\n#5 /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php(1336): Illuminate\\\\Foundation\\\\Application->make('Jiminny\\\\\\\\Reposit...')\n#6 /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php(1237): Illuminate\\\\Container\\\\Container->resolveClass(Object(ReflectionParameter))\n#7 /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php(1162): Illuminate\\\\Container\\\\Container->resolveDependencies(Array)\n#8 /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php(933): Illuminate\\\\Container\\\\Container->build('Jiminny\\\\\\\\Http\\\\\\\\Co...')\n#9 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Application.php(1078): Illuminate\\\\Container\\\\Container->resolve('Jiminny\\\\\\\\Http\\\\\\\\Co...', Array, true)\n#10 /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php(864): Illuminate\\\\Foundation\\\\Application->resolve('Jiminny\\\\\\\\Http\\\\\\\\Co...', Array)\n#11 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Application.php(1058): Illuminate\\\\Container\\\\Container->make('Jiminny\\\\\\\\Http\\\\\\\\Co...', Array)\n#12 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Route.php(286): Illuminate\\\\Foundation\\\\Application->make('Jiminny\\\\\\\\Http\\\\\\\\Co...')\n#13 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Route.php(1133): Illuminate\\\\Routing\\\\Route->getController()\n#14 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Route.php(1062): Illuminate\\\\Routing\\\\Route->controllerMiddleware()\n#15 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Router.php(834): Illuminate\\\\Routing\\\\Route->gatherMiddleware()\n#16 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Router.php(816): Illuminate\\\\Routing\\\\Router->gatherRouteMiddleware(Object(Illuminate\\\\Routing\\\\Route))\n#17 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Router.php(800): Illuminate\\\\Routing\\\\Router->runRouteWithinStack(Object(Illuminate\\\\Routing\\\\Route), Object(Illuminate\\\\Http\\\\Request))\n#18 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Router.php(764): Illuminate\\\\Routing\\\\Router->runRoute(Object(Illuminate\\\\Http\\\\Request), Object(Illuminate\\\\Routing\\\\Route))\n#19 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Router.php(753): Illuminate\\\\Routing\\\\Router->dispatchToRoute(Object(Illuminate\\\\Http\\\\Request))\n#20 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Http/Kernel.php(200): Illuminate\\\\Routing\\\\Router->dispatch(Object(Illuminate\\\\Http\\\\Request))\n#21 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(180): Illuminate\\\\Foundation\\\\Http\\\\Kernel->Illuminate\\\\Foundation\\\\Http\\\\{closure}(Object(Illuminate\\\\Http\\\\Request))\n#22 /home/jiminny/vendor/barryvdh/laravel-debugbar/src/Middleware/InjectDebugbar.php(59): Illuminate\\\\Pipeline\\\\Pipeline->Illuminate\\\\Pipeline\\\\{closure}(Object(Illuminate\\\\Http\\\\Request))\n#23 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(219): Barryvdh\\\\Debugbar\\\\Middleware\\\\InjectDebugbar->handle(Object(Illuminate\\\\Http\\\\Request), Object(Closure))\n#24 /home/jiminny/vendor/laravel/framework/src/Illuminate/Http/Middleware/HandleCors.php(74): Illuminate\\\\Pipeline\\\\Pipeline->Illuminate\\\\Pipeline\\\\{closure}(Object(Illuminate\\\\Http\\\\Request))\n#25 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(219): Illuminate\\\\Http\\\\Middleware\\\\HandleCors->handle(Object(Illuminate\\\\Http\\\\Request), Object(Closure))\n#26 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Http/Middleware/TransformsRequest.php(21): Illuminate\\\\Pipeline\\\\Pipeline->Illuminate\\\\Pipeline\\\\{closure}(Object(Illuminate\\\\Http\\\\Request))\n#27 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Http/Middleware/TrimStrings.php(51): Illuminate\\\\Foundation\\\\Http\\\\Middleware\\\\TransformsRequest->handle(Object(Illuminate\\\\Http\\\\Request), Object(Closure))\n#28 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(219): Illuminate\\\\Foundation\\\\Http\\\\Middleware\\\\TrimStrings->handle(Object(Illuminate\\\\Http\\\\Request), Object(Closure))\n#29 /home/jiminny/vendor/bepsvpt/secure-headers/src/SecureHeadersMiddleware.php(18): Illuminate\\\\Pipeline\\\\Pipeline->Illuminate\\\\Pipeline\\\\{closure}(Object(Illuminate\\\\Http\\\\Request))\n#30 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(219): Bepsvpt\\\\SecureHeaders\\\\SecureHeadersMiddleware->handle(Object(Illuminate\\\\Http\\\\Request), Object(Closure))\n#31 /home/jiminny/app/Http/Middleware/SentryContext.php(60): Illuminate\\\\Pipeline\\\\Pipeline->Illuminate\\\\Pipeline\\\\{closure}(Object(Illuminate\\\\Http\\\\Request))\n#32 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(219): Jiminny\\\\Http\\\\Middleware\\\\SentryContext->handle(Object(Illuminate\\\\Http\\\\Request), Object(Closure))\n#33 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Http/Middleware/PreventRequestsDuringMaintenance.php(109): Illuminate\\\\Pipeline\\\\Pipeline->Illuminate\\\\Pipeline\\\\{closure}(Object(Illuminate\\\\Http\\\\Request))\n#34 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(219): Illuminate\\\\Foundation\\\\Http\\\\Middleware\\\\PreventRequestsDuringMaintenance->handle(Object(Illuminate\\\\Http\\\\Request), Object(Closure))\n#35 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Http/Middleware/InvokeDeferredCallbacks.php(22): Illuminate\\\\Pipeline\\\\Pipeline->Illuminate\\\\Pipeline\\\\{closure}(Object(Illuminate\\\\Http\\\\Request))\n#36 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(219): Illuminate\\\\Foundation\\\\Http\\\\Middleware\\\\InvokeDeferredCallbacks->handle(Object(Illuminate\\\\Http\\\\Request), Object(Closure))\n#37 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(137): Illuminate\\\\Pipeline\\\\Pipeline->Illuminate\\\\Pipeline\\\\{closure}(Object(Illuminate\\\\Http\\\\Request))\n#38 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Http/Kernel.php(175): Illuminate\\\\Pipeline\\\\Pipeline->then(Object(Closure))\n#39 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Http/Kernel.php(144): Illuminate\\\\Foundation\\\\Http\\\\Kernel->sendRequestThroughRouter(Object(Illuminate\\\\Http\\\\Request))\n#40 /home/jiminny/public/index.php(51): Illuminate\\\\Foundation\\\\Http\\\\Kernel->handle(Object(Illuminate\\\\Http\\\\Request))\n#41 {main}\n\"} {\"correlation_id\":\"46b082fc-5a28-4cc4-8bd1-c670f8662e7f\",\"trace_id\":\"8ae7dc26-61c7-42e4-8617-0b85f53e54c1\"}\n[2026-04-27 10:28:19] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage before starting command {\"command\":\"calendar:sync\",\"memoryBeforeCommandInMb\":60.0,\"memoryPeakBeforeCommandInMb\":99.727} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:19] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage before starting command {\"command\":\"mailbox:batch:retry-failed\",\"memoryBeforeCommandInMb\":60.0,\"memoryPeakBeforeCommandInMb\":99.727} {\"correlation_id\":\"e155562a-84a1-45f5-8897-1779f96886f6\",\"trace_id\":\"12141267-1032-479b-aab5-e17e133007d9\"}\n[2026-04-27 10:28:19] local.NOTICE: Calendar sync start {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:19] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage for command {\"command\":\"mailbox:batch:retry-failed\",\"memoryBeforeCommandInMb\":60.0,\"memoryAfterCommandInMB\":62.0,\"memoryPeakBeforeCommandInMb\":99.727,\"memoryPeakAfterCommandInMB\":99.727} {\"correlation_id\":\"e155562a-84a1-45f5-8897-1779f96886f6\",\"trace_id\":\"12141267-1032-479b-aab5-e17e133007d9\"}\n[2026-04-27 10:28:19] local.INFO: [SocialAccountService] Fetching token {\"socialAccountId\":1393,\"provider\":\"google\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:19] local.INFO: [SocialAccountService] Token needs refreshing {\"socialAccountId\":1393,\"provider\":\"google\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:19] local.INFO: [EncryptedTokenManager] Generating access token. {\"mode\":\"legacy\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:19] local.INFO: [SocialAccountService] Refreshing token from provider {\"socialAccountId\":1393,\"provider\":\"google\",\"refreshToken\":\"5aa7e2d96b53201cd16fca5d2e4ef3ad03320971fc064781d18aee3ae7b99fbf\",\"state\":\"full-refresh\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:20] local.ERROR: [SocialAccountService] Failed to refresh token {\"socialAccountId\":1393,\"provider\":\"google\",\"responseBody\":{\"error\":\"invalid_grant\",\"error_description\":\"Account has been deleted\"}} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:20] local.INFO: [SocialAccountObserver] Saving model {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:20] local.ERROR: [SocialAccountService] Failed to refresh token {\"socialAccountId\":1393,\"provider\":\"google\",\"reason\":\"Flow refresh required.\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:20] local.INFO: [SocialAccountService] Fetching token {\"socialAccountId\":1387,\"provider\":\"google\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:20] local.INFO: [SocialAccountService] Token needs refreshing {\"socialAccountId\":1387,\"provider\":\"google\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:20] local.INFO: [EncryptedTokenManager] Generating access token. {\"mode\":\"legacy\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:20] local.INFO: [SocialAccountService] Refreshing token from provider {\"socialAccountId\":1387,\"provider\":\"google\",\"refreshToken\":\"8157ac6de94842937194009e9c50e459253600f799dacf6a40755ffdbeb5bba6\",\"state\":\"full-refresh\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:21] local.ERROR: [SocialAccountService] Failed to refresh token {\"socialAccountId\":1387,\"provider\":\"google\",\"responseBody\":{\"error\":\"invalid_grant\",\"error_description\":\"Account has been deleted\"}} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:21] local.INFO: [SocialAccountObserver] Saving model {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:21] local.ERROR: [SocialAccountService] Failed to refresh token {\"socialAccountId\":1387,\"provider\":\"google\",\"reason\":\"Flow refresh required.\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:21] local.INFO: [SocialAccountService] Fetching token {\"socialAccountId\":1348,\"provider\":\"google\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:21] local.INFO: [SocialAccountService] Token needs refreshing {\"socialAccountId\":1348,\"provider\":\"google\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:21] local.INFO: [EncryptedTokenManager] Generating access token. {\"mode\":\"legacy\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:21] local.INFO: [SocialAccountService] Refreshing token from provider {\"socialAccountId\":1348,\"provider\":\"google\",\"refreshToken\":\"9e7d13d3032d0cb1b79d8e95aef01383e8e91eb52ff8ee960c8a0b6b95cd8c73\",\"state\":\"full-refresh\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:21] local.ERROR: [SocialAccountService] Failed to refresh token {\"socialAccountId\":1348,\"provider\":\"google\",\"responseBody\":{\"error\":\"invalid_grant\",\"error_description\":\"Bad Request\"}} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:21] local.INFO: [SocialAccountObserver] Saving model {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:21] local.ERROR: [SocialAccountService] Failed to refresh token {\"socialAccountId\":1348,\"provider\":\"google\",\"reason\":\"Flow refresh required.\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:21] local.INFO: [SocialAccountService] Fetching token {\"socialAccountId\":1361,\"provider\":\"google\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:21] local.INFO: [SocialAccountService] Token needs refreshing {\"socialAccountId\":1361,\"provider\":\"google\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:21] local.INFO: [EncryptedTokenManager] Generating access token. {\"mode\":\"legacy\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:21] local.INFO: [SocialAccountService] Refreshing token from provider {\"socialAccountId\":1361,\"provider\":\"google\",\"refreshToken\":\"6c843da199c2b9907445329304fcc4ec5057a4ee748d8299641764395c08e1fd\",\"state\":\"full-refresh\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:22] local.ERROR: [SocialAccountService] Failed to refresh token {\"socialAccountId\":1361,\"provider\":\"google\",\"responseBody\":{\"error\":\"invalid_grant\",\"error_description\":\"Account has been deleted\"}} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:22] local.INFO: [SocialAccountObserver] Saving model {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:22] local.ERROR: [SocialAccountService] Failed to refresh token {\"socialAccountId\":1361,\"provider\":\"google\",\"reason\":\"Flow refresh required.\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:22] local.INFO: [SocialAccountService] Fetching token {\"socialAccountId\":1310,\"provider\":\"google\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:22] local.INFO: [SocialAccountService] Token needs refreshing {\"socialAccountId\":1310,\"provider\":\"google\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:22] local.INFO: [EncryptedTokenManager] Generating access token. {\"mode\":\"legacy\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:22] local.INFO: [SocialAccountService] Refreshing token from provider {\"socialAccountId\":1310,\"provider\":\"google\",\"refreshToken\":\"e34818922c2830a660813a63f6169a4a9a992ae2cccd7dc8dd7796cfdb470ef1\",\"state\":\"full-refresh\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:23] local.ERROR: [SocialAccountService] Failed to refresh token {\"socialAccountId\":1310,\"provider\":\"google\",\"responseBody\":{\"error\":\"invalid_grant\",\"error_description\":\"Bad Request\"}} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:23] local.INFO: [SocialAccountObserver] Saving model {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:23] local.ERROR: [SocialAccountService] Failed to refresh token {\"socialAccountId\":1310,\"provider\":\"google\",\"reason\":\"Flow refresh required.\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:23] local.INFO: [SocialAccountService] Fetching token {\"socialAccountId\":1333,\"provider\":\"google\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:23] local.INFO: [SocialAccountService] Token needs refreshing {\"socialAccountId\":1333,\"provider\":\"google\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:23] local.INFO: [EncryptedTokenManager] Generating access token. {\"mode\":\"legacy\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:23] local.INFO: [SocialAccountService] Refreshing token from provider {\"socialAccountId\":1333,\"provider\":\"google\",\"refreshToken\":\"6c902986546d8e8da1dc539b046cdc1d458f519acc972e5b5f1d6a1a295165e0\",\"state\":\"full-refresh\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:23] local.ERROR: [SocialAccountService] Failed to refresh token {\"socialAccountId\":1333,\"provider\":\"google\",\"responseBody\":{\"error\":\"unauthorized_client\",\"error_description\":\"Unauthorized\"}} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:23] local.INFO: [SocialAccountObserver] Saving model {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:23] local.ERROR: [SocialAccountService] Failed to refresh token {\"socialAccountId\":1333,\"provider\":\"google\",\"reason\":\"Flow refresh required.\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:24] local.INFO: [SocialAccountService] Fetching token {\"socialAccountId\":1368,\"provider\":\"google\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:24] local.INFO: [SocialAccountService] Token needs refreshing {\"socialAccountId\":1368,\"provider\":\"google\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:24] local.INFO: [EncryptedTokenManager] Generating access token. {\"mode\":\"legacy\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:24] local.INFO: [SocialAccountService] Refreshing token from provider {\"socialAccountId\":1368,\"provider\":\"google\",\"refreshToken\":\"d2f128898ff8543bd16b69cfae37896ab85119b0f5ed2b431d739593bb600333\",\"state\":\"full-refresh\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:24] local.ERROR: [SocialAccountService] Failed to refresh token {\"socialAccountId\":1368,\"provider\":\"google\",\"responseBody\":{\"error\":\"invalid_grant\",\"error_description\":\"Bad Request\"}} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:24] local.INFO: [SocialAccountObserver] Saving model {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:24] local.ERROR: [SocialAccountService] Failed to refresh token {\"socialAccountId\":1368,\"provider\":\"google\",\"reason\":\"Flow refresh required.\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:24] local.INFO: [SocialAccountService] Fetching token {\"socialAccountId\":1365,\"provider\":\"google\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:24] local.INFO: [SocialAccountService] Token needs refreshing {\"socialAccountId\":1365,\"provider\":\"google\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:24] local.INFO: [EncryptedTokenManager] Generating access token. {\"mode\":\"legacy\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:24] local.INFO: [SocialAccountService] Refreshing token from provider {\"socialAccountId\":1365,\"provider\":\"google\",\"refreshToken\":\"7676e4a9afcd082b413248ab5ec6e487021fec6a9bdf315860a59cefad9caad8\",\"state\":\"full-refresh\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:25] local.ERROR: [SocialAccountService] Failed to refresh token {\"socialAccountId\":1365,\"provider\":\"google\",\"responseBody\":{\"error\":\"unauthorized_client\",\"error_description\":\"Unauthorized\"}} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:25] local.INFO: [SocialAccountObserver] Saving model {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:25] local.ERROR: [SocialAccountService] Failed to refresh token {\"socialAccountId\":1365,\"provider\":\"google\",\"reason\":\"Flow refresh required.\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:25] local.INFO: [SocialAccountService] Fetching token {\"socialAccountId\":1364,\"provider\":\"google\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:25] local.INFO: [SocialAccountService] Token needs refreshing {\"socialAccountId\":1364,\"provider\":\"google\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:25] local.INFO: [EncryptedTokenManager] Generating access token. {\"mode\":\"legacy\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:25] local.INFO: [SocialAccountService] Refreshing token from provider {\"socialAccountId\":1364,\"provider\":\"google\",\"refreshToken\":\"dd5882ebce76e645292ce33ae74238abbb77c0a4ecc6a2bfe723cad82e72ba8e\",\"state\":\"full-refresh\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:25] local.ERROR: [SocialAccountService] Failed to refresh token {\"socialAccountId\":1364,\"provider\":\"google\",\"responseBody\":{\"error\":\"unauthorized_client\",\"error_description\":\"Unauthorized\"}} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:25] local.INFO: [SocialAccountObserver] Saving model {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:25] local.ERROR: [SocialAccountService] Failed to refresh token {\"socialAccountId\":1364,\"provider\":\"google\",\"reason\":\"Flow refresh required.\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:25] local.INFO: [SocialAccountService] Fetching token {\"socialAccountId\":1370,\"provider\":\"office\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:25] local.INFO: [SocialAccountService] Token needs refreshing {\"socialAccountId\":1370,\"provider\":\"office\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:25] local.INFO: [EncryptedTokenManager] Generating access token. {\"mode\":\"legacy\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:25] local.INFO: [SocialAccountService] Refreshing token from provider {\"socialAccountId\":1370,\"provider\":\"office\",\"refreshToken\":\"b7ee8035306d0043cea6e00e7c4fe14f745e44074a1194db62a31cdf8b70af3e\",\"state\":\"full-refresh\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:26] local.ERROR: [SocialAccountService] Failed to refresh token {\"socialAccountId\":1370,\"provider\":\"office\",\"responseBody\":\"{\\\"error\\\":\\\"invalid_client\\\",\\\"error_description\\\":\\\"AADSTS7000215: Invalid client secret provided. Ensure the secret being sent in the request is the client secret value, not the client secret ID, for a secret added to app 'bbcbb2ef-6200-4fae-82bd-d81f5dd738da'. Trace ID: 2be54cfa-1691-4388-a284-754fdf414400 Correlation ID: 9a934049-899b-45c5-99a5-15c9c470d8a2 Timestamp: 2026-04-27 10:28:26Z\\\",\\\"error_codes\\\":[7000215],\\\"timestamp\\\":\\\"2026-04-27 10:28:26Z\\\",\\\"trace_id\\\":\\\"2be54cfa-1691-4388-a284-754fdf414400\\\",\\\"correlation_id\\\":\\\"9a934049-899b-45c5-99a5-15c9c470d8a2\\\",\\\"error_uri\\\":\\\"https://login.microsoftonline.com/error?code=7000215\\\"}\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:26] local.INFO: [SocialAccountObserver] Saving model {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:26] local.ERROR: [SocialAccountService] Failed to refresh token {\"socialAccountId\":1370,\"provider\":\"office\",\"reason\":\"Flow refresh required.\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:26] local.INFO: [SocialAccountService] Fetching token {\"socialAccountId\":1202,\"provider\":\"office\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:26] local.INFO: [SocialAccountService] Token needs refreshing {\"socialAccountId\":1202,\"provider\":\"office\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:26] local.INFO: [EncryptedTokenManager] Generating access token. {\"mode\":\"legacy\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:26] local.INFO: [SocialAccountService] Refreshing token from provider {\"socialAccountId\":1202,\"provider\":\"office\",\"refreshToken\":\"b458799ccc29b21a6e2eb5260fdb63e49ccba21bf942a3973fb63799bd7f0afe\",\"state\":\"full-refresh\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:26] local.ERROR: [SocialAccountService] Failed to refresh token {\"socialAccountId\":1202,\"provider\":\"office\",\"responseBody\":\"{\\\"error\\\":\\\"invalid_client\\\",\\\"error_description\\\":\\\"AADSTS7000215: Invalid client secret provided. Ensure the secret being sent in the request is the client secret value, not the client secret ID, for a secret added to app 'bbcbb2ef-6200-4fae-82bd-d81f5dd738da'. Trace ID: 27729e9a-0943-41bd-9752-4ec9a8444500 Correlation ID: 824a9761-9387-4c53-ae31-5c242b1d4298 Timestamp: 2026-04-27 10:28:26Z\\\",\\\"error_codes\\\":[7000215],\\\"timestamp\\\":\\\"2026-04-27 10:28:26Z\\\",\\\"trace_id\\\":\\\"27729e9a-0943-41bd-9752-4ec9a8444500\\\",\\\"correlation_id\\\":\\\"824a9761-9387-4c53-ae31-5c242b1d4298\\\",\\\"error_uri\\\":\\\"https://login.microsoftonline.com/error?code=7000215\\\"}\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:26] local.INFO: [SocialAccountObserver] Saving model {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:26] local.ERROR: [SocialAccountService] Failed to refresh token {\"socialAccountId\":1202,\"provider\":\"office\",\"reason\":\"Flow refresh required.\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:26] local.INFO: [SocialAccountService] Fetching token {\"socialAccountId\":1502,\"provider\":\"google\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:26] local.INFO: [SocialAccountService] Token needs refreshing {\"socialAccountId\":1502,\"provider\":\"google\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:26] local.INFO: [EncryptedTokenManager] Generating access token. {\"mode\":\"legacy\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:26] local.INFO: [SocialAccountService] Refreshing token from provider {\"socialAccountId\":1502,\"provider\":\"google\",\"refreshToken\":\"d417c92ebaa137295a04675f715d0511ae8acac9d779b102eac50d6300116d3e\",\"state\":\"connected\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:27] local.INFO: [SocialAccountObserver] Saving model {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:27] local.INFO: [SocialAccountObserver] Access token was modified, encrypting {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:27] local.INFO: [SocialAccountService] Token refreshed {\"socialAccountId\":1502,\"provider\":\"google\",\"state\":\"connected\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:27] local.INFO: Calendar sync job dispatched {\"calendar_id\":501} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:27] local.INFO: [SocialAccountService] Fetching token {\"socialAccountId\":1300,\"provider\":\"google\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:27] local.INFO: [SocialAccountService] Token needs refreshing {\"socialAccountId\":1300,\"provider\":\"google\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:27] local.INFO: [EncryptedTokenManager] Generating access token. {\"mode\":\"legacy\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:27] local.INFO: [SocialAccountService] Refreshing token from provider {\"socialAccountId\":1300,\"provider\":\"google\",\"refreshToken\":\"4b811db0725fd9602a95943519a7da935e2a5065da7d9ebfcb170752e3e1ddb8\",\"state\":\"full-refresh\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:27] local.ERROR: [SocialAccountService] Failed to refresh token {\"socialAccountId\":1300,\"provider\":\"google\",\"responseBody\":{\"error\":\"invalid_grant\",\"error_description\":\"Account has been deleted\"}} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:27] local.INFO: [SocialAccountObserver] Saving model {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:27] local.ERROR: [SocialAccountService] Failed to refresh token {\"socialAccountId\":1300,\"provider\":\"google\",\"reason\":\"Flow refresh required.\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:27] local.INFO: [SocialAccountService] Fetching token {\"socialAccountId\":1409,\"provider\":\"google\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:27] local.INFO: [SocialAccountService] Token needs refreshing {\"socialAccountId\":1409,\"provider\":\"google\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:27] local.INFO: [EncryptedTokenManager] Generating access token. {\"mode\":\"legacy\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:27] local.INFO: [SocialAccountService] Refreshing token from provider {\"socialAccountId\":1409,\"provider\":\"google\",\"refreshToken\":\"e2a3f2d06894894eed1ee87d9db1ace77d4d42ee6e1288a8940ad2c10333b0c4\",\"state\":\"full-refresh\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:28] local.ERROR: [SocialAccountService] Failed to refresh token {\"socialAccountId\":1409,\"provider\":\"google\",\"responseBody\":{\"error\":\"invalid_grant\",\"error_description\":\"Bad Request\"}} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:28] local.INFO: [SocialAccountObserver] Saving model {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:28] local.ERROR: [SocialAccountService] Failed to refresh token {\"socialAccountId\":1409,\"provider\":\"google\",\"reason\":\"Flow refresh required.\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:28] local.INFO: [SocialAccountService] Fetching token {\"socialAccountId\":1352,\"provider\":\"google\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:28] local.INFO: [SocialAccountService] Token needs refreshing {\"socialAccountId\":1352,\"provider\":\"google\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:28] local.INFO: [EncryptedTokenManager] Generating access token. {\"mode\":\"legacy\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:28] local.INFO: [SocialAccountService] Refreshing token from provider {\"socialAccountId\":1352,\"provider\":\"google\",\"refreshToken\":\"dd4b16b00fdc1216da6b717c02338c073636e29162826b2de6db3f064fc029eb\",\"state\":\"full-refresh\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:28] local.ERROR: [SocialAccountService] Failed to refresh token {\"socialAccountId\":1352,\"provider\":\"google\",\"responseBody\":{\"error\":\"unauthorized_client\",\"error_description\":\"Unauthorized\"}} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:28] local.INFO: [SocialAccountObserver] Saving model {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:28] local.ERROR: [SocialAccountService] Failed to refresh token {\"socialAccountId\":1352,\"provider\":\"google\",\"reason\":\"Flow refresh required.\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:28] local.INFO: [SocialAccountService] Fetching token {\"socialAccountId\":1296,\"provider\":\"office\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:28] local.INFO: [SocialAccountService] Token needs refreshing {\"socialAccountId\":1296,\"provider\":\"office\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:28] local.INFO: [EncryptedTokenManager] Generating access token. {\"mode\":\"legacy\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:28] local.INFO: [SocialAccountService] Refreshing token from provider {\"socialAccountId\":1296,\"provider\":\"office\",\"refreshToken\":\"011ae723c9d800c674e0b4be76f49fc046dac7d501b66c59ef0d9549cfa56ae5\",\"state\":\"full-refresh\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:28] local.ERROR: [SocialAccountService] Failed to refresh token {\"socialAccountId\":1296,\"provider\":\"office\",\"responseBody\":\"{\\\"error\\\":\\\"invalid_client\\\",\\\"error_description\\\":\\\"AADSTS7000215: Invalid client secret provided. Ensure the secret being sent in the request is the client secret value, not the client secret ID, for a secret added to app 'bbcbb2ef-6200-4fae-82bd-d81f5dd738da'. Trace ID: 73b326f0-9fcd-4ff5-ad61-edd6a0504500 Correlation ID: 6bd8fef3-b24c-46d8-8bba-a9b2ab41c108 Timestamp: 2026-04-27 10:28:28Z\\\",\\\"error_codes\\\":[7000215],\\\"timestamp\\\":\\\"2026-04-27 10:28:28Z\\\",\\\"trace_id\\\":\\\"73b326f0-9fcd-4ff5-ad61-edd6a0504500\\\",\\\"correlation_id\\\":\\\"6bd8fef3-b24c-46d8-8bba-a9b2ab41c108\\\",\\\"error_uri\\\":\\\"https://login.microsoftonline.com/error?code=7000215\\\"}\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:28] local.INFO: [SocialAccountObserver] Saving model {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:28] local.ERROR: [SocialAccountService] Failed to refresh token {\"socialAccountId\":1296,\"provider\":\"office\",\"reason\":\"Flow refresh required.\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:28] local.INFO: [SocialAccountService] Fetching token {\"socialAccountId\":391,\"provider\":\"office\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:28] local.INFO: [SocialAccountService] Token needs refreshing {\"socialAccountId\":391,\"provider\":\"office\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:28] local.INFO: [EncryptedTokenManager] Generating access token. {\"mode\":\"legacy\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:28] local.INFO: [SocialAccountService] Refreshing token from provider {\"socialAccountId\":391,\"provider\":\"office\",\"refreshToken\":\"00045eebae0f39b34887c6d53f92ae78064f7145e1f4b67754aebd03cfb2d881\",\"state\":\"full-refresh\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:28] local.INFO: [SocialAccountService] Fetching token {\"socialAccountId\":1502,\"provider\":\"google\"} {\"correlation_id\":\"8a9488be-81de-40f5-8554-2503eec42566\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:28] local.INFO: [SocialAccountService] Token retrieved {\"socialAccountId\":1502,\"provider\":\"google\"} {\"correlation_id\":\"8a9488be-81de-40f5-8554-2503eec42566\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:28] local.INFO: [EncryptedTokenManager] Generating access token. {\"mode\":\"legacy\"} {\"correlation_id\":\"8a9488be-81de-40f5-8554-2503eec42566\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:29] local.INFO: [Calendar] Processing sync {\"calendarId\":\"a33076c1-8d97-431a-99f0-85c9524e118b\",\"from\":null,\"to\":null,\"delta\":\"CIiFh8TP44kDEIiFh8TP44kDGAUgkZvkzgIokZvkzgI=\",\"last_sync\":\"2024-12-09 07:12:53\",\"dateMode\":\"daily\"} {\"correlation_id\":\"8a9488be-81de-40f5-8554-2503eec42566\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:29] local.INFO: [CrmOwnerResolver] Integration owner matched as CRM Owner {\"crm_provider\":\"integration-app\",\"crm_owner\":1695,\"team_id\":3143} {\"correlation_id\":\"8a9488be-81de-40f5-8554-2503eec42566\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:29] local.INFO: [SocialAccountService] Fetching token {\"socialAccountId\":1502,\"provider\":\"google\"} {\"correlation_id\":\"8a9488be-81de-40f5-8554-2503eec42566\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:29] local.INFO: [SocialAccountService] Token retrieved {\"socialAccountId\":1502,\"provider\":\"google\"} {\"correlation_id\":\"8a9488be-81de-40f5-8554-2503eec42566\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:29] local.INFO: [EncryptedTokenManager] Generating access token. {\"mode\":\"legacy\"} {\"correlation_id\":\"8a9488be-81de-40f5-8554-2503eec42566\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:29] local.ERROR: [SocialAccountService] Failed to refresh token {\"socialAccountId\":391,\"provider\":\"office\",\"responseBody\":\"{\\\"error\\\":\\\"invalid_client\\\",\\\"error_description\\\":\\\"AADSTS7000215: Invalid client secret provided. Ensure the secret being sent in the request is the client secret value, not the client secret ID, for a secret added to app 'bbcbb2ef-6200-4fae-82bd-d81f5dd738da'. Trace ID: c5f1db6f-22fb-41cc-bd89-c46497634200 Correlation ID: 7b4df0cc-bf50-4017-9b1a-e25ffb6fe9bf Timestamp: 2026-04-27 10:28:29Z\\\",\\\"error_codes\\\":[7000215],\\\"timestamp\\\":\\\"2026-04-27 10:28:29Z\\\",\\\"trace_id\\\":\\\"c5f1db6f-22fb-41cc-bd89-c46497634200\\\",\\\"correlation_id\\\":\\\"7b4df0cc-bf50-4017-9b1a-e25ffb6fe9bf\\\",\\\"error_uri\\\":\\\"https://login.microsoftonline.com/error?code=7000215\\\"}\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:29] local.INFO: [SocialAccountObserver] Saving model {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:29] local.ERROR: [SocialAccountService] Failed to refresh token {\"socialAccountId\":391,\"provider\":\"office\",\"reason\":\"Flow refresh required.\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:29] local.INFO: [SocialAccountService] Fetching token {\"socialAccountId\":1271,\"provider\":\"office\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:29] local.INFO: [SocialAccountService] Token needs refreshing {\"socialAccountId\":1271,\"provider\":\"office\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:29] local.INFO: [EncryptedTokenManager] Generating access token. {\"mode\":\"legacy\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:29] local.INFO: [SocialAccountService] Refreshing token from provider {\"socialAccountId\":1271,\"provider\":\"office\",\"refreshToken\":\"118cde2c06993147b07ccaec4cbcd5026a819dea6c71081166a492933e392afb\",\"state\":\"full-refresh\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:29] local.INFO: [Google Calendar] Failed to watch channel for calendar {\"calendarId\":\"a33076c1-8d97-431a-99f0-85c9524e118b\",\"code\":400,\"reason\":\"{\n \\\"error\\\": {\n \\\"errors\\\": [\n {\n \\\"domain\\\": \\\"global\\\",\n \\\"reason\\\": \\\"push.webhookUrlNotHttps\\\",\n \\\"message\\\": \\\"WebHook callback must be HTTPS: /webhook/calendar/google?resourceType=event\\\"\n }\n ],\n \\\"code\\\": 400,\n \\\"message\\\": \\\"WebHook callback must be HTTPS: /webhook/calendar/google?resourceType=event\\\"\n }\n}\"} {\"correlation_id\":\"8a9488be-81de-40f5-8554-2503eec42566\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:29] local.WARNING: [Calendar] Sync failed {\"calendarId\":\"a33076c1-8d97-431a-99f0-85c9524e118b\",\"code\":400,\"reason\":\"{\n \\\"error\\\": {\n \\\"errors\\\": [\n {\n \\\"domain\\\": \\\"global\\\",\n \\\"reason\\\": \\\"push.webhookUrlNotHttps\\\",\n \\\"message\\\": \\\"WebHook callback must be HTTPS: /webhook/calendar/google?resourceType=event\\\"\n }\n ],\n \\\"code\\\": 400,\n \\\"message\\\": \\\"WebHook callback must be HTTPS: /webhook/calendar/google?resourceType=event\\\"\n }\n}\"} {\"correlation_id\":\"8a9488be-81de-40f5-8554-2503eec42566\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:29] local.ERROR: [SocialAccountService] Failed to refresh token {\"socialAccountId\":1271,\"provider\":\"office\",\"responseBody\":\"{\\\"error\\\":\\\"invalid_client\\\",\\\"error_description\\\":\\\"AADSTS7000215: Invalid client secret provided. Ensure the secret being sent in the request is the client secret value, not the client secret ID, for a secret added to app 'bbcbb2ef-6200-4fae-82bd-d81f5dd738da'. Trace ID: 8d6e82ba-88c3-4ec9-95ee-a4a905803900 Correlation ID: 6ede51d5-704d-4e24-9fb7-88f69f3da5db Timestamp: 2026-04-27 10:28:29Z\\\",\\\"error_codes\\\":[7000215],\\\"timestamp\\\":\\\"2026-04-27 10:28:29Z\\\",\\\"trace_id\\\":\\\"8d6e82ba-88c3-4ec9-95ee-a4a905803900\\\",\\\"correlation_id\\\":\\\"6ede51d5-704d-4e24-9fb7-88f69f3da5db\\\",\\\"error_uri\\\":\\\"https://login.microsoftonline.com/error?code=7000215\\\"}\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:30] local.INFO: [SocialAccountObserver] Saving model {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:30] local.ERROR: [SocialAccountService] Failed to refresh token {\"socialAccountId\":1271,\"provider\":\"office\",\"reason\":\"Flow refresh required.\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:30] local.INFO: [SocialAccountService] Fetching token {\"socialAccountId\":1351,\"provider\":\"google\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:30] local.INFO: [SocialAccountService] Token needs refreshing {\"socialAccountId\":1351,\"provider\":\"google\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:30] local.INFO: [EncryptedTokenManager] Generating access token. {\"mode\":\"legacy\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:30] local.INFO: [SocialAccountService] Refreshing token from provider {\"socialAccountId\":1351,\"provider\":\"google\",\"refreshToken\":\"4271d15b9e60a606439caddc68337f783e472c85b03dacff14d1b6dfded9051c\",\"state\":\"full-refresh\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:30] local.ERROR: [SocialAccountService] Failed to refresh token {\"socialAccountId\":1351,\"provider\":\"google\",\"responseBody\":{\"error\":\"invalid_grant\",\"error_description\":\"Token has been expired or revoked.\"}} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:30] local.INFO: [SocialAccountObserver] Saving model {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:30] local.ERROR: [SocialAccountService] Failed to refresh token {\"socialAccountId\":1351,\"provider\":\"google\",\"reason\":\"Flow refresh required.\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:30] local.INFO: [SocialAccountService] Fetching token {\"socialAccountId\":1366,\"provider\":\"google\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:30] local.INFO: [SocialAccountService] Token needs refreshing {\"socialAccountId\":1366,\"provider\":\"google\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:30] local.INFO: [EncryptedTokenManager] Generating access token. {\"mode\":\"legacy\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:30] local.INFO: [SocialAccountService] Refreshing token from provider {\"socialAccountId\":1366,\"provider\":\"google\",\"refreshToken\":\"ae21385059b2eebfd43f68aecd56eccd702a1aabb6598f1f7ab594ed8af491b4\",\"state\":\"full-refresh\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:30] local.ERROR: [SocialAccountService] Failed to refresh token {\"socialAccountId\":1366,\"provider\":\"google\",\"responseBody\":{\"error\":\"invalid_grant\",\"error_description\":\"Bad Request\"}} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:30] local.INFO: [SocialAccountObserver] Saving model {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:30] local.ERROR: [SocialAccountService] Failed to refresh token {\"socialAccountId\":1366,\"provider\":\"google\",\"reason\":\"Flow refresh required.\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:30] local.INFO: [SocialAccountService] Fetching token {\"socialAccountId\":1115,\"provider\":\"google\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:30] local.INFO: [SocialAccountService] Token needs refreshing {\"socialAccountId\":1115,\"provider\":\"google\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:30] local.INFO: [EncryptedTokenManager] Generating access token. {\"mode\":\"legacy\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:30] local.INFO: [SocialAccountService] Refreshing token from provider {\"socialAccountId\":1115,\"provider\":\"google\",\"refreshToken\":\"356b60f12e262a5e24d3042386ef47d6a6cfe3074c242f4426edcec8646192b1\",\"state\":\"connected\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:30] local.INFO: [SocialAccountObserver] Saving model {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:30] local.INFO: [SocialAccountObserver] Access token was modified, encrypting {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:31] local.INFO: [SocialAccountService] Token refreshed {\"socialAccountId\":1115,\"provider\":\"google\",\"state\":\"connected\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:31] local.INFO: Calendar sync job dispatched {\"calendar_id\":378} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:31] local.INFO: [SocialAccountService] Fetching token {\"socialAccountId\":1421,\"provider\":\"office\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:31] local.INFO: [SocialAccountService] Token needs refreshing {\"socialAccountId\":1421,\"provider\":\"office\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:31] local.INFO: [EncryptedTokenManager] Generating access token. {\"mode\":\"legacy\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:31] local.INFO: [SocialAccountService] Refreshing token from provider {\"socialAccountId\":1421,\"provider\":\"office\",\"refreshToken\":\"9e3e287aa0cc900726e3d4eead2600fb64aa34b437790f6a1fd8048306cb2db9\",\"state\":\"connected\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:31] local.INFO: [SocialAccountService] Fetching token {\"socialAccountId\":1115,\"provider\":\"google\"} {\"correlation_id\":\"8fdf411d-8a4f-46c1-a46a-cc84f3c2f2b1\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:31] local.INFO: [SocialAccountService] Token retrieved {\"socialAccountId\":1115,\"provider\":\"google\"} {\"correlation_id\":\"8fdf411d-8a4f-46c1-a46a-cc84f3c2f2b1\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:31] local.INFO: [EncryptedTokenManager] Generating access token. {\"mode\":\"legacy\"} {\"correlation_id\":\"8fdf411d-8a4f-46c1-a46a-cc84f3c2f2b1\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:31] local.INFO: [Calendar] Processing sync {\"calendarId\":\"2676cb6d-f86c-427e-bf78-591e388e3c1e\",\"from\":null,\"to\":null,\"delta\":\"CJ_x49O3jpIDEJ_x49O3jpIDGAUgw67KlwMow67KlwM=\",\"last_sync\":\"2026-01-19 07:48:40\",\"dateMode\":\"daily\"} {\"correlation_id\":\"8fdf411d-8a4f-46c1-a46a-cc84f3c2f2b1\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:31] local.WARNING: [Pipedrive] Account not connected for user {\"userId\":\"e6538737-e7b4-455f-a37a-3e79b665a220\",\"account\":{\"Jiminny\\\\Models\\\\SocialAccount\":{\"id\":1116,\"sociable_id\":241,\"provider_user_id\":\"19555731\",\"expires\":1775683749,\"refresh_token_expires\":null,\"provider\":\"pipedrive\",\"state\":\"full-refresh\",\"auth_scope\":\"base,deals:full,activities:full,contacts:full,search:read\",\"retry_after\":null,\"created_at\":\"2023-09-08 09:44:29\",\"updated_at\":\"2026-04-08 22:58:34\"}}} {\"correlation_id\":\"8fdf411d-8a4f-46c1-a46a-cc84f3c2f2b1\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:31] local.INFO: [CrmOwnerResolver] Integration owner is not connected, attempting team members {\"crm_provider\":\"pipedrive\",\"crm_owner\":241,\"team_id\":19} {\"correlation_id\":\"8fdf411d-8a4f-46c1-a46a-cc84f3c2f2b1\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:31] local.INFO: [CrmOwnerResolver] No team members found with active crm connection {\"crm_provider\":\"pipedrive\",\"team_id\":19} {\"correlation_id\":\"8fdf411d-8a4f-46c1-a46a-cc84f3c2f2b1\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:31] local.INFO: [CrmOwnerResolver] No team member found with active crm connection {\"crm_provider\":\"pipedrive\",\"team_id\":19} {\"correlation_id\":\"8fdf411d-8a4f-46c1-a46a-cc84f3c2f2b1\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:31] local.WARNING: [Calendar] CRM disconnected for user so events will not be matched {\"provider\":\"pipedrive\",\"user_id\":241,\"message\":\"Your Pipedrive account has become disconnected. Please login to Jiminny to reconnect.\"} {\"correlation_id\":\"8fdf411d-8a4f-46c1-a46a-cc84f3c2f2b1\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:31] local.INFO: [SocialAccountService] Fetching token {\"socialAccountId\":1115,\"provider\":\"google\"} {\"correlation_id\":\"8fdf411d-8a4f-46c1-a46a-cc84f3c2f2b1\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:31] local.INFO: [SocialAccountService] Token retrieved {\"socialAccountId\":1115,\"provider\":\"google\"} {\"correlation_id\":\"8fdf411d-8a4f-46c1-a46a-cc84f3c2f2b1\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:31] local.INFO: [EncryptedTokenManager] Generating access token. {\"mode\":\"legacy\"} {\"correlation_id\":\"8fdf411d-8a4f-46c1-a46a-cc84f3c2f2b1\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:31] local.INFO: [Google Calendar] Failed to watch channel for calendar {\"calendarId\":\"2676cb6d-f86c-427e-bf78-591e388e3c1e\",\"code\":400,\"reason\":\"{\n \\\"error\\\": {\n \\\"errors\\\": [\n {\n \\\"domain\\\": \\\"global\\\",\n \\\"reason\\\": \\\"push.webhookUrlNotHttps\\\",\n \\\"message\\\": \\\"WebHook callback must be HTTPS: /webhook/calendar/google?resourceType=event\\\"\n }\n ],\n \\\"code\\\": 400,\n \\\"message\\\": \\\"WebHook callback must be HTTPS: /webhook/calendar/google?resourceType=event\\\"\n }\n}\"} {\"correlation_id\":\"8fdf411d-8a4f-46c1-a46a-cc84f3c2f2b1\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:31] local.WARNING: [Calendar] Sync failed {\"calendarId\":\"2676cb6d-f86c-427e-bf78-591e388e3c1e\",\"code\":400,\"reason\":\"{\n \\\"error\\\": {\n \\\"errors\\\": [\n {\n \\\"domain\\\": \\\"global\\\",\n \\\"reason\\\": \\\"push.webhookUrlNotHttps\\\",\n \\\"message\\\": \\\"WebHook callback must be HTTPS: /webhook/calendar/google?resourceType=event\\\"\n }\n ],\n \\\"code\\\": 400,\n \\\"message\\\": \\\"WebHook callback must be HTTPS: /webhook/calendar/google?resourceType=event\\\"\n }\n}\"} {\"correlation_id\":\"8fdf411d-8a4f-46c1-a46a-cc84f3c2f2b1\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:32] local.INFO: [SocialAccountObserver] Saving model {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:32] local.INFO: [SocialAccountObserver] Access token was modified, encrypting {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:32] local.INFO: [SocialAccountObserver] Refresh token was modified, encrypting {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:32] local.INFO: [SocialAccountService] Token refreshed {\"socialAccountId\":1421,\"provider\":\"office\",\"state\":\"connected\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:32] local.INFO: Calendar sync job dispatched {\"calendar_id\":504} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:32] local.NOTICE: Calendar sync end {\"retrieved_calendars\":31,\"processed_calendars\":3} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:32] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage for command {\"command\":\"calendar:sync\",\"memoryBeforeCommandInMb\":60.0,\"memoryAfterCommandInMB\":62.0,\"memoryPeakBeforeCommandInMb\":99.727,\"memoryPeakAfterCommandInMB\":99.727} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:33] local.INFO: [SocialAccountService] Fetching token {\"socialAccountId\":1421,\"provider\":\"office\"} {\"correlation_id\":\"52b2ff86-82c7-4ed0-b528-35d7d00d69f0\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:33] local.INFO: [SocialAccountService] Token retrieved {\"socialAccountId\":1421,\"provider\":\"office\"} {\"correlation_id\":\"52b2ff86-82c7-4ed0-b528-35d7d00d69f0\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:33] local.INFO: [EncryptedTokenManager] Generating access token. {\"mode\":\"legacy\"} {\"correlation_id\":\"52b2ff86-82c7-4ed0-b528-35d7d00d69f0\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:34] local.INFO: [Calendar] Processing sync {\"calendarId\":\"9e8b1a2c-1a8f-42bd-b161-810fc0baf540\",\"from\":null,\"to\":null,\"delta\":\"R0usmcdvmMuZCBYV0hguCMx0nGVsTQEodM41jSlu-5eY2JUTYHZXE-bDro2tnepp8q_Vq3ITNS8CkncjGjcBfaHkza05dCTIlaXcSIoPNGOl9vNckYy_ZhVgE1SWvgRcCeVT-SyogdB9hP-oaWbH9r51OQrrzAwmalK5aCksyUVh9jG_A-mGVzRPnUB4SKa77o5P659lpx1egON48YU2tQ.D2oErKma-mQrBdXxAKdH7beUkp6HS2PXUWYLCgvbP2c\",\"last_sync\":\"2026-04-27 06:28:41\",\"dateMode\":\"daily\"} {\"correlation_id\":\"52b2ff86-82c7-4ed0-b528-35d7d00d69f0\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:34] local.INFO: [SocialAccountService] Fetching token {\"socialAccountId\":1499,\"provider\":\"hubspot\"} {\"correlation_id\":\"52b2ff86-82c7-4ed0-b528-35d7d00d69f0\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:34] local.INFO: [SocialAccountService] Token retrieved {\"socialAccountId\":1499,\"provider\":\"hubspot\"} {\"correlation_id\":\"52b2ff86-82c7-4ed0-b528-35d7d00d69f0\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:34] local.INFO: [EncryptedTokenManager] Generating access token. {\"mode\":\"legacy\"} {\"correlation_id\":\"52b2ff86-82c7-4ed0-b528-35d7d00d69f0\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:34] local.INFO: [CrmOwnerResolver] Integration owner matched as CRM Owner {\"crm_provider\":\"hubspot\",\"crm_owner\":89,\"team_id\":2} {\"correlation_id\":\"52b2ff86-82c7-4ed0-b528-35d7d00d69f0\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:34] local.INFO: [MS Office Calendar] Skipping delta sync for daily mode {\"calendarId\":\"9e8b1a2c-1a8f-42bd-b161-810fc0baf540\"} {\"correlation_id\":\"52b2ff86-82c7-4ed0-b528-35d7d00d69f0\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:29:06] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage before starting command {\"command\":\"meeting-bot:schedule-bot\",\"memoryBeforeCommandInMb\":60.0,\"memoryPeakBeforeCommandInMb\":99.727} {\"correlation_id\":\"7e95c05b-50c8-4e0b-a01c-efeed6af544c\",\"trace_id\":\"07db4d4f-d6e7-44d9-8e54-4f038ea69afb\"}\n[2026-04-27 10:29:06] local.INFO: [ScheduleBotCommand] Number of activities to be captured: 0 {\"correlation_id\":\"7e95c05b-50c8-4e0b-a01c-efeed6af544c\",\"trace_id\":\"07db4d4f-d6e7-44d9-8e54-4f038ea69afb\"}\n[2026-04-27 10:29:06] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage for command {\"command\":\"meeting-bot:schedule-bot\",\"memoryBeforeCommandInMb\":60.0,\"memoryAfterCommandInMB\":62.0,\"memoryPeakBeforeCommandInMb\":99.727,\"memoryPeakAfterCommandInMB\":99.727} {\"correlation_id\":\"7e95c05b-50c8-4e0b-a01c-efeed6af544c\",\"trace_id\":\"07db4d4f-d6e7-44d9-8e54-4f038ea69afb\"}\n[2026-04-27 10:29:09] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage before starting command {\"command\":\"dialers:monitor-activities\",\"memoryBeforeCommandInMb\":60.0,\"memoryPeakBeforeCommandInMb\":99.727} {\"correlation_id\":\"e6e7fbf8-6e80-4529-858e-a4e95d0d3805\",\"trace_id\":\"f91a115a-9fdc-46ce-b9f2-13a1e6d0e325\"}\n[2026-04-27 10:29:09] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage for command {\"command\":\"dialers:monitor-activities\",\"memoryBeforeCommandInMb\":60.0,\"memoryAfterCommandInMB\":62.0,\"memoryPeakBeforeCommandInMb\":99.727,\"memoryPeakAfterCommandInMB\":99.727} {\"correlation_id\":\"e6e7fbf8-6e80-4529-858e-a4e95d0d3805\",\"trace_id\":\"f91a115a-9fdc-46ce-b9f2-13a1e6d0e325\"}\n[2026-04-27 10:29:10] local.NOTICE: Monitoring start {\"correlation_id\":\"211e5e36-1c6c-4198-b928-d2f1525ab0ea\",\"trace_id\":\"c26e980d-6e32-426d-9667-f1d902432b8d\"}\n[2026-04-27 10:29:10] local.NOTICE: Monitoring end {\"correlation_id\":\"211e5e36-1c6c-4198-b928-d2f1525ab0ea\",\"trace_id\":\"c26e980d-6e32-426d-9667-f1d902432b8d\"}\n[2026-04-27 10:29:12] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage before starting command {\"command\":\"mailbox:skip-lists:refresh\",\"memoryBeforeCommandInMb\":60.0,\"memoryPeakBeforeCommandInMb\":99.727} {\"correlation_id\":\"684bdf68-76ee-42dd-9d97-62f414a33b1b\",\"trace_id\":\"284c98ef-5b7f-4572-996c-72c5bc2995f9\"}\n[2026-04-27 10:29:13] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage for command {\"command\":\"mailbox:skip-lists:refresh\",\"memoryBeforeCommandInMb\":60.0,\"memoryAfterCommandInMB\":62.0,\"memoryPeakBeforeCommandInMb\":99.727,\"memoryPeakAfterCommandInMB\":99.727} {\"correlation_id\":\"684bdf68-76ee-42dd-9d97-62f414a33b1b\",\"trace_id\":\"284c98ef-5b7f-4572-996c-72c5bc2995f9\"}\n[2026-04-27 10:29:18] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage before starting command {\"command\":\"mailbox:batch:process\",\"memoryBeforeCommandInMb\":60.0,\"memoryPeakBeforeCommandInMb\":99.727} {\"correlation_id\":\"6e74b148-ae54-42ff-ab72-2ded18d10ff4\",\"trace_id\":\"d05bc9a0-450c-45b2-be3b-225a551ed909\"}\n[2026-04-27 10:29:18] local.INFO: [EmailSchedule] STARTING batch process {\"host\":\"docker_lamp_1\"} {\"correlation_id\":\"6e74b148-ae54-42ff-ab72-2ded18d10ff4\",\"trace_id\":\"d05bc9a0-450c-45b2-be3b-225a551ed909\"}\n[2026-04-27 10:29:18] local.INFO: [EmailSchedule] FINISHED batch process {\"host\":\"docker_lamp_1\",\"processed\":0} {\"correlation_id\":\"6e74b148-ae54-42ff-ab72-2ded18d10ff4\",\"trace_id\":\"d05bc9a0-450c-45b2-be3b-225a551ed909\"}\n[2026-04-27 10:29:18] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage for command {\"command\":\"mailbox:batch:process\",\"memoryBeforeCommandInMb\":60.0,\"memoryAfterCommandInMB\":62.0,\"memoryPeakBeforeCommandInMb\":99.727,\"memoryPeakAfterCommandInMB\":99.727} {\"correlation_id\":\"6e74b148-ae54-42ff-ab72-2ded18d10ff4\",\"trace_id\":\"d05bc9a0-450c-45b2-be3b-225a551ed909\"}\n[2026-04-27 10:30:08] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage before starting command {\"command\":\"meeting-bot:schedule-bot\",\"memoryBeforeCommandInMb\":60.0,\"memoryPeakBeforeCommandInMb\":99.727} {\"correlation_id\":\"24b807e0-b413-4ecc-a89b-365280be88ac\",\"trace_id\":\"542ac70c-6756-4409-8224-0844a7576d9c\"}\n[2026-04-27 10:30:08] local.INFO: [ScheduleBotCommand] Number of activities to be captured: 0 {\"correlation_id\":\"24b807e0-b413-4ecc-a89b-365280be88ac\",\"trace_id\":\"542ac70c-6756-4409-8224-0844a7576d9c\"}\n[2026-04-27 10:30:08] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage for command {\"command\":\"meeting-bot:schedule-bot\",\"memoryBeforeCommandInMb\":60.0,\"memoryAfterCommandInMB\":62.0,\"memoryPeakBeforeCommandInMb\":99.727,\"memoryPeakAfterCommandInMB\":99.727} {\"correlation_id\":\"24b807e0-b413-4ecc-a89b-365280be88ac\",\"trace_id\":\"542ac70c-6756-4409-8224-0844a7576d9c\"}\n[2026-04-27 10:30:10] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage before starting command {\"command\":\"dialers:monitor-activities\",\"memoryBeforeCommandInMb\":60.0,\"memoryPeakBeforeCommandInMb\":99.727} {\"correlation_id\":\"2711ca04-b4a7-4f3a-b0b2-5826fea4da3b\",\"trace_id\":\"2613ffca-f181-4d91-ad55-ec1bdd97ee7e\"}\n[2026-04-27 10:30:10] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage for command {\"command\":\"dialers:monitor-activities\",\"memoryBeforeCommandInMb\":60.0,\"memoryAfterCommandInMB\":62.0,\"memoryPeakBeforeCommandInMb\":99.727,\"memoryPeakAfterCommandInMB\":99.727} {\"correlation_id\":\"2711ca04-b4a7-4f3a-b0b2-5826fea4da3b\",\"trace_id\":\"2613ffca-f181-4d91-ad55-ec1bdd97ee7e\"}\n[2026-04-27 10:30:17] local.NOTICE: Monitoring start {\"correlation_id\":\"e1537475-8481-4144-b9f5-596ea8034b8a\",\"trace_id\":\"f5561d0a-0cb7-4c72-8bde-cce617183529\"}\n[2026-04-27 10:30:17] local.NOTICE: Monitoring end {\"correlation_id\":\"e1537475-8481-4144-b9f5-596ea8034b8a\",\"trace_id\":\"f5561d0a-0cb7-4c72-8bde-cce617183529\"}\n[2026-04-27 10:30:21] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage before starting command {\"command\":\"mailbox:skip-lists:refresh\",\"memoryBeforeCommandInMb\":60.0,\"memoryPeakBeforeCommandInMb\":99.727} {\"correlation_id\":\"8a911b60-16a0-416e-934e-3b7e0280e452\",\"trace_id\":\"3c2bbdd6-985d-4624-b42b-fd9cc2b4756f\"}\n[2026-04-27 10:30:21] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage for command {\"command\":\"mailbox:skip-lists:refresh\",\"memoryBeforeCommandInMb\":60.0,\"memoryAfterCommandInMB\":62.0,\"memoryPeakBeforeCommandInMb\":99.727,\"memoryPeakAfterCommandInMB\":99.727} {\"correlation_id\":\"8a911b60-16a0-416e-934e-3b7e0280e452\",\"trace_id\":\"3c2bbdd6-985d-4624-b42b-fd9cc2b4756f\"}\n[2026-04-27 10:30:25] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage before starting command {\"command\":\"mailbox:batch:process\",\"memoryBeforeCommandInMb\":60.0,\"memoryPeakBeforeCommandInMb\":99.727} {\"correlation_id\":\"f62efaa6-201f-43b2-bf45-9619982dffb1\",\"trace_id\":\"f1ad3d60-af4e-4427-9d77-6aa71cfed172\"}\n[2026-04-27 10:30:25] local.INFO: [EmailSchedule] STARTING batch process {\"host\":\"docker_lamp_1\"} {\"correlation_id\":\"f62efaa6-201f-43b2-bf45-9619982dffb1\",\"trace_id\":\"f1ad3d60-af4e-4427-9d77-6aa71cfed172\"}\n[2026-04-27 10:30:25] local.INFO: [EmailSchedule] FINISHED batch process {\"host\":\"docker_lamp_1\",\"processed\":0} {\"correlation_id\":\"f62efaa6-201f-43b2-bf45-9619982dffb1\",\"trace_id\":\"f1ad3d60-af4e-4427-9d77-6aa71cfed172\"}\n[2026-04-27 10:30:25] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage for command {\"command\":\"mailbox:batch:process\",\"memoryBeforeCommandInMb\":60.0,\"memoryAfterCommandInMB\":62.0,\"memoryPeakBeforeCommandInMb\":99.727,\"memoryPeakAfterCommandInMB\":99.727} {\"correlation_id\":\"f62efaa6-201f-43b2-bf45-9619982dffb1\",\"trace_id\":\"f1ad3d60-af4e-4427-9d77-6aa71cfed172\"}\n[2026-04-27 10:30:30] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage before starting command {\"command\":\"conference:monitor:count\",\"memoryBeforeCommandInMb\":60.0,\"memoryPeakBeforeCommandInMb\":99.727} {\"correlation_id\":\"cf6dc132-ecb8-4efc-bead-06bf1c96cae6\",\"trace_id\":\"9e2b073b-940d-498a-babe-115f633dd954\"}\n[2026-04-27 10:30:30] local.INFO: Running conference:monitor:count command for activities in (2026-04-27 10:28:00, 2026-04-27 10:30:00] {\"correlation_id\":\"cf6dc132-ecb8-4efc-bead-06bf1c96cae6\",\"trace_id\":\"9e2b073b-940d-498a-babe-115f633dd954\"}\n[2026-04-27 10:30:30] local.INFO: [conference:monitor:count] No activities found in (2026-04-27 10:28:00, 2026-04-27 10:30:00] {\"correlation_id\":\"cf6dc132-ecb8-4efc-bead-06bf1c96cae6\",\"trace_id\":\"9e2b073b-940d-498a-babe-115f633dd954\"}\n[2026-04-27 10:30:30] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage for command {\"command\":\"conference:monitor:count\",\"memoryBeforeCommandInMb\":60.0,\"memoryAfterCommandInMB\":62.0,\"memoryPeakBeforeCommandInMb\":99.727,\"memoryPeakAfterCommandInMB\":99.727} {\"correlation_id\":\"cf6dc132-ecb8-4efc-bead-06bf1c96cae6\",\"trace_id\":\"9e2b073b-940d-498a-babe-115f633dd954\"}\n[2026-04-27 10:30:32] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage before starting command {\"command\":\"activity:purge-stale\",\"memoryBeforeCommandInMb\":60.0,\"memoryPeakBeforeCommandInMb\":99.727} {\"correlation_id\":\"f455260d-d1c1-456f-8937-d4bb026d9078\",\"trace_id\":\"f8c2e585-0137-4103-9e16-1b4ce05df2ef\"}\n[2026-04-27 10:30:32] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage for command {\"command\":\"activity:purge-stale\",\"memoryBeforeCommandInMb\":60.0,\"memoryAfterCommandInMB\":62.0,\"memoryPeakBeforeCommandInMb\":99.727,\"memoryPeakAfterCommandInMB\":99.727} {\"correlation_id\":\"f455260d-d1c1-456f-8937-d4bb026d9078\",\"trace_id\":\"f8c2e585-0137-4103-9e16-1b4ce05df2ef\"}\n[2026-04-27 10:30:35] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage before starting command {\"command\":\"mailbox:text-relay:sync\",\"memoryBeforeCommandInMb\":60.0,\"memoryPeakBeforeCommandInMb\":99.727} {\"correlation_id\":\"be7d3c6a-664a-42e8-a4b3-cbd021296404\",\"trace_id\":\"0b250827-4c02-4237-8b34-e5ddd004eba1\"}\n[2026-04-27 10:30:35] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage for command {\"command\":\"mailbox:text-relay:sync\",\"memoryBeforeCommandInMb\":60.0,\"memoryAfterCommandInMB\":62.0,\"memoryPeakBeforeCommandInMb\":99.727,\"memoryPeakAfterCommandInMB\":99.727} {\"correlation_id\":\"be7d3c6a-664a-42e8-a4b3-cbd021296404\",\"trace_id\":\"0b250827-4c02-4237-8b34-e5ddd004eba1\"}\n[2026-04-27 10:30:37] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage before starting command {\"command\":\"conference:pre-meeting-notification\",\"memoryBeforeCommandInMb\":60.0,\"memoryPeakBeforeCommandInMb\":99.727} {\"correlation_id\":\"5d8fbdf6-2737-4d73-babe-699c88279e1f\",\"trace_id\":\"0882a963-b02f-4049-b670-12b92f80829a\"}\n[2026-04-27 10:30:37] local.INFO: Running pre-meeting notification command {\"correlation_id\":\"5d8fbdf6-2737-4d73-babe-699c88279e1f\",\"trace_id\":\"0882a963-b02f-4049-b670-12b92f80829a\"}\n[2026-04-27 10:30:37] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage for command {\"command\":\"conference:pre-meeting-notification\",\"memoryBeforeCommandInMb\":60.0,\"memoryAfterCommandInMB\":62.0,\"memoryPeakBeforeCommandInMb\":99.727,\"memoryPeakAfterCommandInMB\":99.727} {\"correlation_id\":\"5d8fbdf6-2737-4d73-babe-699c88279e1f\",\"trace_id\":\"0882a963-b02f-4049-b670-12b92f80829a\"}\n[2026-04-27 10:30:39] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage before starting command {\"command\":\"conference:monitor:start\",\"memoryBeforeCommandInMb\":60.0,\"memoryPeakBeforeCommandInMb\":99.727} {\"correlation_id\":\"e8968e47-9acd-4bd4-9e7d-3c415f46df05\",\"trace_id\":\"287fba85-f395-41ab-813d-49cb5d0ea06d\"}\n[2026-04-27 10:30:39] local.INFO: Running conference:monitor:start command for activities in (2026-04-27 10:20:00, 2026-04-27 10:25:00] {\"correlation_id\":\"e8968e47-9acd-4bd4-9e7d-3c415f46df05\",\"trace_id\":\"287fba85-f395-41ab-813d-49cb5d0ea06d\"}\n[2026-04-27 10:30:39] local.INFO: [conference:monitor:start] No activities found in (2026-04-27 10:20:00, 2026-04-27 10:25:00] {\"correlation_id\":\"e8968e47-9acd-4bd4-9e7d-3c415f46df05\",\"trace_id\":\"287fba85-f395-41ab-813d-49cb5d0ea06d\"}\n[2026-04-27 10:30:39] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage for command {\"command\":\"conference:monitor:start\",\"memoryBeforeCommandInMb\":60.0,\"memoryAfterCommandInMB\":62.0,\"memoryPeakBeforeCommandInMb\":99.727,\"memoryPeakAfterCommandInMB\":99.727} {\"correlation_id\":\"e8968e47-9acd-4bd4-9e7d-3c415f46df05\",\"trace_id\":\"287fba85-f395-41ab-813d-49cb5d0ea06d\"}\n[2026-04-27 10:30:43] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage before starting command {\"command\":\"conference:monitor:end\",\"memoryBeforeCommandInMb\":60.0,\"memoryPeakBeforeCommandInMb\":99.727} {\"correlation_id\":\"b87eb011-7108-46bd-aa90-975ebe41f483\",\"trace_id\":\"1f54c2a6-fad1-4804-87d8-4972a378e91b\"}\n[2026-04-27 10:30:43] local.INFO: conference:monitor:end:Jiminny\\Console\\Commands\\Activities\\MonitorMeetingEndCommand::logActivitiesEnded {\"from\":\"10:25\",\"to\":\"10:30\"} {\"correlation_id\":\"b87eb011-7108-46bd-aa90-975ebe41f483\",\"trace_id\":\"1f54c2a6-fad1-4804-87d8-4972a378e91b\"}\n[2026-04-27 10:30:44] local.INFO: conference:monitor:end:Jiminny\\Console\\Commands\\Activities\\MonitorMeetingEndCommand::logActivitiesWithUnfinishedSession {\"from\":\"00:20\",\"to\":\"00:25\"} {\"correlation_id\":\"b87eb011-7108-46bd-aa90-975ebe41f483\",\"trace_id\":\"1f54c2a6-fad1-4804-87d8-4972a378e91b\"}\n[2026-04-27 10:30:44] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage for command {\"command\":\"conference:monitor:end\",\"memoryBeforeCommandInMb\":60.0,\"memoryAfterCommandInMB\":62.0,\"memoryPeakBeforeCommandInMb\":99.727,\"memoryPeakAfterCommandInMB\":99.727} {\"correlation_id\":\"b87eb011-7108-46bd-aa90-975ebe41f483\",\"trace_id\":\"1f54c2a6-fad1-4804-87d8-4972a378e91b\"}\n[2026-04-27 10:30:51] local.NOTICE: Repairing HubSpot tokens start {\"correlation_id\":\"2e6dfa3d-f07d-4eee-8232-6138133e2fbb\",\"trace_id\":\"41dea86e-0ecc-414e-8303-e60d8ae09976\"}\n[2026-04-27 10:30:51] local.INFO: Trying to refresh HubSpot token {\"account_id\":59,\"updated_at\":\"2025-10-03 09:32:05\"} {\"correlation_id\":\"2e6dfa3d-f07d-4eee-8232-6138133e2fbb\",\"trace_id\":\"41dea86e-0ecc-414e-8303-e60d8ae09976\"}\n[2026-04-27 10:30:51] local.INFO: [EncryptedTokenManager] Generating access token. {\"mode\":\"legacy\"} {\"correlation_id\":\"2e6dfa3d-f07d-4eee-8232-6138133e2fbb\",\"trace_id\":\"41dea86e-0ecc-414e-8303-e60d8ae09976\"}\n[2026-04-27 10:30:51] local.INFO: [SocialAccountService] Refreshing token from provider {\"socialAccountId\":59,\"provider\":\"hubspot\",\"refreshToken\":\"97b78f6e2cc49965c00c2492b602b02708b1392551e6b3f113fbaa48992af90b\",\"state\":\"full-refresh\"} {\"correlation_id\":\"2e6dfa3d-f07d-4eee-8232-6138133e2fbb\",\"trace_id\":\"41dea86e-0ecc-414e-8303-e60d8ae09976\"}\n[2026-04-27 10:30:52] local.ERROR: Failed to refresh HubSpot token {\"account_id\":59,\"updated_at\":\"2025-10-03 09:32:05\",\"reason\":\"missing or invalid refresh token\",\"previous\":\"\"} {\"correlation_id\":\"2e6dfa3d-f07d-4eee-8232-6138133e2fbb\",\"trace_id\":\"41dea86e-0ecc-414e-8303-e60d8ae09976\"}\n[2026-04-27 10:30:52] local.INFO: Trying to refresh HubSpot token {\"account_id\":306,\"updated_at\":\"2023-11-27 09:30:03\"} {\"correlation_id\":\"2e6dfa3d-f07d-4eee-8232-6138133e2fbb\",\"trace_id\":\"41dea86e-0ecc-414e-8303-e60d8ae09976\"}\n[2026-04-27 10:30:52] local.INFO: [EncryptedTokenManager] Generating access token. {\"mode\":\"legacy\"} {\"correlation_id\":\"2e6dfa3d-f07d-4eee-8232-6138133e2fbb\",\"trace_id\":\"41dea86e-0ecc-414e-8303-e60d8ae09976\"}\n[2026-04-27 10:30:52] local.INFO: [SocialAccountService] Refreshing token from provider {\"socialAccountId\":306,\"provider\":\"hubspot\",\"refreshToken\":\"6fa6aa8cc641d131231acc3470f5c03cb3b07b2e580fb18f8acb3b1dbb72549b\",\"state\":\"full-refresh\"} {\"correlation_id\":\"2e6dfa3d-f07d-4eee-8232-6138133e2fbb\",\"trace_id\":\"41dea86e-0ecc-414e-8303-e60d8ae09976\"}\n[2026-04-27 10:30:52] local.ERROR: Failed to refresh HubSpot token {\"account_id\":306,\"updated_at\":\"2023-11-27 09:30:03\",\"reason\":\"missing or invalid refresh token\",\"previous\":\"\"} {\"correlation_id\":\"2e6dfa3d-f07d-4eee-8232-6138133e2fbb\",\"trace_id\":\"41dea86e-0ecc-414e-8303-e60d8ae09976\"}\n[2026-04-27 10:30:52] local.INFO: Trying to refresh HubSpot token {\"account_id\":1372,\"updated_at\":\"2025-10-02 14:47:06\"} {\"correlation_id\":\"2e6dfa3d-f07d-4eee-8232-6138133e2fbb\",\"trace_id\":\"41dea86e-0ecc-414e-8303-e60d8ae09976\"}\n[2026-04-27 10:30:52] local.INFO: [EncryptedTokenManager] Generating access token. {\"mode\":\"legacy\"} {\"correlation_id\":\"2e6dfa3d-f07d-4eee-8232-6138133e2fbb\",\"trace_id\":\"41dea86e-0ecc-414e-8303-e60d8ae09976\"}\n[2026-04-27 10:30:52] local.INFO: [SocialAccountService] Refreshing token from provider {\"socialAccountId\":1372,\"provider\":\"hubspot\",\"refreshToken\":\"9aa73948c761da29dce46c177cf9aee1fde483a44169ca38723f9f0597d7a8c4\",\"state\":\"full-refresh\"} {\"correlation_id\":\"2e6dfa3d-f07d-4eee-8232-6138133e2fbb\",\"trace_id\":\"41dea86e-0ecc-414e-8303-e60d8ae09976\"}\n[2026-04-27 10:30:52] local.ERROR: Failed to refresh HubSpot token {\"account_id\":1372,\"updated_at\":\"2025-10-02 14:47:06\",\"reason\":\"missing or invalid refresh token\",\"previous\":\"\"} {\"correlation_id\":\"2e6dfa3d-f07d-4eee-8232-6138133e2fbb\",\"trace_id\":\"41dea86e-0ecc-414e-8303-e60d8ae09976\"}\n[2026-04-27 10:30:52] local.NOTICE: Repairing HubSpot tokens end {\"total\":3,\"fixed\":0,\"failed\":3} {\"correlation_id\":\"2e6dfa3d-f07d-4eee-8232-6138133e2fbb\",\"trace_id\":\"41dea86e-0ecc-414e-8303-e60d8ae09976\"}\n[2026-04-27 10:30:56] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage before starting command {\"command\":\"jiminny:transcription:retry-failed\",\"memoryBeforeCommandInMb\":60.0,\"memoryPeakBeforeCommandInMb\":99.727} {\"correlation_id\":\"a6655a64-38ad-4c65-96f1-f5a294dbb343\",\"trace_id\":\"52cdca32-c0c8-4215-81a2-4263bbde2158\"}\n[2026-04-27 10:30:56] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage before starting command {\"command\":\"conference:pre-meeting-reminder\",\"memoryBeforeCommandInMb\":60.0,\"memoryPeakBeforeCommandInMb\":99.727} {\"correlation_id\":\"ec2d49dd-ab36-4f4e-8a60-7bf5ea384cac\",\"trace_id\":\"b3e89960-9a27-4bf7-8b55-95f5704f9a10\"}\n[2026-04-27 10:30:57] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage for command {\"command\":\"jiminny:transcription:retry-failed\",\"memoryBeforeCommandInMb\":60.0,\"memoryAfterCommandInMB\":62.0,\"memoryPeakBeforeCommandInMb\":99.727,\"memoryPeakAfterCommandInMB\":99.727} {\"correlation_id\":\"a6655a64-38ad-4c65-96f1-f5a294dbb343\",\"trace_id\":\"52cdca32-c0c8-4215-81a2-4263bbde2158\"}\n[2026-04-27 10:30:57] local.INFO: [HubSpot Journal Polling] Getting offset from database {\"offset\":\"\",\"jiminny_team_id\":1} {\"correlation_id\":\"1f928e93-b17e-4518-a1b9-a8280d1d95e8\",\"trace_id\":\"ba203ed6-fe98-4fd7-aa62-8de9fb8ffeca\"}\n[2026-04-27 10:30:57] local.INFO: [HubSpot Journal Command] Starting polling service {\"correlation_id\":\"1f928e93-b17e-4518-a1b9-a8280d1d95e8\",\"trace_id\":\"ba203ed6-fe98-4fd7-aa62-8de9fb8ffeca\"}\n[2026-04-27 10:30:57] local.INFO: [HubSpot Journal Polling] Service starting {\"memory_limit\":\"256M\",\"max_execution_time\":\"0\",\"initial_memory_mb\":62.0} {\"correlation_id\":\"1f928e93-b17e-4518-a1b9-a8280d1d95e8\",\"trace_id\":\"ba203ed6-fe98-4fd7-aa62-8de9fb8ffeca\"}\n[2026-04-27 10:30:57] local.INFO: [HubSpot Journal Polling] Acquired polling lock {\"expires_at\":\"2026-04-27T10:32:57.492763Z\"} {\"correlation_id\":\"1f928e93-b17e-4518-a1b9-a8280d1d95e8\",\"trace_id\":\"ba203ed6-fe98-4fd7-aa62-8de9fb8ffeca\"}\n[2026-04-27 10:30:57] local.INFO: [HubSpot Journal Polling] Getting offset from database {\"offset\":\"\",\"jiminny_team_id\":1} {\"correlation_id\":\"1f928e93-b17e-4518-a1b9-a8280d1d95e8\",\"trace_id\":\"ba203ed6-fe98-4fd7-aa62-8de9fb8ffeca\"}\n[2026-04-27 10:30:57] local.INFO: [HubSpot Journal API] Fetching latest journal entry {\"url\":\"https://api.hubapi.com/webhooks/v4/journal/latest\"} {\"correlation_id\":\"1f928e93-b17e-4518-a1b9-a8280d1d95e8\",\"trace_id\":\"ba203ed6-fe98-4fd7-aa62-8de9fb8ffeca\"}\n[2026-04-27 10:30:57] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage for command {\"command\":\"conference:pre-meeting-reminder\",\"memoryBeforeCommandInMb\":60.0,\"memoryAfterCommandInMB\":62.0,\"memoryPeakBeforeCommandInMb\":99.727,\"memoryPeakAfterCommandInMB\":99.727} {\"correlation_id\":\"ec2d49dd-ab36-4f4e-8a60-7bf5ea384cac\",\"trace_id\":\"b3e89960-9a27-4bf7-8b55-95f5704f9a10\"}\n[2026-04-27 10:30:58] local.INFO: [HubSpot Journal Polling] No data {\"correlation_id\":\"1f928e93-b17e-4518-a1b9-a8280d1d95e8\",\"trace_id\":\"ba203ed6-fe98-4fd7-aa62-8de9fb8ffeca\"}\n[2026-04-27 10:31:02] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage before starting command {\"command\":\"crm:reset-governor\",\"memoryBeforeCommandInMb\":60.0,\"memoryPeakBeforeCommandInMb\":99.727} {\"correlation_id\":\"f261cd75-c916-4f21-b4f4-856fe0ed315c\",\"trace_id\":\"6db8819d-ffaa-4fe8-8ccd-0dcc326b8cdc\"}\n[2026-04-27 10:31:02] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage for command {\"command\":\"crm:reset-governor\",\"memoryBeforeCommandInMb\":60.0,\"memoryAfterCommandInMB\":62.0,\"memoryPeakBeforeCommandInMb\":99.727,\"memoryPeakAfterCommandInMB\":99.727} {\"correlation_id\":\"f261cd75-c916-4f21-b4f4-856fe0ed315c\",\"trace_id\":\"6db8819d-ffaa-4fe8-8ccd-0dcc326b8cdc\"}\n[2026-04-27 10:31:03] local.INFO: [HubSpot Journal Polling] Getting offset from database {\"offset\":\"\",\"jiminny_team_id\":1} {\"correlation_id\":\"1f928e93-b17e-4518-a1b9-a8280d1d95e8\",\"trace_id\":\"ba203ed6-fe98-4fd7-aa62-8de9fb8ffeca\"}\n[2026-04-27 10:31:03] local.INFO: [HubSpot Journal API] Fetching latest journal entry {\"url\":\"https://api.hubapi.com/webhooks/v4/journal/latest\"} {\"correlation_id\":\"1f928e93-b17e-4518-a1b9-a8280d1d95e8\",\"trace_id\":\"ba203ed6-fe98-4fd7-aa62-8de9fb8ffeca\"}\n[2026-04-27 10:31:03] local.INFO: [HubSpot Journal Polling] No data {\"correlation_id\":\"1f928e93-b17e-4518-a1b9-a8280d1d95e8\",\"trace_id\":\"ba203ed6-fe98-4fd7-aa62-8de9fb8ffeca\"}\n[2026-04-27 10:31:06] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage before starting command {\"command\":\"activity:sync\",\"memoryBeforeCommandInMb\":60.0,\"memoryPeakBeforeCommandInMb\":99.727} {\"correlation_id\":\"f9151ed9-60aa-4b2b-b962-9eab5f75405b\",\"trace_id\":\"dba432b8-cb4b-4e76-8684-01d96f91e453\"}\n[2026-04-27 10:31:07] local.INFO: Dispatching activity sync job {\"import_id\":811739,\"provider\":\"twilio-flex\",\"team\":\"jiminny\"} {\"correlation_id\":\"f9151ed9-60aa-4b2b-b962-9eab5f75405b\",\"trace_id\":\"dba432b8-cb4b-4e76-8684-01d96f91e453\"}\n[2026-04-27 10:31:07] local.INFO: Dispatching activity sync job {\"import_id\":811740,\"provider\":\"xant\",\"team\":\"jiminny\"} {\"correlation_id\":\"f9151ed9-60aa-4b2b-b962-9eab5f75405b\",\"trace_id\":\"dba432b8-cb4b-4e76-8684-01d96f91e453\"}\n[2026-04-27 10:31:07] local.INFO: Dispatching activity sync job {\"import_id\":811741,\"provider\":\"apollo\",\"team\":\"jiminny\"} {\"correlation_id\":\"f9151ed9-60aa-4b2b-b962-9eab5f75405b\",\"trace_id\":\"dba432b8-cb4b-4e76-8684-01d96f91e453\"}\n[2026-04-27 10:31:07] local.INFO: Dispatching activity sync job {\"import_id\":811742,\"provider\":\"groove\",\"team\":\"jiminny\"} {\"correlation_id\":\"f9151ed9-60aa-4b2b-b962-9eab5f75405b\",\"trace_id\":\"dba432b8-cb4b-4e76-8684-01d96f91e453\"}\n[2026-04-27 10:31:07] local.INFO: Dispatching activity sync job {\"import_id\":811743,\"provider\":\"twilio-video\",\"team\":\"jiminny\"} {\"correlation_id\":\"f9151ed9-60aa-4b2b-b962-9eab5f75405b\",\"trace_id\":\"dba432b8-cb4b-4e76-8684-01d96f91e453\"}\n[2026-04-27 10:31:07] local.INFO: Dispatching activity sync job {\"import_id\":811744,\"provider\":\"hubspot\",\"team\":\"hubspot\"} {\"correlation_id\":\"f9151ed9-60aa-4b2b-b962-9eab5f75405b\",\"trace_id\":\"dba432b8-cb4b-4e76-8684-01d96f91e453\"}\n[2026-04-27 10:31:07] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage for command {\"command\":\"activity:sync\",\"memoryBeforeCommandInMb\":60.0,\"memoryAfterCommandInMB\":62.0,\"memoryPeakBeforeCommandInMb\":99.727,\"memoryPeakAfterCommandInMB\":99.727} {\"correlation_id\":\"f9151ed9-60aa-4b2b-b962-9eab5f75405b\",\"trace_id\":\"dba432b8-cb4b-4e76-8684-01d96f91e453\"}\n[2026-04-27 10:31:07] local.WARNING: [Salesforce] Account not connected for user {\"userId\":\"cdf8b554-d951-4758-bc2b-c1b85d1cd0b9\",\"account\":null} {\"correlation_id\":\"e7a521c5-fad4-48c2-b880-d572b5d237f4\",\"trace_id\":\"dba432b8-cb4b-4e76-8684-01d96f91e453\"}\n[2026-04-27 10:31:07] local.INFO: [CrmOwnerResolver] Integration owner is not connected, attempting team members {\"crm_provider\":\"salesforce\",\"crm_owner\":3,\"team_id\":1} {\"correlation_id\":\"e7a521c5-fad4-48c2-b880-d572b5d237f4\",\"trace_id\":\"dba432b8-cb4b-4e76-8684-01d96f91e453\"}\n[2026-04-27 10:31:07] local.INFO: [SocialAccountService] Fetching token {\"socialAccountId\":1500,\"provider\":\"salesforce\"} {\"correlation_id\":\"e7a521c5-fad4-48c2-b880-d572b5d237f4\",\"trace_id\":\"dba432b8-cb4b-4e76-8684-01d96f91e453\"}\n[2026-04-27 10:31:07] local.INFO: [SocialAccountService] Token retrieved {\"socialAccountId\":1500,\"provider\":\"salesforce\"} {\"correlation_id\":\"e7a521c5-fad4-48c2-b880-d572b5d237f4\",\"trace_id\":\"dba432b8-cb4b-4e76-8684-01d96f91e453\"}\n[2026-04-27 10:31:07] local.INFO: [EncryptedTokenManager] Generating access token. {\"mode\":\"legacy\"} {\"correlation_id\":\"e7a521c5-fad4-48c2-b880-d572b5d237f4\",\"trace_id\":\"dba432b8-cb4b-4e76-8684-01d96f91e453\"}\n[2026-04-27 10:31:07] local.INFO: [CrmOwnerResolver] TeamMember found with active crm connection {\"crm_provider\":\"salesforce\",\"crm_owner\":143,\"team_id\":1} {\"correlation_id\":\"e7a521c5-fad4-48c2-b880-d572b5d237f4\",\"trace_id\":\"dba432b8-cb4b-4e76-8684-01d96f91e453\"}\n[2026-04-27 10:31:07] local.INFO: [SocialAccountService] Fetching token {\"socialAccountId\":1194,\"provider\":\"twilio-flex\"} {\"correlation_id\":\"e7a521c5-fad4-48c2-b880-d572b5d237f4\",\"trace_id\":\"dba432b8-cb4b-4e76-8684-01d96f91e453\"}\n[2026-04-27 10:31:07] local.INFO: [SocialAccountService] Token retrieved {\"socialAccountId\":1194,\"provider\":\"twilio-flex\"} {\"correlation_id\":\"e7a521c5-fad4-48c2-b880-d572b5d237f4\",\"trace_id\":\"dba432b8-cb4b-4e76-8684-01d96f91e453\"}\n[2026-04-27 10:31:07] local.INFO: [EncryptedTokenManager] Generating access token. {\"mode\":\"legacy\"} {\"correlation_id\":\"e7a521c5-fad4-48c2-b880-d572b5d237f4\",\"trace_id\":\"dba432b8-cb4b-4e76-8684-01d96f91e453\"}\n[2026-04-27 10:31:07] local.INFO: [SyncActivity] Start {\"import_id\":811739,\"provider\":\"twilio-flex\",\"provider_id\":317,\"team\":\"jiminny\",\"team_id\":1} {\"correlation_id\":\"e7a521c5-fad4-48c2-b880-d572b5d237f4\",\"trace_id\":\"dba432b8-cb4b-4e76-8684-01d96f91e453\"}\n[2026-04-27 10:31:07] local.NOTICE: [TwilioFlex] Calls import start {\"correlation_id\":\"e7a521c5-fad4-48c2-b880-d572b5d237f4\",\"trace_id\":\"dba432b8-cb4b-4e76-8684-01d96f91e453\"}\n[2026-04-27 10:31:08] local.ALERT: [SyncActivity] Failed {\"import_id\":811739,\"provider\":\"twilio-flex\",\"provider_id\":317,\"team\":\"jiminny\",\"team_id\":1,\"reason\":\"[HTTP 401] Unable to fetch page: Authenticate\",\"file\":\"/home/jiminny/vendor/twilio/sdk/src/Twilio/Page.php\",\"line\":60} {\"correlation_id\":\"e7a521c5-fad4-48c2-b880-d572b5d237f4\",\"trace_id\":\"dba432b8-cb4b-4e76-8684-01d96f91e453\"}\n[2026-04-27 10:31:08] local.INFO: [HubSpot Journal Polling] Getting offset from database {\"offset\":\"\",\"jiminny_team_id\":1} {\"correlation_id\":\"1f928e93-b17e-4518-a1b9-a8280d1d95e8\",\"trace_id\":\"ba203ed6-fe98-4fd7-aa62-8de9fb8ffeca\"}\n[2026-04-27 10:31:08] local.INFO: [HubSpot Journal API] Fetching latest journal entry {\"url\":\"https://api.hubapi.com/webhooks/v4/journal/latest\"} {\"correlation_id\":\"1f928e93-b17e-4518-a1b9-a8280d1d95e8\",\"trace_id\":\"ba203ed6-fe98-4fd7-aa62-8de9fb8ffeca\"}\n[2026-04-27 10:31:09] local.INFO: [SocialAccountService] Fetching token {\"socialAccountId\":1500,\"provider\":\"salesforce\"} {\"correlation_id\":\"73848601-79bb-4161-91ae-37acf4385d24\",\"trace_id\":\"dba432b8-cb4b-4e76-8684-01d96f91e453\"}\n[2026-04-27 10:31:09] local.INFO: [SocialAccountService] Token retrieved {\"socialAccountId\":1500,\"provider\":\"salesforce\"} {\"correlation_id\":\"73848601-79bb-4161-91ae-37acf4385d24\",\"trace_id\":\"dba432b8-cb4b-4e76-8684-01d96f91e453\"}\n[2026-04-27 10:31:09] local.INFO: [EncryptedTokenManager] Generating access token. {\"mode\":\"legacy\"} {\"correlation_id\":\"73848601-79bb-4161-91ae-37acf4385d24\",\"trace_id\":\"dba432b8-cb4b-4e76-8684-01d96f91e453\"}\n[2026-04-27 10:31:09] local.INFO: [CrmOwnerResolver] Integration owner matched as CRM Owner {\"crm_provider\":\"salesforce\",\"crm_owner\":143,\"team_id\":1} {\"correlation_id\":\"73848601-79bb-4161-91ae-37acf4385d24\",\"trace_id\":\"dba432b8-cb4b-4e76-8684-01d96f91e453\"}\n[2026-04-27 10:31:09] local.INFO: [SocialAccountService] Fetching token {\"socialAccountId\":1500,\"provider\":\"salesforce\"} {\"correlation_id\":\"73848601-79bb-4161-91ae-37acf4385d24\",\"trace_id\":\"dba432b8-cb4b-4e76-8684-01d96f91e453\"}\n[2026-04-27 10:31:09] local.INFO: [HubSpot Journal Polling] No data {\"correlation_id\":\"1f928e93-b17e-4518-a1b9-a8280d1d95e8\",\"trace_id\":\"ba203ed6-fe98-4fd7-aa62-8de9fb8ffeca\"}\n[2026-04-27 10:31:09] local.INFO: [SocialAccountService] Token retrieved {\"socialAccountId\":1500,\"provider\":\"salesforce\"} {\"correlation_id\":\"73848601-79bb-4161-91ae-37acf4385d24\",\"trace_id\":\"dba432b8-cb4b-4e76-8684-01d96f91e453\"}\n[2026-04-27 10:31:09] local.INFO: [EncryptedTokenManager] Generating access token. {\"mode\":\"legacy\"} {\"correlation_id\":\"73848601-79bb-4161-91ae-37acf4385d24\",\"trace_id\":\"dba432b8-cb4b-4e76-8684-01d96f91e453\"}\n[2026-04-27 10:31:09] local.INFO: [SyncActivity] Start {\"import_id\":811740,\"provider\":\"xant\",\"provider_id\":161,\"team\":\"jiminny\",\"team_id\":1} {\"correlation_id\":\"73848601-79bb-4161-91ae-37acf4385d24\",\"trace_id\":\"dba432b8-cb4b-4e76-8684-01d96f91e453\"}\n[2026-04-27 10:31:09] local.INFO: [Salesforce] Performing query {\"query\":\"\n SELECT Playbooks_Call_Date__c,Playbooks_Call_Recording__c,CreatedDate,TaskSubtype,CallType,CallDurationInSeconds,Id,OwnerId,WhoId,WhatId,Priority,ActivityDate,Subject,Description,Status,Type\n FROM Task\n WHERE IsDeleted = false\n AND LastModifiedDate >= :from\n AND LastModifiedDate <= :to\n ORDER BY LastModifiedDate ASC\n LIMIT :limit\",\"params\":{\"from\":\"2026-04-27T10:14:00Z\",\"to\":\"2026-04-27T10:30:00Z\",\"ownerId\":null,\"subType\":null,\"limit\":5000}} {\"correlation_id\":\"73848601-79bb-4161-91ae-37acf4385d24\",\"trace_id\":\"dba432b8-cb4b-4e76-8684-01d96f91e453\"}\n[2026-04-27 10:31:09] local.INFO: [Salesforce] Sending request {\"endpoint\":\"https://jiminny--stagingenv.sandbox.my.salesforce.com/services/data/v50.0/query/?q=%0A++++++++++++SELECT+Playbooks_Call_Date__c%2CPlaybooks_Call_Recording__c%2CCreatedDate%2CTaskSubtype%2CCallType%2CCallDurationInSeconds%2CId%2COwnerId%2CWhoId%2CWhatId%2CPriority%2CActivityDate%2CSubject%2CDescription%2CStatus%2CType%0A++++++++++++++FROM+Task%0A+++++++++++++WHERE+IsDeleted+%3D+false%0A+++++++++++++++AND+LastModifiedDate+%3E%3D+2026-04-27T10%3A14%3A00Z%0A+++++++++++++++AND+LastModifiedDate+%3C%3D+2026-04-27T10%3A30%3A00Z%0A++++++++++ORDER+BY+LastModifiedDate+ASC%0A+++++++++++++LIMIT+5000 GET\",\"team_id\":1} {\"correlation_id\":\"73848601-79bb-4161-91ae-37acf4385d24\",\"trace_id\":\"dba432b8-cb4b-4e76-8684-01d96f91e453\"}\n[2026-04-27 10:31:09] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage before starting command {\"command\":\"mailbox:batch:fail-stalled\",\"memoryBeforeCommandInMb\":60.0,\"memoryPeakBeforeCommandInMb\":99.727} {\"correlation_id\":\"0d30446d-c469-4ade-80dc-f57f997ea610\",\"trace_id\":\"61eea326-3f40-4a50-a5e6-bddd7fa25d27\"}\n[2026-04-27 10:31:09] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage for command {\"command\":\"mailbox:batch:fail-stalled\",\"memoryBeforeCommandInMb\":60.0,\"memoryAfterCommandInMB\":62.0,\"memoryPeakBeforeCommandInMb\":99.727,\"memoryPeakAfterCommandInMB\":99.727} {\"correlation_id\":\"0d30446d-c469-4ade-80dc-f57f997ea610\",\"trace_id\":\"61eea326-3f40-4a50-a5e6-bddd7fa25d27\"}\n[2026-04-27 10:31:09] local.INFO: [Xant (InsideSales)] No calls found. {\"correlation_id\":\"73848601-79bb-4161-91ae-37acf4385d24\",\"trace_id\":\"dba432b8-cb4b-4e76-8684-01d96f91e453\"}\n[2026-04-27 10:31:09] local.INFO: [SyncActivity] End {\"import_id\":811740,\"provider\":\"xant\",\"provider_id\":161,\"team\":\"jiminny\",\"team_id\":1} {\"correlation_id\":\"73848601-79bb-4161-91ae-37acf4385d24\",\"trace_id\":\"dba432b8-cb4b-4e76-8684-01d96f91e453\"}\n[2026-04-27 10:31:09] local.INFO: [SyncActivity] Memory usage {\"import_id\":811740,\"provider\":\"xant\",\"provider_id\":161,\"team\":\"jiminny\",\"team_id\":1,\"memory_usage\":25149472,\"memory_real_usage\":65011712,\"pid\":13616} {\"correlation_id\":\"73848601-79bb-4161-91ae-37acf4385d24\",\"trace_id\":\"dba432b8-cb4b-4e76-8684-01d96f91e453\"}\n[2026-04-27 10:31:09] local.INFO: [SocialAccountService] Fetching token {\"socialAccountId\":1500,\"provider\":\"salesforce\"} {\"correlation_id\":\"11c977fb-9603-4dde-a4d7-f821d47fcdc9\",\"trace_id\":\"dba432b8-cb4b-4e76-8684-01d96f91e453\"}\n[2026-04-27 10:31:09] local.INFO: [SocialAccountService] Token retrieved {\"socialAccountId\":1500,\"provider\":\"salesforce\"} {\"correlation_id\":\"11c977fb-9603-4dde-a4d7-f821d47fcdc9\",\"trace_id\":\"dba432b8-cb4b-4e76-8684-01d96f91e453\"}\n[2026-04-27 10:31:09] local.INFO: [EncryptedTokenManager] Generating access token. {\"mode\":\"legacy\"} {\"correlation_id\":\"11c977fb-9603-4dde-a4d7-f821d47fcdc9\",\"trace_id\":\"dba432b8-cb4b-4e76-8684-01d96f91e453\"}\n[2026-04-27 10:31:09] local.INFO: [CrmOwnerResolver] Integration owner matched as CRM Owner {\"crm_provider\":\"salesforce\",\"crm_owner\":143,\"team_id\":1} {\"correlation_id\":\"11c977fb-9603-4dde-a4d7-f821d47fcdc9\",\"trace_id\":\"dba432b8-cb4b-4e76-8684-01d96f91e453\"}\n[2026-04-27 10:31:09] local.INFO: [SocialAccountService] Fetching token {\"socialAccountId\":1500,\"provider\":\"salesforce\"} {\"correlation_id\":\"11c977fb-9603-4dde-a4d7-f821d47fcdc9\",\"trace_id\":\"dba432b8-cb4b-4e76-8684-01d96f91e453\"}\n[2026-04-27 10:31:09] local.INFO: [SocialAccountService] Token retrieved {\"socialAccountId\":1500,\"provider\":\"salesforce\"} {\"correlation_id\":\"11c977fb-9603-4dde-a4d7-f821d47fcdc9\",\"trace_id\":\"dba432b8-cb4b-4e76-8684-01d96f91e453\"}\n[2026-04-27 10:31:09] local.INFO: [EncryptedTokenManager] Generating access token. {\"mode\":\"legacy\"} {\"correlation_id\":\"11c977fb-9603-4dde-a4d7-f821d47fcdc9\",\"trace_id\":\"dba432b8-cb4b-4e76-8684-01d96f91e453\"}\n[2026-04-27 10:31:09] local.INFO: [SyncActivity] Start {\"import_id\":811741,\"provider\":\"apollo\",\"provider_id\":441,\"team\":\"jiminny\",\"team_id\":1} {\"correlation_id\":\"11c977fb-9603-4dde-a4d7-f821d47fcdc9\",\"trace_id\":\"dba432b8-cb4b-4e76-8684-01d96f91e453\"}\n[2026-04-27 10:31:09] local.INFO: [Salesforce] Performing query {\"query\":\"\n SELECT AccountId,CreatedDate,TaskSubtype,CallType,Id,OwnerId,WhoId,WhatId,Priority,ActivityDate,Subject,Description,Status,Type\n FROM Task\n WHERE IsDeleted = false\n AND LastModifiedDate >= :from\n AND LastModifiedDate <= :to\n ORDER BY LastModifiedDate ASC\n LIMIT :limit\",\"params\":{\"from\":\"2026-04-27T10:14:00Z\",\"to\":\"2026-04-27T10:30:00Z\",\"ownerId\":null,\"subType\":null,\"limit\":5000}} {\"correlation_id\":\"11c977fb-9603-4dde-a4d7-f821d47fcdc9\",\"trace_id\":\"dba432b8-cb4b-4e76-8684-01d96f91e453\"}\n[2026-04-27 10:31:09] local.INFO: [Salesforce] Sending request {\"endpoint\":\"https://jiminny--stagingenv.sandbox.my.salesforce.com/services/data/v50.0/query/?q=%0A++++++++++++SELECT+AccountId%2CCreatedDate%2CTaskSubtype%2CCallType%2CId%2COwnerId%2CWhoId%2CWhatId%2CPriority%2CActivityDate%2CSubject%2CDescription%2CStatus%2CType%0A++++++++++++++FROM+Task%0A+++++++++++++WHERE+IsDeleted+%3D+false%0A+++++++++++++++AND+LastModifiedDate+%3E%3D+2026-04-27T10%3A14%3A00Z%0A+++++++++++++++AND+LastModifiedDate+%3C%3D+2026-04-27T10%3A30%3A00Z%0A++++++++++ORDER+BY+LastModifiedDate+ASC%0A+++++++++++++LIMIT+5000 GET\",\"team_id\":1} {\"correlation_id\":\"11c977fb-9603-4dde-a4d7-f821d47fcdc9\",\"trace_id\":\"dba432b8-cb4b-4e76-8684-01d96f91e453\"}\n[2026-04-27 10:31:10] local.INFO: [Apollo] No calls found. {\"correlation_id\":\"11c977fb-9603-4dde-a4d7-f821d47fcdc9\",\"trace_id\":\"dba432b8-cb4b-4e76-8684-01d96f91e453\"}\n[2026-04-27 10:31:10] local.INFO: [SyncActivity] End {\"import_id\":811741,\"provider\":\"apollo\",\"provider_id\":441,\"team\":\"jiminny\",\"team_id\":1} {\"correlation_id\":\"11c977fb-9603-4dde-a4d7-f821d47fcdc9\",\"trace_id\":\"dba432b8-cb4b-4e76-8684-01d96f91e453\"}\n[2026-04-27 10:31:10] local.INFO: [SyncActivity] Memory usage {\"import_id\":811741,\"provider\":\"apollo\",\"provider_id\":441,\"team\":\"jiminny\",\"team_id\":1,\"memory_usage\":25527752,\"memory_real_usage\":65011712,\"pid\":13616} {\"correlation_id\":\"11c977fb-9603-4dde-a4d7-f821d47fcdc9\",\"trace_id\":\"dba432b8-cb4b-4e76-8684-01d96f91e453\"}\n[2026-04-27 10:31:10] local.INFO: [SocialAccountService] Fetching token {\"socialAccountId\":1500,\"provider\":\"salesforce\"} {\"correlation_id\":\"856f8092-8c5e-409d-8aa6-ffb8efd8bfa1\",\"trace_id\":\"dba432b8-cb4b-4e76-8684-01d96f91e453\"}\n[2026-04-27 10:31:10] local.INFO: [SocialAccountService] Token retrieved {\"socialAccountId\":1500,\"provider\":\"salesforce\"} {\"correlation_id\":\"856f8092-8c5e-409d-8aa6-ffb8efd8bfa1\",\"trace_id\":\"dba432b8-cb4b-4e76-8684-01d96f91e453\"}\n[2026-04-27 10:31:10] local.INFO: [EncryptedTokenManager] Generating access token. {\"mode\":\"legacy\"} {\"correlation_id\":\"856f8092-8c5e-409d-8aa6-ffb8efd8bfa1\",\"trace_id\":\"dba432b8-cb4b-4e76-8684-01d96f91e453\"}\n[2026-04-27 10:31:10] local.INFO: [CrmOwnerResolver] Integration owner matched as CRM Owner {\"crm_provider\":\"salesforce\",\"crm_owner\":143,\"team_id\":1} {\"correlation_id\":\"856f8092-8c5e-409d-8aa6-ffb8efd8bfa1\",\"trace_id\":\"dba432b8-cb4b-4e76-8684-01d96f91e453\"}\n[2026-04-27 10:31:10] local.INFO: [SocialAccountService] Fetching token {\"socialAccountId\":1500,\"provider\":\"salesforce\"} {\"correlation_id\":\"856f8092-8c5e-409d-8aa6-ffb8efd8bfa1\",\"trace_id\":\"dba432b8-cb4b-4e76-8684-01d96f91e453\"}\n[2026-04-27 10:31:10] local.INFO: [SocialAccountService] Token retrieved {\"socialAccountId\":1500,\"provider\":\"salesforce\"} {\"correlation_id\":\"856f8092-8c5e-409d-8aa6-ffb8efd8bfa1\",\"trace_id\":\"dba432b8-cb4b-4e76-8684-01d96f91e453\"}\n[2026-04-27 10:31:10] local.INFO: [EncryptedTokenManager] Generating access token. {\"mode\":\"legacy\"} {\"correlation_id\":\"856f8092-8c5e-409d-8aa6-ffb8efd8bfa1\",\"trace_id\":\"dba432b8-cb4b-4e76-8684-01d96f91e453\"}\n[2026-04-27 10:31:10] local.INFO: [SyncActivity] Start {\"import_id\":811742,\"provider\":\"groove\",\"provider_id\":228,\"team\":\"jiminny\",\"team_id\":1} {\"correlation_id\":\"856f8092-8c5e-409d-8aa6-ffb8efd8bfa1\",\"trace_id\":\"dba432b8-cb4b-4e76-8684-01d96f91e453\"}\n[2026-04-27 10:31:10] local.INFO: [Salesforce] Performing query {\"query\":\"\n SELECT call_recording_url__c,TaskSubtype,CreatedDate,CallType,CallDurationInSeconds,Id,OwnerId,WhoId,WhatId,Priority,ActivityDate,Subject,Description,Status,Type\n FROM Task\n WHERE IsDeleted = false\n AND LastModifiedDate >= :from\n AND LastModifiedDate <= :to\n ORDER BY LastModifiedDate ASC\n LIMIT :limit\",\"params\":{\"from\":\"2026-04-27T10:14:00Z\",\"to\":\"2026-04-27T10:30:00Z\",\"ownerId\":null,\"subType\":null,\"limit\":5000}} {\"correlation_id\":\"856f8092-8c5e-409d-8aa6-ffb8efd8bfa1\",\"trace_id\":\"dba432b8-cb4b-4e76-8684-01d96f91e453\"}\n[2026-04-27 10:31:10] local.INFO: [Salesforce] Sending request {\"endpoint\":\"https://jiminny--stagingenv.sandbox.my.salesforce.com/services/data/v50.0/query/?q=%0A++++++++++++SELECT+call_recording_url__c%2CTaskSubtype%2CCreatedDate%2CCallType%2CCallDurationInSeconds%2CId%2COwnerId%2CWhoId%2CWhatId%2CPriority%2CActivityDate%2CSubject%2CDescription%2CStatus%2CType%0A++++++++++++++FROM+Task%0A+++++++++++++WHERE+IsDeleted+%3D+false%0A+++++++++++++++AND+LastModifiedDate+%3E%3D+2026-04-27T10%3A14%3A00Z%0A+++++++++++++++AND+LastModifiedDate+%3C%3D+2026-04-27T10%3A30%3A00Z%0A++++++++++ORDER+BY+LastModifiedDate+ASC%0A+++++++++++++LIMIT+5000 GET\",\"team_id\":1} {\"correlation_id\":\"856f8092-8c5e-409d-8aa6-ffb8efd8bfa1\",\"trace_id\":\"dba432b8-cb4b-4e76-8684-01d96f91e453\"}\n[2026-04-27 10:31:10] local.ERROR: [Salesforce] Request exception [400] \nSELECT call_recording_url__c,TaskSubtype\n ^\nERROR at Row:1:Column:8\nNo such column 'call_recording_url__c' on entity 'Task'. If you are attempting to use a custom field, be sure to append the '__c' after the custom field name. Please reference your WSDL or the describe call for the appropriate names. {\"url\":\"https://jiminny--stagingenv.sandbox.my.salesforce.com/services/data/v50.0/query/?q=%0A++++++++++++SELECT+call_recording_url__c%2CTaskSubtype%2CCreatedDate%2CCallType%2CCallDurationInSeconds%2CId%2COwnerId%2CWhoId%2CWhatId%2CPriority%2CActivityDate%2CSubject%2CDescription%2CStatus%2CType%0A++++++++++++++FROM+Task%0A+++++++++++++WHERE+IsDeleted+%3D+false%0A+++++++++++++++AND+LastModifiedDate+%3E%3D+2026-04-27T10%3A14%3A00Z%0A+++++++++++++++AND+LastModifiedDate+%3C%3D+2026-04-27T10%3A30%3A00Z%0A++++++++++ORDER+BY+LastModifiedDate+ASC%0A+++++++++++++LIMIT+5000\",\"data\":{\"headers\":{\"Authorization\":\"Bearer 00D2g0000008hH4!AQEAQC41CLa_xP1bAbi9E2OUbAOpW..Wx961OtqTwJ3oWpOKs6yPdm47B4yKRBHbRmmJ.Lvd34sjr0gnAkV8fLb8IZWzIW93\"}},\"response\":{\"GuzzleHttp\\\\Psr7\\\\Stream\":\"[{\\\"message\\\":\\\"\\\\nSELECT call_recording_url__c,TaskSubtype\\\\n ^\\\\nERROR at Row:1:Column:8\\\\nNo such column 'call_recording_url__c' on entity 'Task'. If you are attempting to use a custom field, be sure to append the '__c' after the custom field name. Please reference your WSDL or the describe call for the appropriate names.\\\",\\\"errorCode\\\":\\\"INVALID_FIELD\\\"}]\"},\"fields\":[]} {\"correlation_id\":\"856f8092-8c5e-409d-8aa6-ffb8efd8bfa1\",\"trace_id\":\"dba432b8-cb4b-4e76-8684-01d96f91e453\"}\n[2026-04-27 10:31:10] local.ALERT: [SyncActivity] Failed {\"import_id\":811742,\"provider\":\"groove\",\"provider_id\":228,\"team\":\"jiminny\",\"team_id\":1,\"reason\":\"\nSELECT call_recording_url__c,TaskSubtype\n ^\nERROR at Row:1:Column:8\nNo such column 'call_recording_url__c' on entity 'Task'. If you are attempting to use a custom field, be sure to append the '__c' after the custom field name. Please reference your WSDL or the describe call for the appropriate names.\",\"file\":\"/home/jiminny/app/Services/Crm/Salesforce/Client.php\",\"line\":564} {\"correlation_id\":\"856f8092-8c5e-409d-8aa6-ffb8efd8bfa1\",\"trace_id\":\"dba432b8-cb4b-4e76-8684-01d96f91e453\"}\n[2026-04-27 10:31:10] local.INFO: [SocialAccountService] Fetching token {\"socialAccountId\":1500,\"provider\":\"salesforce\"} {\"correlation_id\":\"0ec6aef6-345c-4602-b6a1-646b95e481c0\",\"trace_id\":\"dba432b8-cb4b-4e76-8684-01d96f91e453\"}\n[2026-04-27 10:31:10] local.INFO: [SocialAccountService] Token retrieved {\"socialAccountId\":1500,\"provider\":\"salesforce\"} {\"correlation_id\":\"0ec6aef6-345c-4602-b6a1-646b95e481c0\",\"trace_id\":\"dba432b8-cb4b-4e76-8684-01d96f91e453\"}\n[2026-04-27 10:31:10] local.INFO: [EncryptedTokenManager] Generating access token. {\"mode\":\"legacy\"} {\"correlation_id\":\"0ec6aef6-345c-4602-b6a1-646b95e481c0\",\"trace_id\":\"dba432b8-cb4b-4e76-8684-01d96f91e453\"}\n[2026-04-27 10:31:10] local.INFO: [CrmOwnerResolver] Integration owner matched as CRM Owner {\"crm_provider\":\"salesforce\",\"crm_owner\":143,\"team_id\":1} {\"correlation_id\":\"0ec6aef6-345c-4602-b6a1-646b95e481c0\",\"trace_id\":\"dba432b8-cb4b-4e76-8684-01d96f91e453\"}\n[2026-04-27 10:31:10] local.INFO: [SocialAccountService] Fetching token {\"socialAccountId\":1500,\"provider\":\"salesforce\"} {\"correlation_id\":\"0ec6aef6-345c-4602-b6a1-646b95e481c0\",\"trace_id\":\"dba432b8-cb4b-4e76-8684-01d96f91e453\"}\n[2026-04-27 10:31:10] local.INFO: [SocialAccountService] Token retrieved {\"socialAccountId\":1500,\"provider\":\"salesforce\"} {\"correlation_id\":\"0ec6aef6-345c-4602-b6a1-646b95e481c0\",\"trace_id\":\"dba432b8-cb4b-4e76-8684-01d96f91e453\"}\n[2026-04-27 10:31:10] local.INFO: [EncryptedTokenManager] Generating access token. {\"mode\":\"legacy\"} {\"correlation_id\":\"0ec6aef6-345c-4602-b6a1-646b95e481c0\",\"trace_id\":\"dba432b8-cb4b-4e76-8684-01d96f91e453\"}\n[2026-04-27 10:31:10] local.INFO: [SyncActivity] Start {\"import_id\":811743,\"provider\":\"twilio-video\",\"provider_id\":243,\"team\":\"jiminny\",\"team_id\":1} {\"correlation_id\":\"0ec6aef6-345c-4602-b6a1-646b95e481c0\",\"trace_id\":\"dba432b8-cb4b-4e76-8684-01d96f91e453\"}\n[2026-04-27 10:31:10] local.INFO: [Salesforce] Performing query {\"query\":\"SELECT Id,OwnerId,WhoId,WhatId,Priority,ActivityDate,Subject,Description,Status,Type,twilio_call_sid__c,Lead_UUID__c,Opportunity__c\n FROM Task\n WHERE Type = 'Video'\n AND isClosed = true\n AND IsDeleted = false\n AND LastModifiedDate >= :from\n AND twilio_call_sid__c != NULL AND LastModifiedDate <= :to ORDER BY LastModifiedDate ASC\n LIMIT :limit\",\"params\":{\"from\":\"2026-04-27T10:14:00Z\",\"to\":\"2026-04-27T10:30:00Z\",\"ownerId\":null,\"subType\":null,\"limit\":5000}} {\"correlation_id\":\"0ec6aef6-345c-4602-b6a1-646b95e481c0\",\"trace_id\":\"dba432b8-cb4b-4e76-8684-01d96f91e453\"}\n[2026-04-27 10:31:10] local.INFO: [Salesforce] Sending request {\"endpoint\":\"https://jiminny--stagingenv.sandbox.my.salesforce.com/services/data/v50.0/query/?q=SELECT+Id%2COwnerId%2CWhoId%2CWhatId%2CPriority%2CActivityDate%2CSubject%2CDescription%2CStatus%2CType%2Ctwilio_call_sid__c%2CLead_UUID__c%2COpportunity__c%0A++++++++++++++FROM+Task%0A++++++++++++WHERE+Type+%3D+%27Video%27%0A++++++++++++++AND+isClosed+%3D+true%0A++++++++++++++AND+IsDeleted+%3D+false%0A++++++++++++++AND+LastModifiedDate+%3E%3D+2026-04-27T10%3A14%3A00Z%0A++++++++++++++AND+twilio_call_sid__c+%21%3D+NULL+AND+LastModifiedDate+%3C%3D+2026-04-27T10%3A30%3A00Z+ORDER+BY+LastModifiedDate+ASC%0A+++++++++++++LIMIT+5000 GET\",\"team_id\":1} {\"correlation_id\":\"0ec6aef6-345c-4602-b6a1-646b95e481c0\",\"trace_id\":\"dba432b8-cb4b-4e76-8684-01d96f91e453\"}\n[2026-04-27 10:31:11] local.INFO: [Twilio Video] No calls found. {\"correlation_id\":\"0ec6aef6-345c-4602-b6a1-646b95e481c0\",\"trace_id\":\"dba432b8-cb4b-4e76-8684-01d96f91e453\"}\n[2026-04-27 10:31:11] local.INFO: [SyncActivity] End {\"import_id\":811743,\"provider\":\"twilio-video\",\"provider_id\":243,\"team\":\"jiminny\",\"team_id\":1} {\"correlation_id\":\"0ec6aef6-345c-4602-b6a1-646b95e481c0\",\"trace_id\":\"dba432b8-cb4b-4e76-8684-01d96f91e453\"}\n[2026-04-27 10:31:11] local.INFO: [SyncActivity] Memory usage {\"import_id\":811743,\"provider\":\"twilio-video\",\"provider_id\":243,\"team\":\"jiminny\",\"team_id\":1,\"memory_usage\":25679680,\"memory_real_usage\":65011712,\"pid\":13616} {\"correlation_id\":\"0ec6aef6-345c-4602-b6a1-646b95e481c0\",\"trace_id\":\"dba432b8-cb4b-4e76-8684-01d96f91e453\"}\n[2026-04-27 10:31:11] local.INFO: [SocialAccountService] Fetching token {\"socialAccountId\":1499,\"provider\":\"hubspot\"} {\"correlation_id\":\"3b9d7e85-b8c2-4ae2-863e-c343c277afdc\",\"trace_id\":\"dba432b8-cb4b-4e76-8684-01d96f91e453\"}\n[2026-04-27 10:31:11] local.INFO: [SocialAccountService] Token retrieved {\"socialAccountId\":1499,\"provider\":\"hubspot\"} {\"correlation_id\":\"3b9d7e85-b8c2-4ae2-863e-c343c277afdc\",\"trace_id\":\"dba432b8-cb4b-4e76-8684-01d96f91e453\"}\n[2026-04-27 10:31:11] local.INFO: [EncryptedTokenManager] Generating access token. {\"mode\":\"legacy\"} {\"correlation_id\":\"3b9d7e85-b8c2-4ae2-863e-c343c277afdc\",\"trace_id\":\"dba432b8-cb4b-4e76-8684-01d96f91e453\"}\n[2026-04-27 10:31:11] local.INFO: [CrmOwnerResolver] Integration owner matched as CRM Owner {\"crm_provider\":\"hubspot\",\"crm_owner\":89,\"team_id\":2} {\"correlation_id\":\"3b9d7e85-b8c2-4ae2-863e-c343c277afdc\",\"trace_id\":\"dba432b8-cb4b-4e76-8684-01d96f91e453\"}\n[2026-04-27 10:31:11] local.INFO: [SocialAccountService] Fetching token {\"socialAccountId\":408,\"provider\":\"hubspot\"} {\"correlation_id\":\"3b9d7e85-b8c2-4ae2-863e-c343c277afdc\",\"trace_id\":\"dba432b8-cb4b-4e76-8684-01d96f91e453\"}\n[2026-04-27 10:31:11] local.INFO: [SocialAccountService] Token needs refreshing {\"socialAccountId\":408,\"provider\":\"hubspot\"} {\"correlation_id\":\"3b9d7e85-b8c2-4ae2-863e-c343c277afdc\",\"trace_id\":\"dba432b8-cb4b-4e76-8684-01d96f91e453\"}\n[2026-04-27 10:31:11] local.INFO: [EncryptedTokenManager] Generating access token. {\"mode\":\"legacy\"} {\"correlation_id\":\"3b9d7e85-b8c2-4ae2-863e-c343c277afdc\",\"trace_id\":\"dba432b8-cb4b-4e76-8684-01d96f91e453\"}\n[2026-04-27 10:31:11] local.INFO: [SocialAccountService] Refreshing token from provider {\"socialAccountId\":408,\"provider\":\"hubspot\",\"refreshToken\":\"de4e47eb985578f4218833e763e31059e88b562e87e10749b3389be2328f0aa7\",\"state\":\"connected\"} {\"correlation_id\":\"3b9d7e85-b8c2-4ae2-863e-c343c277afdc\",\"trace_id\":\"dba432b8-cb4b-4e76-8684-01d96f91e453\"}\n[2026-04-27 10:31:11] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage before starting command {\"command\":\"crm:bullhorn:ping\",\"memoryBeforeCommandInMb\":60.0,\"memoryPeakBeforeCommandInMb\":99.727} {\"correlation_id\":\"a2085168-092a-44b0-8dd2-c8845bc36f86\",\"trace_id\":\"9445425b-6aaf-4a23-9323-ecf85939f967\"}\n[2026-04-27 10:31:11] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage for command {\"command\":\"crm:bullhorn:ping\",\"memoryBeforeCommandInMb\":60.0,\"memoryAfterCommandInMB\":62.0,\"memoryPeakBeforeCommandInMb\":99.727,\"memoryPeakAfterCommandInMB\":99.727} {\"correlation_id\":\"a2085168-092a-44b0-8dd2-c8845bc36f86\",\"trace_id\":\"9445425b-6aaf-4a23-9323-ecf85939f967\"}\n[2026-04-27 10:31:12] local.INFO: [SocialAccountObserver] Saving model {\"correlation_id\":\"3b9d7e85-b8c2-4ae2-863e-c343c277afdc\",\"trace_id\":\"dba432b8-cb4b-4e76-8684-01d96f91e453\"}","role_description":"text entry area","is_enabled":true,"is_focused":true,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Sync Changes","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.008643617,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Hide This Notification","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.008643617,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Code changed:","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.042220745,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Hide","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.008643617,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"2","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.007978723,"height":0.0},"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"60","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.010305851,"height":0.0},"on_screen":false,"role_description":"text"},{"role":"AXButton","text":"Previous Highlighted Error","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.00731383,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Next Highlighted Error","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.006981383,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXTextArea","text":"<?php\n\ndeclare(strict_types=1);\n\nnamespace Jiminny\\Services\\Crm\\Hubspot;\n\nuse HubSpot\\Client\\Crm\\Deals\\ApiException as DealApiException;\nuse HubSpot\\Client\\Crm\\Contacts\\ApiException as ContactApiException;\nuse HubSpot\\Client\\Crm\\Companies\\ApiException as CompanyApiException;\nuse HubSpot\\Client\\Crm\\Contacts\\Model\\SimplePublicObjectWithAssociations as ContactsWithAssociations;\nuse HubSpot\\Client\\Crm\\Companies\\Model\\SimplePublicObjectWithAssociations as CompaniesWithAssociations;\nuse HubSpot\\Client\\Crm\\Deals\\Model\\SimplePublicObjectWithAssociations as DealWithAssociations;\nuse HubSpot\\Client\\Crm\\Objects\\Model\\SimplePublicObjectInput;\nuse HubSpot\\Client\\Crm\\Objects\\Model\\SimplePublicObjectWithAssociations as ObjectWithAssociations;\nuse HubSpot\\Client\\Crm\\Pipelines\\Model\\Error;\nuse HubSpot\\Client\\Crm\\Pipelines\\Model\\PipelineStage;\nuse HubSpot\\Client\\Crm\\Properties\\Model\\Property;\nuse HubSpot\\Discovery\\Discovery;\nuse Jiminny\\Component\\Utility\\Service\\ProviderRateLimiter;\nuse Jiminny\\Exceptions\\CrmException;\nuse Jiminny\\Exceptions\\RateLimitException;\nuse Jiminny\\Exceptions\\SocialAccountTokenInvalidException;\nuse Jiminny\\Jobs\\Crm\\NoteObject;\nuse Jiminny\\Models\\Crm\\Field;\nuse Jiminny\\Services\\Crm\\BaseClient;\nuse Jiminny\\Services\\Crm\\Hubspot\\DTO\\Response\\Owner;\nuse Jiminny\\Services\\SocialAccountService;\nuse SevenShores\\Hubspot\\Exceptions\\BadRequest;\nuse SevenShores\\Hubspot\\Exceptions\\HubspotException;\nuse SevenShores\\Hubspot\\Factory;\nuse SevenShores\\Hubspot\\Http\\Response;\nuse Jiminny\\Services\\Crm\\Hubspot\\Pagination\\HubspotPaginationService;\nuse Throwable;\n\n/**\n * @phpstan-type CrmFieldOption array{id:string, label:string, value?:string}\n */\nclass Client extends BaseClient implements HubspotClientInterface\n{\n public const string MIN_API_VERSION = '2';\n\n public const string BASE_URL = 'https://api.hubapi.com';\n\n public const int ASSOCIATIONS_BATCH_SIZE_LIMIT = 1000;\n\n private HubspotPaginationService $paginationService;\n private HubspotTokenManager $tokenManager;\n private ProviderRateLimiter $rateLimiter;\n\n public function __construct(\n SocialAccountService $socialAccountService,\n HubspotPaginationService $paginationService,\n HubspotTokenManager $tokenManager,\n ProviderRateLimiter $rateLimiter,\n ) {\n parent::__construct($socialAccountService);\n $this->paginationService = $paginationService;\n $this->tokenManager = $tokenManager;\n $this->rateLimiter = $rateLimiter;\n\n $this->setBaseUrl(self::BASE_URL);\n $this->setVersion(self::MIN_API_VERSION);\n }\n\n /**\n * Single entry point for every HubSpot API call. Enforces the per-portal\n * rate limit configured in the rate_limits table (morphed to the current\n * Configuration) and reacts to a real 429 from HubSpot by translating it\n * into a RateLimitException carrying Retry-After.\n *\n * Wrap any outbound HubSpot call (SDK or raw HTTP) like:\n *\n * $this->executeRequest(fn () => $this->getNewInstance()->crm()->...);\n *\n * @template T\n * @param callable(): T $apiCall\n * @return T\n *\n * @throws RateLimitException\n */\n private function executeRequest(callable $apiCall)\n {\n if (! $this->rateLimiter->canMakeRequest($this->config)) {\n $retryAfter = $this->rateLimiter->requestAvailableIn($this->config);\n\n $this->log->warning('[Hubspot] Rate limit exceeded, deferring request', [\n 'team_id' => $this->config->team_id,\n 'config_id' => $this->config->getId(),\n 'retry_after' => $retryAfter,\n ]);\n\n throw new RateLimitException(\n 'Hubspot rate limit reached for configuration ' . $this->config->getId(),\n $retryAfter,\n );\n }\n\n $this->rateLimiter->incrementRequestCount($this->config);\n\n try {\n return $apiCall();\n } catch (Throwable $e) {\n if ($this->isHubspotRateLimit($e)) {\n $retryAfter = $this->parseRetryAfter($e);\n\n $this->log->warning('[Hubspot] Received 429 from API', [\n 'team_id' => $this->config->team_id,\n 'config_id' => $this->config->getId(),\n 'retry_after' => $retryAfter,\n 'reason' => $e->getMessage(),\n ]);\n\n throw new RateLimitException('Hubspot returned 429', $retryAfter, $e);\n }\n\n throw $e;\n }\n }\n\n private function isHubspotRateLimit(Throwable $e): bool\n {\n return method_exists($e, 'getCode') && (int) $e->getCode() === 429;\n }\n\n private function parseRetryAfter(Throwable $e): int\n {\n if (method_exists($e, 'getResponseHeaders')) {\n $headers = $e->getResponseHeaders() ?: [];\n $value = $headers['Retry-After'] ?? $headers['retry-after'] ?? null;\n if (is_array($value)) {\n $value = $value[0] ?? null;\n }\n if (is_numeric($value)) {\n return (int) $value;\n }\n }\n\n return 10;\n }\n\n public function getMinimumApiVersion(): string\n {\n return self::MIN_API_VERSION;\n }\n\n public function getInstance(): Factory\n {\n return new Factory([\n 'key' => $this->accessToken,\n 'oauth2' => true,\n 'base_url' => $this->baseUrl,\n ]);\n }\n\n public function getNewInstance(): Discovery\n {\n return \\HubSpot\\Factory::createWithAccessToken($this->accessToken);\n }\n\n /**\n * Secondly and daily limits for Hubspot API\n *\n * Product Tier: Free & Starter | Professional & Enterprise | API add-on (any tier)\n * Burst: 100/10 seconds | 150/10 seconds | 200/10 seconds\n * Daily: 250,000 | 500,000 | 1,000,000\n *\n * Official documentation states: The search endpoints are rate limited to five requests per second.\n * Since with 5 RPS were still hitting secondly rate limits we lowered it to 4\n */\n public function getPaginatedData(array $payload, string $type, int $offset = 0): array\n {\n $total = 0;\n $lastId = null;\n $rows = [];\n foreach ($this->getPaginatedDataGenerator($payload, $type, $offset, $total, $lastId) as $row) {\n $rows[] = $row;\n }\n\n return ['results' => $rows, 'total' => $total, 'last_record' => $lastId];\n }\n\n /**\n * @throws HubspotException\n * @throws SocialAccountTokenInvalidException\n * @throws BadRequest\n */\n public function getPaginatedDataGenerator(\n array $payload,\n string $type,\n int $offset = 0,\n int &$total = 0,\n ?string &$lastRecordId = null\n ): \\Generator {\n return $this->paginationService->getPaginatedDataGenerator(\n $this,\n $payload,\n $type,\n $offset,\n $total,\n $lastRecordId\n );\n }\n\n /**\n * @throws DealApiException\n * @throws CrmException\n */\n public function getOpportunityById(string $crmId, array $fields): array\n {\n try {\n $deal = $this->executeRequest(fn () => $this->getNewInstance()->crm()->deals()->basicApi()->getById(\n $crmId,\n implode(',', $fields),\n 'companies,contacts'\n ));\n } catch (DealApiException $e) {\n $this->log->info('[Hubspot] Failed to fetch opportunity', [\n 'crm_id' => $crmId,\n 'reason' => $e->getMessage(),\n ]);\n\n throw $e;\n }\n\n if (! $deal instanceof DealWithAssociations) {\n throw new CrmException('Deal not found');\n }\n\n return [\n 'id' => $deal->getId(),\n 'properties' => $deal->getProperties(),\n 'associations' => $deal->getAssociations(),\n ];\n }\n\n /**\n * Generic batch read method for HubSpot objects\n *\n * @param string $objectType The object type ('deals', 'companies', 'contacts')\n * @param array<string> $crmIds Array of HubSpot object IDs (max 100)\n * @param array<string> $fields Array of property names to fetch\n *\n * @return array<string, array> Array keyed by CRM ID with object data\n */\n private function batchReadObjects(string $objectType, array $crmIds, array $fields): array\n {\n if (empty($crmIds)) {\n return [];\n }\n\n $this->validateBatchSize($objectType, $crmIds);\n $this->ensureValidToken();\n\n try {\n $batchConfig = $this->createBatchConfiguration($objectType);\n $batchReadRequest = $this->prepareBatchRequest($batchConfig, $crmIds, $fields);\n $response = $batchConfig['api']->read($batchReadRequest);\n\n $this->validateApiResponse($response, $objectType);\n\n $results = $this->processApiResults($response);\n $this->logBatchResults($objectType, $crmIds, $results);\n\n return $results;\n } catch (\\Throwable $e) {\n $this->handleBatchError($e, $objectType, $crmIds);\n }\n }\n\n private function validateBatchSize(string $objectType, array $crmIds): void\n {\n if (count($crmIds) > 100) {\n throw new \\InvalidArgumentException(\"Batch size cannot exceed 100 {$objectType}\");\n }\n }\n\n private function createBatchConfiguration(string $objectType): array\n {\n $configurations = [\n 'deals' => [\n 'batchReadRequest' => new \\HubSpot\\Client\\Crm\\Deals\\Model\\BatchReadInputSimplePublicObjectId(),\n 'inputClass' => \\HubSpot\\Client\\Crm\\Deals\\Model\\SimplePublicObjectId::class,\n 'api' => $this->getNewInstance()->crm()->deals()->batchApi(),\n ],\n 'companies' => [\n 'batchReadRequest' => new \\HubSpot\\Client\\Crm\\Companies\\Model\\BatchReadInputSimplePublicObjectId(),\n 'inputClass' => \\HubSpot\\Client\\Crm\\Companies\\Model\\SimplePublicObjectId::class,\n 'api' => $this->getNewInstance()->crm()->companies()->batchApi(),\n ],\n 'contacts' => [\n 'batchReadRequest' => new \\HubSpot\\Client\\Crm\\Contacts\\Model\\BatchReadInputSimplePublicObjectId(),\n 'inputClass' => \\HubSpot\\Client\\Crm\\Contacts\\Model\\SimplePublicObjectId::class,\n 'api' => $this->getNewInstance()->crm()->contacts()->batchApi(),\n ],\n ];\n\n if (! isset($configurations[$objectType])) {\n throw new \\InvalidArgumentException(\"Unsupported object type: {$objectType}\");\n }\n\n return $configurations[$objectType];\n }\n\n private function prepareBatchRequest(array $batchConfig, array $crmIds, array $fields): object\n {\n $batchReadRequest = $batchConfig['batchReadRequest'];\n $inputClass = $batchConfig['inputClass'];\n\n $inputs = array_map(function ($crmId) use ($inputClass) {\n $input = new $inputClass();\n $input->setId($crmId);\n\n return $input;\n }, $crmIds);\n\n $batchReadRequest->setInputs($inputs);\n $batchReadRequest->setProperties($fields);\n\n return $batchReadRequest;\n }\n\n private function validateApiResponse($response, string $objectType): void\n {\n if (! $response) {\n throw new CrmException(\"HubSpot API returned null response for {$objectType} batch read\");\n }\n }\n\n private function processApiResults($response): array\n {\n $results = [];\n $responseResults = $response->getResults();\n\n if ($responseResults) {\n foreach ($responseResults as $object) {\n if ($object && $object->getId()) {\n $results[$object->getId()] = [\n 'id' => $object->getId(),\n 'properties' => $object->getProperties() ?: [],\n ];\n }\n }\n }\n\n return $results;\n }\n\n private function logBatchResults(string $objectType, array $crmIds, array $results): void\n {\n $this->log->info(\"[HubSpot] Batch fetched {$objectType}\", [\n 'requested_count' => count($crmIds),\n 'returned_count' => count($results),\n 'crm_ids' => $crmIds,\n ]);\n }\n\n private function handleBatchError(\\Throwable $e, string $objectType, array $crmIds): void\n {\n $errorMessage = $e->getMessage() ?: 'Unknown error';\n $errorTrace = $e->getTraceAsString() ?: 'No trace available';\n\n $this->log->error(\"[HubSpot] Failed to batch fetch {$objectType}\", [\n 'crm_ids' => $crmIds,\n 'error' => $errorMessage,\n 'trace' => $errorTrace,\n ]);\n\n throw new CrmException(\"Failed to batch fetch {$objectType}: \" . $errorMessage);\n }\n\n /**\n * Batch read multiple opportunities by their CRM IDs\n *\n * @param array<string> $crmIds Array of HubSpot deal IDs (max 100)\n * @param array<string> $fields Array of property names to fetch\n *\n * @return array<string, array> Array keyed by CRM ID with opportunity data\n */\n public function getOpportunitiesByIds(array $crmIds, array $fields): array\n {\n return $this->batchReadObjects('deals', $crmIds, $fields);\n }\n\n /**\n * Batch read multiple companies by their CRM IDs\n *\n * @param array<string> $crmIds Array of HubSpot company IDs (max 100)\n * @param array<string> $fields Array of property names to fetch\n *\n * @return array<string, array> Array keyed by CRM ID with company data\n */\n public function getCompaniesByIds(array $crmIds, array $fields): array\n {\n return $this->batchReadObjects('companies', $crmIds, $fields);\n }\n\n /**\n * Batch read multiple contacts by their CRM IDs\n *\n * @param array<string> $crmIds Array of HubSpot contact IDs (max 100)\n * @param array<string> $fields Array of property names to fetch\n *\n * @return array<string, array> Array keyed by CRM ID with contact data\n */\n public function getContactsByIds(array $crmIds, array $fields): array\n {\n return $this->batchReadObjects('contacts', $crmIds, $fields);\n }\n\n /**\n * @throws CompanyApiException\n * @throws CrmException\n */\n public function getAccountById(string $crmId, array $fields): array\n {\n try {\n $company = $this->getNewInstance()->crm()->companies()->basicApi()->getById(\n $crmId,\n implode(',', $fields),\n );\n } catch (CompanyApiException $e) {\n $this->log->info('[Hubspot] Failed to fetch account', [\n 'crm_id' => $crmId,\n 'reason' => $e->getMessage(),\n ]);\n\n throw $e;\n }\n\n if (! $company instanceof CompaniesWithAssociations) {\n throw new CrmException('Account not found');\n }\n\n return [\n 'id' => $company->getId(),\n 'properties' => $company->getProperties(),\n ];\n }\n\n /**\n * @throws ContactApiException\n * @throws CrmException\n */\n public function getContactById(string $crmId, array $fields): array\n {\n try {\n $contact = $this->getNewInstance()->crm()->contacts()->basicApi()->getById(\n $crmId,\n implode(',', $fields)\n );\n } catch (ContactApiException $e) {\n $this->log->info('[Hubspot] Failed to fetch contact', [\n 'crm_id' => $crmId,\n 'reason' => $e->getMessage(),\n ]);\n\n throw $e;\n }\n\n if (! $contact instanceof ContactsWithAssociations) {\n throw new CrmException('Contact not found');\n }\n\n return [\n 'id' => $contact->getId(),\n 'properties' => $contact->getProperties(),\n ];\n }\n\n /**\n * This is email search request that Hubspot offers as GET (more generous quota)\n */\n public function getContactByEmail(string $email, array $fields = []): array\n {\n try {\n $contact = $this->getNewInstance()->crm()->contacts()->basicApi()->getById(\n $email,\n implode(',', $fields),\n null,\n false,\n 'email'\n );\n\n return [\n 'id' => $contact->getId(),\n 'properties' => $contact->getProperties(),\n ];\n } catch (ContactApiException $e) {\n $this->log->info('[Hubspot] Failed to fetch contact', [\n 'email' => $email,\n 'reason' => $e->getMessage(),\n ]);\n\n return [];\n }\n }\n\n /**\n * @throws CrmException\n */\n public function fetchProperty(string $objectType, string $propertyId): Property\n {\n $result = $this->getNewInstance()->crm()->properties()->coreApi()->getByName($objectType, $propertyId);\n\n if (! $result instanceof Property) {\n $this->log->error('[Hubspot] Failed to fetch property', [\n 'object_type' => $objectType,\n 'property_id' => $propertyId,\n 'reason' => $result->getMessage(),\n ]);\n\n throw new CrmException('Failed to fetch property');\n }\n\n return $result;\n }\n\n /**\n * @return array<CrmFieldOption>\n */\n public function fetchPropertyOptions(string $objectType, string $propertyId): array\n {\n /** @var array<CrmFieldOption> */\n return $this->fetchProperty($objectType, $propertyId)->getOptions();\n }\n\n /**\n * @return array<array{id:string, label:string, deleted:bool}>\n */\n public function fetchCallDispositions(): array\n {\n /** @var Response $response */\n $response = $this->getInstance()->engagements()->getCallDispositions();\n\n /**\n * @var array<array{\n * id:string,\n * label:string,\n * deleted: bool\n * }>\n */\n return $response->toArray();\n }\n\n /**\n * @return array<CrmFieldOption>\n */\n public function fetchOpportunityPipelineStages(): array\n {\n $stages = [];\n $apiResponse = $this->getNewInstance()->crm()->pipelines()->pipelinesApi()->getAll('deals');\n\n if ($apiResponse instanceof Error) {\n $this->log->error('[Hubspot] Failed to fetch opportunity pipelines', [\n 'reason' => $apiResponse->getMessage(),\n ]);\n\n return [];\n }\n\n foreach ($apiResponse->getResults() as $pipeline) {\n $pipelineStages = array_map(\n static function (PipelineStage $stage) {\n return [\n 'id' => $stage->getId(),\n 'label' => $stage->getLabel(),\n ];\n },\n $pipeline->getStages()\n );\n\n $stages = array_merge($stages, $pipelineStages);\n }\n\n return $stages;\n }\n\n public function fetchOpportunityPipelines(): array\n {\n $pipelines = [];\n\n try {\n $apiResponse = $this->makeRequest('/crm/v3/pipelines/deals');\n } catch (\\Exception $e) {\n $this->log->info('[Hubspot] Failed to fetch opportunity pipelines', [\n 'reason' => $e->getMessage(),\n ]);\n\n return [];\n }\n\n $response = $apiResponse->toArray();\n\n foreach ($response['results'] as $pipeline) {\n $pipelines[] = [\n 'id' => $pipeline['id'],\n 'label' => $pipeline['label'],\n ];\n }\n\n return $pipelines;\n }\n\n /**\n * @return array<CrmFieldOption>\n */\n public function fetchMeetingOutcomeFieldOptions(Field $field): array\n {\n return $field->getCrmProviderId() === 'meetingOutcome'\n ? $this->fetchMeetingOutcomeTypes()\n : $this->fetchCallActivityTypes();\n }\n\n public function fetchMeetingOutcomeTypes(): array\n {\n return $this->extractMeetingTypeOptions(\n 'https://api.hubapi.com/crm/v3/properties/meeting/hs_meeting_outcome'\n );\n }\n\n public function fetchCallActivityTypes(): array\n {\n return $this->extractMeetingTypeOptions(\n 'https://api.hubapi.com/crm/v3/properties/call/hs_activity_type'\n );\n }\n\n private function extractMeetingTypeOptions(string $endpoint): array\n {\n /** @var Response $response */\n $response = $this->getInstance()\n ->getClient()\n ->request('GET', $endpoint);\n\n /**\n * @var array<array{\n * value: string,\n * label: string,\n * displayOrder: int\n * }> $optionData\n */\n $optionData = $response->toArray()['options'] ?? [];\n\n $options = [];\n foreach ($optionData as $item) {\n $options[] = [\n 'id' => $item['value'],\n 'value' => $item['value'],\n 'label' => $item['label'],\n 'display_order' => $item['displayOrder'],\n ];\n }\n\n return $options;\n }\n\n /**\n * @return array<CrmFieldOption>\n */\n public function fetchDispositionFieldOptions(): array\n {\n $options = [];\n\n $dispositions = $this->fetchCallDispositions();\n\n foreach ($dispositions as $disposition) {\n if ($disposition['deleted'] !== false) {\n continue;\n }\n\n $option['value'] = $disposition['id'];\n $option['id'] = $disposition['id'];\n $option['label'] = $disposition['label'];\n\n $options[] = $option;\n }\n\n return $options;\n }\n\n /**\n * @return array<CrmFieldOption>\n */\n public function fetchOpportunityFieldOptions(Field $field): array\n {\n if ($field->isStageField()) {\n return $this->fetchOpportunityPipelineStages();\n }\n\n if ($field->isPipelineField()) {\n return $this->fetchOpportunityPipelines();\n }\n\n return $this->fetchPropertyOptions('deals', $field->getCrmProviderId());\n }\n\n /**\n * @throws BadRequest\n * @throws HubspotException\n */\n public function makeRequest(string $endpoint, $method = 'GET', $payload = [], ?string $queryString = null)\n {\n $endpoint = self::BASE_URL . $endpoint;\n\n if ($method === 'GET') {\n $response = $this->getInstance()->getClient()?->request(\n method: $method,\n endpoint: $endpoint,\n query_string: $queryString\n );\n } else {\n $response = $this->getInstance()->getClient()->request($method, $endpoint, [\n 'json' => ($payload),\n ]);\n }\n\n $max = $response->getHeaderLine('X-HubSpot-RateLimit-Max'); // \"110\"\n $remaining = $response->getHeaderLine('X-HubSpot-RateLimit-Remaining'); // \"109\"\n $interval = $response->getHeaderLine('X-HubSpot-RateLimit-Interval-Milliseconds'); // \"10000\"\n $body = json_decode((string) $response->getBody(), true);\n\n \\Illuminate\\Support\\Facades\\Log::channel('custom_channel')->info('$max ' . PHP_EOL . print_r($max, true));\n \\Illuminate\\Support\\Facades\\Log::channel('custom_channel')->info('$remaining ' . PHP_EOL . print_r($remaining, true));\n \\Illuminate\\Support\\Facades\\Log::channel('custom_channel')->info('$interval ' . PHP_EOL . print_r($interval, true));\n \\Illuminate\\Support\\Facades\\Log::channel('custom_channel')->info('$body ' . PHP_EOL . print_r($body, true));\n\n return $response;\n }\n\n /**\n * @throws BadRequest\n * @throws HubspotException\n */\n public function createMeeting(array $payload): Response\n {\n $endpoint = '/crm/v3/objects/meetings';\n\n return $this->makeRequest($endpoint, 'POST', $payload);\n }\n\n /**\n * @throws BadRequest\n * @throws HubspotException\n */\n public function updateMeeting(string $meetingId, array $payload): Response\n {\n $endpoint = '/crm/v3/objects/meetings/' . $meetingId;\n\n return $this->makeRequest($endpoint, 'PATCH', $payload);\n }\n\n /**\n * @throws \\Exception\n */\n public function createNote(\n string $body,\n string $ownerId,\n int $timestamp,\n string $objectId,\n NoteObject $noteObject\n ): ?string {\n try {\n $noteInput = new SimplePublicObjectInput([\n 'properties' => [\n 'hs_note_body' => $body,\n 'hubspot_owner_id' => $ownerId,\n 'hs_timestamp' => $timestamp,\n ],\n ]);\n\n // Create note\n $note = $this->getNewInstance()->crm()->objects()->basicApi()->create('note', $noteInput);\n\n $this->getNewInstance()->crm()->objects()->associationsApi()->create(\n 'note',\n $note->getId(),\n $this->getNoteObject($noteObject),\n $objectId,\n $this->getNoteAssociationType($noteObject),\n );\n\n return $note->getId();\n } catch (\\Exception $e) {\n $this->log->error('[Hubspot] Failed to create note', [\n 'objectId' => $objectId,\n 'noteObject' => $noteObject->getObjectType(),\n 'reason' => $e->getMessage(),\n ]);\n\n \\Sentry::captureException($e);\n }\n\n return null;\n }\n\n public function updateEngagement(string $objectId, array $engagement, array $metadata): void\n {\n $this->getInstance()->engagements()->update($objectId, $engagement, $metadata);\n }\n\n public function getEngagementData(string $engagementId): array\n {\n $engagement = $this->getInstance()->engagements()->get($engagementId);\n\n return $engagement->toArray();\n }\n\n public function createEngagement(array $engagement, array $associations, array $metadata): Response\n {\n return $this->getInstance()\n ->engagements()\n ->create($engagement, $associations, $metadata);\n }\n\n public function isUnauthorizedException(\\Exception $e): bool\n {\n // Check for specific HubSpot API exception types first\n if ($e instanceof BadRequest) {\n // BadRequest can contain 401 status codes\n return $e->getCode() === 401;\n }\n\n // Check for HTTP client exceptions with status codes\n if ($e instanceof \\GuzzleHttp\\Exception\\RequestException && $e->hasResponse()) {\n $response = $e->getResponse();\n if ($response !== null) {\n return $response->getStatusCode() === 401;\n }\n }\n\n // Check for Guzzle HTTP exceptions\n if ($e instanceof \\GuzzleHttp\\Exception\\ClientException) {\n return $e->getCode() === 401;\n }\n\n // Fallback to string matching as last resort, but be more specific\n $message = strtolower($e->getMessage());\n\n return str_contains($message, '401 unauthorized') ||\n str_contains($message, 'http 401') ||\n str_contains($message, 'status code 401') ||\n (preg_match('/\\b401\\b/', $message) && str_contains($message, 'unauthorized'));\n }\n\n /**\n * Validates and refreshes the access token if needed before API requests.\n * This ensures long-running processes don't fail due to token expiration.\n *\n * @throws SocialAccountTokenInvalidException\n */\n public function ensureValidToken(): void\n {\n if ($this->oauthAccount === null) {\n return;\n }\n\n $newToken = $this->tokenManager->ensureValidToken($this->oauthAccount);\n if ($newToken !== null) {\n $this->accessToken = $newToken;\n }\n }\n\n public function getConfig()\n {\n return $this->config;\n }\n\n // returns only active (archived=false)\n public function getOwners(): array\n {\n return $this->getNewInstance()->crm()->owners()->getAll();\n }\n\n /**\n * @param bool $archived\n *\n * @return array<Owner>|[]\n */\n public function getOwnersArchived(bool $archived = true): array\n {\n $endpoint = '/crm/v3/owners';\n $queryParams = [\n 'archived' => $archived ? 'true' : 'false',\n ];\n $queryString = http_build_query($queryParams);\n\n $owners = [];\n\n try {\n $response = $this->makeRequest(endpoint: $endpoint, queryString: $queryString);\n $responseData = $response?->toArray();\n\n foreach ($responseData['results'] as $result) {\n try {\n $owners[] = Owner::create($result);\n } catch (Throwable $e) {\n $this->log->error('[HubSpot] Failed to process owner data', [\n 'result' => $result,\n 'error' => $e->getMessage(),\n ]);\n\n continue;\n }\n }\n } catch (Throwable $e) {\n $this->log->error('HubSpot] Failed to fetch owners', [\n 'archived' => $archived,\n 'error' => $e->getMessage(),\n ]);\n\n return [];\n }\n\n return $owners;\n }\n\n public function getMeeting(string $engagementId): ObjectWithAssociations\n {\n return $this->getNewInstance()->crm()->objects()->basicApi()\n ->getById('meeting', $engagementId, null, 'contact,company,deal');\n }\n\n public function deleteEngagement(string $engagementId): void\n {\n $this->getInstance()->engagements()->delete((int) $engagementId);\n }\n\n public function getAssociationsData(array $ids, string $fromObject, string $toObject): array\n {\n $associationData = [];\n $idChunks = array_chunk($ids, self::ASSOCIATIONS_BATCH_SIZE_LIMIT);\n\n foreach ($idChunks as $idChunk) {\n try {\n $batchInput = new \\HubSpot\\Client\\Crm\\Associations\\Model\\BatchInputPublicObjectId();\n $batchInput->setInputs(array_map(function ($id) {\n $publicObjectId = new \\HubSpot\\Client\\Crm\\Associations\\Model\\PublicObjectId();\n $publicObjectId->setId($id);\n\n return $publicObjectId;\n }, $idChunk));\n\n $associatedObjectsData = $this\n ->getNewInstance()\n ->crm()\n ->associations()\n ->batchApi()\n ->read($fromObject, $toObject, $batchInput);\n\n if ($associatedObjectsData instanceof \\HubSpot\\Client\\Crm\\Associations\\Model\\BatchResponsePublicAssociationMulti) {\n foreach ($associatedObjectsData->getResults() as $association) {\n $from = $association->getFrom()->getId();\n $toAssociations = $association->getTo();\n\n if (! empty($toAssociations)) {\n $associationData[$from] = array_map(function ($item) {\n return $item->getId();\n }, $toAssociations);\n }\n }\n }\n } catch (\\Exception $e) {\n $this->log->error('[Hubspot] Failed to fetch associations', [\n 'from_object' => $fromObject,\n 'to_object' => $toObject,\n 'reason' => $e->getMessage(),\n ]);\n }\n }\n\n return $associationData;\n }\n\n /**\n * @throws \\Exception\n */\n private function getNoteAssociationType(NoteObject $noteObject): string\n {\n return match($noteObject) {\n NoteObject::Opportunity => 'note_to_deal',\n NoteObject::Lead, NoteObject::Contact => 'note_to_contact', // or 'note_to_lead' if your portal supports it\n NoteObject::Account => 'note_to_company',\n NoteObject::Call, NoteObject::Event => throw new \\Exception('Not supported'),\n };\n }\n\n /**\n * @throws \\Exception\n */\n private function getNoteObject(NoteObject $noteObject): string\n {\n return match($noteObject) {\n NoteObject::Opportunity => 'deal',\n NoteObject::Lead, NoteObject::Contact => 'contact',\n NoteObject::Account => 'company',\n NoteObject::Call, NoteObject::Event => throw new \\Exception('Not supported'),\n };\n }\n\n public function addAssociations(string $objectType, string $associationType, array $payload): Response\n {\n $endpoint = \"/crm/v4/associations/$objectType/$associationType/batch/create\";\n\n return $this->makeRequest($endpoint, 'POST', $payload);\n }\n\n public function removeAssociations(string $objectType, string $associationType, array $payload): Response\n {\n $endpoint = \"/crm/v4/associations/$objectType/$associationType/batch/archive\";\n\n return $this->makeRequest($endpoint, 'POST', $payload);\n }\n}","depth":4,"on_screen":true,"value":"<?php\n\ndeclare(strict_types=1);\n\nnamespace Jiminny\\Services\\Crm\\Hubspot;\n\nuse HubSpot\\Client\\Crm\\Deals\\ApiException as DealApiException;\nuse HubSpot\\Client\\Crm\\Contacts\\ApiException as ContactApiException;\nuse HubSpot\\Client\\Crm\\Companies\\ApiException as CompanyApiException;\nuse HubSpot\\Client\\Crm\\Contacts\\Model\\SimplePublicObjectWithAssociations as ContactsWithAssociations;\nuse HubSpot\\Client\\Crm\\Companies\\Model\\SimplePublicObjectWithAssociations as CompaniesWithAssociations;\nuse HubSpot\\Client\\Crm\\Deals\\Model\\SimplePublicObjectWithAssociations as DealWithAssociations;\nuse HubSpot\\Client\\Crm\\Objects\\Model\\SimplePublicObjectInput;\nuse HubSpot\\Client\\Crm\\Objects\\Model\\SimplePublicObjectWithAssociations as ObjectWithAssociations;\nuse HubSpot\\Client\\Crm\\Pipelines\\Model\\Error;\nuse HubSpot\\Client\\Crm\\Pipelines\\Model\\PipelineStage;\nuse HubSpot\\Client\\Crm\\Properties\\Model\\Property;\nuse HubSpot\\Discovery\\Discovery;\nuse Jiminny\\Component\\Utility\\Service\\ProviderRateLimiter;\nuse Jiminny\\Exceptions\\CrmException;\nuse Jiminny\\Exceptions\\RateLimitException;\nuse Jiminny\\Exceptions\\SocialAccountTokenInvalidException;\nuse Jiminny\\Jobs\\Crm\\NoteObject;\nuse Jiminny\\Models\\Crm\\Field;\nuse Jiminny\\Services\\Crm\\BaseClient;\nuse Jiminny\\Services\\Crm\\Hubspot\\DTO\\Response\\Owner;\nuse Jiminny\\Services\\SocialAccountService;\nuse SevenShores\\Hubspot\\Exceptions\\BadRequest;\nuse SevenShores\\Hubspot\\Exceptions\\HubspotException;\nuse SevenShores\\Hubspot\\Factory;\nuse SevenShores\\Hubspot\\Http\\Response;\nuse Jiminny\\Services\\Crm\\Hubspot\\Pagination\\HubspotPaginationService;\nuse Throwable;\n\n/**\n * @phpstan-type CrmFieldOption array{id:string, label:string, value?:string}\n */\nclass Client extends BaseClient implements HubspotClientInterface\n{\n public const string MIN_API_VERSION = '2';\n\n public const string BASE_URL = 'https://api.hubapi.com';\n\n public const int ASSOCIATIONS_BATCH_SIZE_LIMIT = 1000;\n\n private HubspotPaginationService $paginationService;\n private HubspotTokenManager $tokenManager;\n private ProviderRateLimiter $rateLimiter;\n\n public function __construct(\n SocialAccountService $socialAccountService,\n HubspotPaginationService $paginationService,\n HubspotTokenManager $tokenManager,\n ProviderRateLimiter $rateLimiter,\n ) {\n parent::__construct($socialAccountService);\n $this->paginationService = $paginationService;\n $this->tokenManager = $tokenManager;\n $this->rateLimiter = $rateLimiter;\n\n $this->setBaseUrl(self::BASE_URL);\n $this->setVersion(self::MIN_API_VERSION);\n }\n\n /**\n * Single entry point for every HubSpot API call. Enforces the per-portal\n * rate limit configured in the rate_limits table (morphed to the current\n * Configuration) and reacts to a real 429 from HubSpot by translating it\n * into a RateLimitException carrying Retry-After.\n *\n * Wrap any outbound HubSpot call (SDK or raw HTTP) like:\n *\n * $this->executeRequest(fn () => $this->getNewInstance()->crm()->...);\n *\n * @template T\n * @param callable(): T $apiCall\n * @return T\n *\n * @throws RateLimitException\n */\n private function executeRequest(callable $apiCall)\n {\n if (! $this->rateLimiter->canMakeRequest($this->config)) {\n $retryAfter = $this->rateLimiter->requestAvailableIn($this->config);\n\n $this->log->warning('[Hubspot] Rate limit exceeded, deferring request', [\n 'team_id' => $this->config->team_id,\n 'config_id' => $this->config->getId(),\n 'retry_after' => $retryAfter,\n ]);\n\n throw new RateLimitException(\n 'Hubspot rate limit reached for configuration ' . $this->config->getId(),\n $retryAfter,\n );\n }\n\n $this->rateLimiter->incrementRequestCount($this->config);\n\n try {\n return $apiCall();\n } catch (Throwable $e) {\n if ($this->isHubspotRateLimit($e)) {\n $retryAfter = $this->parseRetryAfter($e);\n\n $this->log->warning('[Hubspot] Received 429 from API', [\n 'team_id' => $this->config->team_id,\n 'config_id' => $this->config->getId(),\n 'retry_after' => $retryAfter,\n 'reason' => $e->getMessage(),\n ]);\n\n throw new RateLimitException('Hubspot returned 429', $retryAfter, $e);\n }\n\n throw $e;\n }\n }\n\n private function isHubspotRateLimit(Throwable $e): bool\n {\n return method_exists($e, 'getCode') && (int) $e->getCode() === 429;\n }\n\n private function parseRetryAfter(Throwable $e): int\n {\n if (method_exists($e, 'getResponseHeaders')) {\n $headers = $e->getResponseHeaders() ?: [];\n $value = $headers['Retry-After'] ?? $headers['retry-after'] ?? null;\n if (is_array($value)) {\n $value = $value[0] ?? null;\n }\n if (is_numeric($value)) {\n return (int) $value;\n }\n }\n\n return 10;\n }\n\n public function getMinimumApiVersion(): string\n {\n return self::MIN_API_VERSION;\n }\n\n public function getInstance(): Factory\n {\n return new Factory([\n 'key' => $this->accessToken,\n 'oauth2' => true,\n 'base_url' => $this->baseUrl,\n ]);\n }\n\n public function getNewInstance(): Discovery\n {\n return \\HubSpot\\Factory::createWithAccessToken($this->accessToken);\n }\n\n /**\n * Secondly and daily limits for Hubspot API\n *\n * Product Tier: Free & Starter | Professional & Enterprise | API add-on (any tier)\n * Burst: 100/10 seconds | 150/10 seconds | 200/10 seconds\n * Daily: 250,000 | 500,000 | 1,000,000\n *\n * Official documentation states: The search endpoints are rate limited to five requests per second.\n * Since with 5 RPS were still hitting secondly rate limits we lowered it to 4\n */\n public function getPaginatedData(array $payload, string $type, int $offset = 0): array\n {\n $total = 0;\n $lastId = null;\n $rows = [];\n foreach ($this->getPaginatedDataGenerator($payload, $type, $offset, $total, $lastId) as $row) {\n $rows[] = $row;\n }\n\n return ['results' => $rows, 'total' => $total, 'last_record' => $lastId];\n }\n\n /**\n * @throws HubspotException\n * @throws SocialAccountTokenInvalidException\n * @throws BadRequest\n */\n public function getPaginatedDataGenerator(\n array $payload,\n string $type,\n int $offset = 0,\n int &$total = 0,\n ?string &$lastRecordId = null\n ): \\Generator {\n return $this->paginationService->getPaginatedDataGenerator(\n $this,\n $payload,\n $type,\n $offset,\n $total,\n $lastRecordId\n );\n }\n\n /**\n * @throws DealApiException\n * @throws CrmException\n */\n public function getOpportunityById(string $crmId, array $fields): array\n {\n try {\n $deal = $this->executeRequest(fn () => $this->getNewInstance()->crm()->deals()->basicApi()->getById(\n $crmId,\n implode(',', $fields),\n 'companies,contacts'\n ));\n } catch (DealApiException $e) {\n $this->log->info('[Hubspot] Failed to fetch opportunity', [\n 'crm_id' => $crmId,\n 'reason' => $e->getMessage(),\n ]);\n\n throw $e;\n }\n\n if (! $deal instanceof DealWithAssociations) {\n throw new CrmException('Deal not found');\n }\n\n return [\n 'id' => $deal->getId(),\n 'properties' => $deal->getProperties(),\n 'associations' => $deal->getAssociations(),\n ];\n }\n\n /**\n * Generic batch read method for HubSpot objects\n *\n * @param string $objectType The object type ('deals', 'companies', 'contacts')\n * @param array<string> $crmIds Array of HubSpot object IDs (max 100)\n * @param array<string> $fields Array of property names to fetch\n *\n * @return array<string, array> Array keyed by CRM ID with object data\n */\n private function batchReadObjects(string $objectType, array $crmIds, array $fields): array\n {\n if (empty($crmIds)) {\n return [];\n }\n\n $this->validateBatchSize($objectType, $crmIds);\n $this->ensureValidToken();\n\n try {\n $batchConfig = $this->createBatchConfiguration($objectType);\n $batchReadRequest = $this->prepareBatchRequest($batchConfig, $crmIds, $fields);\n $response = $batchConfig['api']->read($batchReadRequest);\n\n $this->validateApiResponse($response, $objectType);\n\n $results = $this->processApiResults($response);\n $this->logBatchResults($objectType, $crmIds, $results);\n\n return $results;\n } catch (\\Throwable $e) {\n $this->handleBatchError($e, $objectType, $crmIds);\n }\n }\n\n private function validateBatchSize(string $objectType, array $crmIds): void\n {\n if (count($crmIds) > 100) {\n throw new \\InvalidArgumentException(\"Batch size cannot exceed 100 {$objectType}\");\n }\n }\n\n private function createBatchConfiguration(string $objectType): array\n {\n $configurations = [\n 'deals' => [\n 'batchReadRequest' => new \\HubSpot\\Client\\Crm\\Deals\\Model\\BatchReadInputSimplePublicObjectId(),\n 'inputClass' => \\HubSpot\\Client\\Crm\\Deals\\Model\\SimplePublicObjectId::class,\n 'api' => $this->getNewInstance()->crm()->deals()->batchApi(),\n ],\n 'companies' => [\n 'batchReadRequest' => new \\HubSpot\\Client\\Crm\\Companies\\Model\\BatchReadInputSimplePublicObjectId(),\n 'inputClass' => \\HubSpot\\Client\\Crm\\Companies\\Model\\SimplePublicObjectId::class,\n 'api' => $this->getNewInstance()->crm()->companies()->batchApi(),\n ],\n 'contacts' => [\n 'batchReadRequest' => new \\HubSpot\\Client\\Crm\\Contacts\\Model\\BatchReadInputSimplePublicObjectId(),\n 'inputClass' => \\HubSpot\\Client\\Crm\\Contacts\\Model\\SimplePublicObjectId::class,\n 'api' => $this->getNewInstance()->crm()->contacts()->batchApi(),\n ],\n ];\n\n if (! isset($configurations[$objectType])) {\n throw new \\InvalidArgumentException(\"Unsupported object type: {$objectType}\");\n }\n\n return $configurations[$objectType];\n }\n\n private function prepareBatchRequest(array $batchConfig, array $crmIds, array $fields): object\n {\n $batchReadRequest = $batchConfig['batchReadRequest'];\n $inputClass = $batchConfig['inputClass'];\n\n $inputs = array_map(function ($crmId) use ($inputClass) {\n $input = new $inputClass();\n $input->setId($crmId);\n\n return $input;\n }, $crmIds);\n\n $batchReadRequest->setInputs($inputs);\n $batchReadRequest->setProperties($fields);\n\n return $batchReadRequest;\n }\n\n private function validateApiResponse($response, string $objectType): void\n {\n if (! $response) {\n throw new CrmException(\"HubSpot API returned null response for {$objectType} batch read\");\n }\n }\n\n private function processApiResults($response): array\n {\n $results = [];\n $responseResults = $response->getResults();\n\n if ($responseResults) {\n foreach ($responseResults as $object) {\n if ($object && $object->getId()) {\n $results[$object->getId()] = [\n 'id' => $object->getId(),\n 'properties' => $object->getProperties() ?: [],\n ];\n }\n }\n }\n\n return $results;\n }\n\n private function logBatchResults(string $objectType, array $crmIds, array $results): void\n {\n $this->log->info(\"[HubSpot] Batch fetched {$objectType}\", [\n 'requested_count' => count($crmIds),\n 'returned_count' => count($results),\n 'crm_ids' => $crmIds,\n ]);\n }\n\n private function handleBatchError(\\Throwable $e, string $objectType, array $crmIds): void\n {\n $errorMessage = $e->getMessage() ?: 'Unknown error';\n $errorTrace = $e->getTraceAsString() ?: 'No trace available';\n\n $this->log->error(\"[HubSpot] Failed to batch fetch {$objectType}\", [\n 'crm_ids' => $crmIds,\n 'error' => $errorMessage,\n 'trace' => $errorTrace,\n ]);\n\n throw new CrmException(\"Failed to batch fetch {$objectType}: \" . $errorMessage);\n }\n\n /**\n * Batch read multiple opportunities by their CRM IDs\n *\n * @param array<string> $crmIds Array of HubSpot deal IDs (max 100)\n * @param array<string> $fields Array of property names to fetch\n *\n * @return array<string, array> Array keyed by CRM ID with opportunity data\n */\n public function getOpportunitiesByIds(array $crmIds, array $fields): array\n {\n return $this->batchReadObjects('deals', $crmIds, $fields);\n }\n\n /**\n * Batch read multiple companies by their CRM IDs\n *\n * @param array<string> $crmIds Array of HubSpot company IDs (max 100)\n * @param array<string> $fields Array of property names to fetch\n *\n * @return array<string, array> Array keyed by CRM ID with company data\n */\n public function getCompaniesByIds(array $crmIds, array $fields): array\n {\n return $this->batchReadObjects('companies', $crmIds, $fields);\n }\n\n /**\n * Batch read multiple contacts by their CRM IDs\n *\n * @param array<string> $crmIds Array of HubSpot contact IDs (max 100)\n * @param array<string> $fields Array of property names to fetch\n *\n * @return array<string, array> Array keyed by CRM ID with contact data\n */\n public function getContactsByIds(array $crmIds, array $fields): array\n {\n return $this->batchReadObjects('contacts', $crmIds, $fields);\n }\n\n /**\n * @throws CompanyApiException\n * @throws CrmException\n */\n public function getAccountById(string $crmId, array $fields): array\n {\n try {\n $company = $this->getNewInstance()->crm()->companies()->basicApi()->getById(\n $crmId,\n implode(',', $fields),\n );\n } catch (CompanyApiException $e) {\n $this->log->info('[Hubspot] Failed to fetch account', [\n 'crm_id' => $crmId,\n 'reason' => $e->getMessage(),\n ]);\n\n throw $e;\n }\n\n if (! $company instanceof CompaniesWithAssociations) {\n throw new CrmException('Account not found');\n }\n\n return [\n 'id' => $company->getId(),\n 'properties' => $company->getProperties(),\n ];\n }\n\n /**\n * @throws ContactApiException\n * @throws CrmException\n */\n public function getContactById(string $crmId, array $fields): array\n {\n try {\n $contact = $this->getNewInstance()->crm()->contacts()->basicApi()->getById(\n $crmId,\n implode(',', $fields)\n );\n } catch (ContactApiException $e) {\n $this->log->info('[Hubspot] Failed to fetch contact', [\n 'crm_id' => $crmId,\n 'reason' => $e->getMessage(),\n ]);\n\n throw $e;\n }\n\n if (! $contact instanceof ContactsWithAssociations) {\n throw new CrmException('Contact not found');\n }\n\n return [\n 'id' => $contact->getId(),\n 'properties' => $contact->getProperties(),\n ];\n }\n\n /**\n * This is email search request that Hubspot offers as GET (more generous quota)\n */\n public function getContactByEmail(string $email, array $fields = []): array\n {\n try {\n $contact = $this->getNewInstance()->crm()->contacts()->basicApi()->getById(\n $email,\n implode(',', $fields),\n null,\n false,\n 'email'\n );\n\n return [\n 'id' => $contact->getId(),\n 'properties' => $contact->getProperties(),\n ];\n } catch (ContactApiException $e) {\n $this->log->info('[Hubspot] Failed to fetch contact', [\n 'email' => $email,\n 'reason' => $e->getMessage(),\n ]);\n\n return [];\n }\n }\n\n /**\n * @throws CrmException\n */\n public function fetchProperty(string $objectType, string $propertyId): Property\n {\n $result = $this->getNewInstance()->crm()->properties()->coreApi()->getByName($objectType, $propertyId);\n\n if (! $result instanceof Property) {\n $this->log->error('[Hubspot] Failed to fetch property', [\n 'object_type' => $objectType,\n 'property_id' => $propertyId,\n 'reason' => $result->getMessage(),\n ]);\n\n throw new CrmException('Failed to fetch property');\n }\n\n return $result;\n }\n\n /**\n * @return array<CrmFieldOption>\n */\n public function fetchPropertyOptions(string $objectType, string $propertyId): array\n {\n /** @var array<CrmFieldOption> */\n return $this->fetchProperty($objectType, $propertyId)->getOptions();\n }\n\n /**\n * @return array<array{id:string, label:string, deleted:bool}>\n */\n public function fetchCallDispositions(): array\n {\n /** @var Response $response */\n $response = $this->getInstance()->engagements()->getCallDispositions();\n\n /**\n * @var array<array{\n * id:string,\n * label:string,\n * deleted: bool\n * }>\n */\n return $response->toArray();\n }\n\n /**\n * @return array<CrmFieldOption>\n */\n public function fetchOpportunityPipelineStages(): array\n {\n $stages = [];\n $apiResponse = $this->getNewInstance()->crm()->pipelines()->pipelinesApi()->getAll('deals');\n\n if ($apiResponse instanceof Error) {\n $this->log->error('[Hubspot] Failed to fetch opportunity pipelines', [\n 'reason' => $apiResponse->getMessage(),\n ]);\n\n return [];\n }\n\n foreach ($apiResponse->getResults() as $pipeline) {\n $pipelineStages = array_map(\n static function (PipelineStage $stage) {\n return [\n 'id' => $stage->getId(),\n 'label' => $stage->getLabel(),\n ];\n },\n $pipeline->getStages()\n );\n\n $stages = array_merge($stages, $pipelineStages);\n }\n\n return $stages;\n }\n\n public function fetchOpportunityPipelines(): array\n {\n $pipelines = [];\n\n try {\n $apiResponse = $this->makeRequest('/crm/v3/pipelines/deals');\n } catch (\\Exception $e) {\n $this->log->info('[Hubspot] Failed to fetch opportunity pipelines', [\n 'reason' => $e->getMessage(),\n ]);\n\n return [];\n }\n\n $response = $apiResponse->toArray();\n\n foreach ($response['results'] as $pipeline) {\n $pipelines[] = [\n 'id' => $pipeline['id'],\n 'label' => $pipeline['label'],\n ];\n }\n\n return $pipelines;\n }\n\n /**\n * @return array<CrmFieldOption>\n */\n public function fetchMeetingOutcomeFieldOptions(Field $field): array\n {\n return $field->getCrmProviderId() === 'meetingOutcome'\n ? $this->fetchMeetingOutcomeTypes()\n : $this->fetchCallActivityTypes();\n }\n\n public function fetchMeetingOutcomeTypes(): array\n {\n return $this->extractMeetingTypeOptions(\n 'https://api.hubapi.com/crm/v3/properties/meeting/hs_meeting_outcome'\n );\n }\n\n public function fetchCallActivityTypes(): array\n {\n return $this->extractMeetingTypeOptions(\n 'https://api.hubapi.com/crm/v3/properties/call/hs_activity_type'\n );\n }\n\n private function extractMeetingTypeOptions(string $endpoint): array\n {\n /** @var Response $response */\n $response = $this->getInstance()\n ->getClient()\n ->request('GET', $endpoint);\n\n /**\n * @var array<array{\n * value: string,\n * label: string,\n * displayOrder: int\n * }> $optionData\n */\n $optionData = $response->toArray()['options'] ?? [];\n\n $options = [];\n foreach ($optionData as $item) {\n $options[] = [\n 'id' => $item['value'],\n 'value' => $item['value'],\n 'label' => $item['label'],\n 'display_order' => $item['displayOrder'],\n ];\n }\n\n return $options;\n }\n\n /**\n * @return array<CrmFieldOption>\n */\n public function fetchDispositionFieldOptions(): array\n {\n $options = [];\n\n $dispositions = $this->fetchCallDispositions();\n\n foreach ($dispositions as $disposition) {\n if ($disposition['deleted'] !== false) {\n continue;\n }\n\n $option['value'] = $disposition['id'];\n $option['id'] = $disposition['id'];\n $option['label'] = $disposition['label'];\n\n $options[] = $option;\n }\n\n return $options;\n }\n\n /**\n * @return array<CrmFieldOption>\n */\n public function fetchOpportunityFieldOptions(Field $field): array\n {\n if ($field->isStageField()) {\n return $this->fetchOpportunityPipelineStages();\n }\n\n if ($field->isPipelineField()) {\n return $this->fetchOpportunityPipelines();\n }\n\n return $this->fetchPropertyOptions('deals', $field->getCrmProviderId());\n }\n\n /**\n * @throws BadRequest\n * @throws HubspotException\n */\n public function makeRequest(string $endpoint, $method = 'GET', $payload = [], ?string $queryString = null)\n {\n $endpoint = self::BASE_URL . $endpoint;\n\n if ($method === 'GET') {\n $response = $this->getInstance()->getClient()?->request(\n method: $method,\n endpoint: $endpoint,\n query_string: $queryString\n );\n } else {\n $response = $this->getInstance()->getClient()->request($method, $endpoint, [\n 'json' => ($payload),\n ]);\n }\n\n $max = $response->getHeaderLine('X-HubSpot-RateLimit-Max'); // \"110\"\n $remaining = $response->getHeaderLine('X-HubSpot-RateLimit-Remaining'); // \"109\"\n $interval = $response->getHeaderLine('X-HubSpot-RateLimit-Interval-Milliseconds'); // \"10000\"\n $body = json_decode((string) $response->getBody(), true);\n\n \\Illuminate\\Support\\Facades\\Log::channel('custom_channel')->info('$max ' . PHP_EOL . print_r($max, true));\n \\Illuminate\\Support\\Facades\\Log::channel('custom_channel')->info('$remaining ' . PHP_EOL . print_r($remaining, true));\n \\Illuminate\\Support\\Facades\\Log::channel('custom_channel')->info('$interval ' . PHP_EOL . print_r($interval, true));\n \\Illuminate\\Support\\Facades\\Log::channel('custom_channel')->info('$body ' . PHP_EOL . print_r($body, true));\n\n return $response;\n }\n\n /**\n * @throws BadRequest\n * @throws HubspotException\n */\n public function createMeeting(array $payload): Response\n {\n $endpoint = '/crm/v3/objects/meetings';\n\n return $this->makeRequest($endpoint, 'POST', $payload);\n }\n\n /**\n * @throws BadRequest\n * @throws HubspotException\n */\n public function updateMeeting(string $meetingId, array $payload): Response\n {\n $endpoint = '/crm/v3/objects/meetings/' . $meetingId;\n\n return $this->makeRequest($endpoint, 'PATCH', $payload);\n }\n\n /**\n * @throws \\Exception\n */\n public function createNote(\n string $body,\n string $ownerId,\n int $timestamp,\n string $objectId,\n NoteObject $noteObject\n ): ?string {\n try {\n $noteInput = new SimplePublicObjectInput([\n 'properties' => [\n 'hs_note_body' => $body,\n 'hubspot_owner_id' => $ownerId,\n 'hs_timestamp' => $timestamp,\n ],\n ]);\n\n // Create note\n $note = $this->getNewInstance()->crm()->objects()->basicApi()->create('note', $noteInput);\n\n $this->getNewInstance()->crm()->objects()->associationsApi()->create(\n 'note',\n $note->getId(),\n $this->getNoteObject($noteObject),\n $objectId,\n $this->getNoteAssociationType($noteObject),\n );\n\n return $note->getId();\n } catch (\\Exception $e) {\n $this->log->error('[Hubspot] Failed to create note', [\n 'objectId' => $objectId,\n 'noteObject' => $noteObject->getObjectType(),\n 'reason' => $e->getMessage(),\n ]);\n\n \\Sentry::captureException($e);\n }\n\n return null;\n }\n\n public function updateEngagement(string $objectId, array $engagement, array $metadata): void\n {\n $this->getInstance()->engagements()->update($objectId, $engagement, $metadata);\n }\n\n public function getEngagementData(string $engagementId): array\n {\n $engagement = $this->getInstance()->engagements()->get($engagementId);\n\n return $engagement->toArray();\n }\n\n public function createEngagement(array $engagement, array $associations, array $metadata): Response\n {\n return $this->getInstance()\n ->engagements()\n ->create($engagement, $associations, $metadata);\n }\n\n public function isUnauthorizedException(\\Exception $e): bool\n {\n // Check for specific HubSpot API exception types first\n if ($e instanceof BadRequest) {\n // BadRequest can contain 401 status codes\n return $e->getCode() === 401;\n }\n\n // Check for HTTP client exceptions with status codes\n if ($e instanceof \\GuzzleHttp\\Exception\\RequestException && $e->hasResponse()) {\n $response = $e->getResponse();\n if ($response !== null) {\n return $response->getStatusCode() === 401;\n }\n }\n\n // Check for Guzzle HTTP exceptions\n if ($e instanceof \\GuzzleHttp\\Exception\\ClientException) {\n return $e->getCode() === 401;\n }\n\n // Fallback to string matching as last resort, but be more specific\n $message = strtolower($e->getMessage());\n\n return str_contains($message, '401 unauthorized') ||\n str_contains($message, 'http 401') ||\n str_contains($message, 'status code 401') ||\n (preg_match('/\\b401\\b/', $message) && str_contains($message, 'unauthorized'));\n }\n\n /**\n * Validates and refreshes the access token if needed before API requests.\n * This ensures long-running processes don't fail due to token expiration.\n *\n * @throws SocialAccountTokenInvalidException\n */\n public function ensureValidToken(): void\n {\n if ($this->oauthAccount === null) {\n return;\n }\n\n $newToken = $this->tokenManager->ensureValidToken($this->oauthAccount);\n if ($newToken !== null) {\n $this->accessToken = $newToken;\n }\n }\n\n public function getConfig()\n {\n return $this->config;\n }\n\n // returns only active (archived=false)\n public function getOwners(): array\n {\n return $this->getNewInstance()->crm()->owners()->getAll();\n }\n\n /**\n * @param bool $archived\n *\n * @return array<Owner>|[]\n */\n public function getOwnersArchived(bool $archived = true): array\n {\n $endpoint = '/crm/v3/owners';\n $queryParams = [\n 'archived' => $archived ? 'true' : 'false',\n ];\n $queryString = http_build_query($queryParams);\n\n $owners = [];\n\n try {\n $response = $this->makeRequest(endpoint: $endpoint, queryString: $queryString);\n $responseData = $response?->toArray();\n\n foreach ($responseData['results'] as $result) {\n try {\n $owners[] = Owner::create($result);\n } catch (Throwable $e) {\n $this->log->error('[HubSpot] Failed to process owner data', [\n 'result' => $result,\n 'error' => $e->getMessage(),\n ]);\n\n continue;\n }\n }\n } catch (Throwable $e) {\n $this->log->error('HubSpot] Failed to fetch owners', [\n 'archived' => $archived,\n 'error' => $e->getMessage(),\n ]);\n\n return [];\n }\n\n return $owners;\n }\n\n public function getMeeting(string $engagementId): ObjectWithAssociations\n {\n return $this->getNewInstance()->crm()->objects()->basicApi()\n ->getById('meeting', $engagementId, null, 'contact,company,deal');\n }\n\n public function deleteEngagement(string $engagementId): void\n {\n $this->getInstance()->engagements()->delete((int) $engagementId);\n }\n\n public function getAssociationsData(array $ids, string $fromObject, string $toObject): array\n {\n $associationData = [];\n $idChunks = array_chunk($ids, self::ASSOCIATIONS_BATCH_SIZE_LIMIT);\n\n foreach ($idChunks as $idChunk) {\n try {\n $batchInput = new \\HubSpot\\Client\\Crm\\Associations\\Model\\BatchInputPublicObjectId();\n $batchInput->setInputs(array_map(function ($id) {\n $publicObjectId = new \\HubSpot\\Client\\Crm\\Associations\\Model\\PublicObjectId();\n $publicObjectId->setId($id);\n\n return $publicObjectId;\n }, $idChunk));\n\n $associatedObjectsData = $this\n ->getNewInstance()\n ->crm()\n ->associations()\n ->batchApi()\n ->read($fromObject, $toObject, $batchInput);\n\n if ($associatedObjectsData instanceof \\HubSpot\\Client\\Crm\\Associations\\Model\\BatchResponsePublicAssociationMulti) {\n foreach ($associatedObjectsData->getResults() as $association) {\n $from = $association->getFrom()->getId();\n $toAssociations = $association->getTo();\n\n if (! empty($toAssociations)) {\n $associationData[$from] = array_map(function ($item) {\n return $item->getId();\n }, $toAssociations);\n }\n }\n }\n } catch (\\Exception $e) {\n $this->log->error('[Hubspot] Failed to fetch associations', [\n 'from_object' => $fromObject,\n 'to_object' => $toObject,\n 'reason' => $e->getMessage(),\n ]);\n }\n }\n\n return $associationData;\n }\n\n /**\n * @throws \\Exception\n */\n private function getNoteAssociationType(NoteObject $noteObject): string\n {\n return match($noteObject) {\n NoteObject::Opportunity => 'note_to_deal',\n NoteObject::Lead, NoteObject::Contact => 'note_to_contact', // or 'note_to_lead' if your portal supports it\n NoteObject::Account => 'note_to_company',\n NoteObject::Call, NoteObject::Event => throw new \\Exception('Not supported'),\n };\n }\n\n /**\n * @throws \\Exception\n */\n private function getNoteObject(NoteObject $noteObject): string\n {\n return match($noteObject) {\n NoteObject::Opportunity => 'deal',\n NoteObject::Lead, NoteObject::Contact => 'contact',\n NoteObject::Account => 'company',\n NoteObject::Call, NoteObject::Event => throw new \\Exception('Not supported'),\n };\n }\n\n public function addAssociations(string $objectType, string $associationType, array $payload): Response\n {\n $endpoint = \"/crm/v4/associations/$objectType/$associationType/batch/create\";\n\n return $this->makeRequest($endpoint, 'POST', $payload);\n }\n\n public function removeAssociations(string $objectType, string $associationType, array $payload): Response\n {\n $endpoint = \"/crm/v4/associations/$objectType/$associationType/batch/archive\";\n\n return $this->makeRequest($endpoint, 'POST', $payload);\n }\n}","role_description":"text entry area","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"Project","depth":3,"on_screen":false,"role_description":"text"},{"role":"AXButton","text":"Project","depth":3,"bounds":{"left":0.011968086,"top":0.047885075,"width":0.024268618,"height":0.024740623},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"New File or Directory…","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.008643617,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Expand Selected","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.008643617,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Collapse All","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.008643617,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Options","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.008643617,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Hide","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.008643617,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"app ~/jiminny/app, folder","depth":6,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":".circleci, folder","depth":7,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":".cursor, folder","depth":7,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":".github","depth":7,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":".sonarlint, folder","depth":7,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":".vscode, folder","depth":7,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":".windsurf, folder","depth":7,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"app, sources root","depth":7,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Actions, folder","depth":8,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Component, folder","depth":8,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Acl, folder","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"ActionItems, folder","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Activity, folder","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"ActivityAnalytics, folder","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"ActivitySearch, folder","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"AiActivityType, folder","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"AiAutomation, folder","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"AiCallScoring, folder","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"AskAnything, folder","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Dtos, folder","depth":10,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Events, folder","depth":10,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"AskAnythingPromptService.php","depth":10,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"HistoryService.php","depth":10,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"AskJiminnyAi, folder","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"AWS, folder","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"BillingManagement, folder","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Cache, folder","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"CoachingFeedback, folder","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Country, folder","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"CustomerApi, folder","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Database, folder","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Datadog, folder","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"DateTime, folder","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"DealInsights, folder","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"DealRisks, folder","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"ElasticSearch","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Eloquent","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Encoding","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Encryption","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"ES","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Faker","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"FeatureFlags","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"FFMpeg","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"FileSystem","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Gecko","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Gong","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"GuzzleHttp","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"KeyPoints","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Kiosk","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"LanguageDetection","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"LiveFeed","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Locks","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Math","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"MediaPipeline","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"MeetingBot, folder","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"MobileSettings, folder","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Model, folder","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Notification, folder","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Nudge, folder","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"ParagraphBreaker, folder","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"ParticipantSpeech, folder","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"PartitionedCookie, folder","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"PlaybackPage, folder","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Playlist, folder","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Prophet, folder","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"ProphetAi, folder","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"ProsperWorks, folder","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Queue, folder","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Job, folder","depth":10,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"RateLimitAware.php, abstract class","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"RateLimitAwareWrapper.php, class","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"BotsQueueConstants.php","depth":10,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Constants.php","depth":10,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"ProcessingQueueConstants.php","depth":10,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Router, folder","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Saml2, folder","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"SCIM, folder","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Seeder, folder","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Sentry, folder","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Serializer, folder","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Settings, folder","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Sidekick, folder","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Slack, folder","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"TeamInsights, folder","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"TimeMemoryMapper, folder","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Transcription, folder","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"TranscriptionSummary, folder","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Twilio, folder","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Uploader, folder","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"UrlGenerator, folder","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Utility, folder","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Exceptions, folder","depth":10,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Service, folder","depth":10,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"BaseRateLimiter.php, class","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"EfficientJsonParser.php, class","depth":11,"on_screen":false,"role_description":"text"}]...
|
-6881570513140039651
|
-8151256403349000395
|
visual_change
|
accessibility
|
NULL
|
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
[2026-04-27 10:26:25] local.INFO: Jiminny\Console\Commands\Command::run Memory usage before starting command {"command":"mailbox:sync","memoryBeforeCommandInMb":60.0,"memoryPeakBeforeCommandInMb":99.727} {"correlation_id":"7b1ea2fb-8f36-4897-9970-91b4abba2cc7","trace_id":"265be7c0-bf8f-4037-a0d2-57df3ef1d7e1"}
[2026-04-27 10:26:25] local.INFO: [EmailSchedule] STARTING Inbox Sync {"host":"docker_lamp_1"} {"correlation_id":"7b1ea2fb-8f36-4897-9970-91b4abba2cc7","trace_id":"265be7c0-bf8f-4037-a0d2-57df3ef1d7e1"}
[2026-04-27 10:26:25] local.INFO: [EmailSchedule] FINISHED Inbox Sync {"host":"docker_lamp_1","events":2} {"correlation_id":"7b1ea2fb-8f36-4897-9970-91b4abba2cc7","trace_id":"265be7c0-bf8f-4037-a0d2-57df3ef1d7e1"}
[2026-04-27 10:26:25] local.INFO: Jiminny\Console\Commands\Command::run Memory usage for command {"command":"mailbox:sync","memoryBeforeCommandInMb":60.0,"memoryAfterCommandInMB":62.0,"memoryPeakBeforeCommandInMb":99.727,"memoryPeakAfterCommandInMB":99.727} {"correlation_id":"7b1ea2fb-8f36-4897-9970-91b4abba2cc7","trace_id":"265be7c0-bf8f-4037-a0d2-57df3ef1d7e1"}
[2026-04-27 10:26:26] local.INFO: [automated-reports] Started {"correlation_id":"d3f79034-397b-4936-9305-bdd99b23f2dd","trace_id":"aa0f8702-73eb-4947-a765-2e89244599c3"}
[2026-04-27 10:26:26] local.INFO: [automated-reports] Checking conditions {"isMonday":true,"isWeekend":false,"isFirstDayOfMonth":false,"currentMonth":4,"isQuarterlyMonth":true} {"correlation_id":"d3f79034-397b-4936-9305-bdd99b23f2dd","trace_id":"aa0f8702-73eb-4947-a765-2e89244599c3"}
[2026-04-27 10:26:26] local.INFO: [automated-reports] Processing daily reports {"correlation_id":"d3f79034-397b-4936-9305-bdd99b23f2dd","trace_id":"aa0f8702-73eb-4947-a765-2e89244599c3"}
[2026-04-27 10:26:26] local.INFO: [automated-reports] Found 1 daily reports to process {"correlation_id":"d3f79034-397b-4936-9305-bdd99b23f2dd","trace_id":"aa0f8702-73eb-4947-a765-2e89244599c3"}
[2026-04-27 10:26:26] local.INFO: [automated-reports] Dispatching Generate Report job for report {"reportUuid":"4f6ca2b5-1993-48aa-99ad-b66f19f15d43","teamId":1,"frequency":"weekly","type":"ask_jiminny"} {"correlation_id":"d3f79034-397b-4936-9305-bdd99b23f2dd","trace_id":"aa0f8702-73eb-4947-a765-2e89244599c3"}
[2026-04-27 10:26:26] local.INFO: [automated-reports] Processing weekly reports {"correlation_id":"d3f79034-397b-4936-9305-bdd99b23f2dd","trace_id":"aa0f8702-73eb-4947-a765-2e89244599c3"}
[2026-04-27 10:26:26] local.INFO: [automated-reports] Found 1 weekly reports to process {"correlation_id":"d3f79034-397b-4936-9305-bdd99b23f2dd","trace_id":"aa0f8702-73eb-4947-a765-2e89244599c3"}
[2026-04-27 10:26:26] local.INFO: [automated-reports] Dispatching Generate Report job for report {"reportUuid":"4f6ca2b5-1993-48aa-99ad-b66f19f15d43","teamId":1,"frequency":"weekly","type":"ask_jiminny"} {"correlation_id":"d3f79034-397b-4936-9305-bdd99b23f2dd","trace_id":"aa0f8702-73eb-4947-a765-2e89244599c3"}
[2026-04-27 10:26:26] local.INFO: [automated-reports] Completed {"correlation_id":"d3f79034-397b-4936-9305-bdd99b23f2dd","trace_id":"aa0f8702-73eb-4947-a765-2e89244599c3"}
[2026-04-27 10:26:26] local.INFO: [HubSpot Journal Polling] Getting offset from database {"offset":"","jiminny_team_id":1} {"correlation_id":"b453f0f4-0d2c-491c-a935-14c60265ec18","trace_id":"a50202d6-5601-46b4-a02e-cf5ac8d061fa"}
[2026-04-27 10:26:26] local.INFO: [HubSpot Journal API] Fetching latest journal entry {"url":"[URL_WITH_CREDENTIALS] {"correlation_id":"d3e3eb3d-5c83-4d6e-9592-aaa38c1843a4","trace_id":"265be7c0-bf8f-4037-a0d2-57df3ef1d7e1"}
[2026-04-27 10:26:28] local.INFO: [Gmail] imported 7 emails via full sync workflow for inbox 212 {"correlation_id":"d3e3eb3d-5c83-4d6e-9592-aaa38c1843a4","trace_id":"265be7c0-bf8f-4037-a0d2-57df3ef1d7e1"}
[2026-04-27 10:26:28] local.INFO: [Gmail] seeding inbox 212 with last message time : 2026-04-27 10:24:57 {"correlation_id":"d3e3eb3d-5c83-4d6e-9592-aaa38c1843a4","trace_id":"265be7c0-bf8f-4037-a0d2-57df3ef1d7e1"}
[2026-04-27 10:26:28] local.INFO: [Sync Mailbox] Sync complete {"inbox_id":212} {"correlation_id":"d3e3eb3d-5c83-4d6e-9592-aaa38c1843a4","trace_id":"265be7c0-bf8f-4037-a0d2-57df3ef1d7e1"}
[2026-04-27 10:27:04] local.INFO: Jiminny\Console\Commands\Command::run Memory usage before starting command {"command":"meeting-bot:schedule-bot","memoryBeforeCommandInMb":60.0,"memoryPeakBeforeCommandInMb":99.727} {"correlation_id":"f55fbf77-7e5f-4c87-accd-011697ca0fa3","trace_id":"9bd1c431-26de-4e31-83d8-3129ed9b3b79"}
[2026-04-27 10:27:04] local.INFO: [ScheduleBotCommand] Number of activities to be captured: 0 {"correlation_id":"f55fbf77-7e5f-4c87-accd-011697ca0fa3","trace_id":"9bd1c431-26de-4e31-83d8-3129ed9b3b79"}
[2026-04-27 10:27:04] local.INFO: Jiminny\Console\Commands\Command::run Memory usage for command {"command":"meeting-bot:schedule-bot","memoryBeforeCommandInMb":60.0,"memoryAfterCommandInMB":62.0,"memoryPeakBeforeCommandInMb":99.727,"memoryPeakAfterCommandInMB":99.727} {"correlation_id":"f55fbf77-7e5f-4c87-accd-011697ca0fa3","trace_id":"9bd1c431-26de-4e31-83d8-3129ed9b3b79"}
[2026-04-27 10:27:05] local.INFO: Jiminny\Console\Commands\Command::run Memory usage before starting command {"command":"dialers:monitor-activities","memoryBeforeCommandInMb":60.0,"memoryPeakBeforeCommandInMb":99.727} {"correlation_id":"da241053-5f3b-4d02-9504-4ef2987cdbd7","trace_id":"364d3fc7-0f11-40c6-8697-d18eff2c4cb6"}
[2026-04-27 10:27:05] local.INFO: Jiminny\Console\Commands\Command::run Memory usage for command {"command":"dialers:monitor-activities","memoryBeforeCommandInMb":60.0,"memoryAfterCommandInMB":62.0,"memoryPeakBeforeCommandInMb":99.727,"memoryPeakAfterCommandInMB":99.727} {"correlation_id":"da241053-5f3b-4d02-9504-4ef2987cdbd7","trace_id":"364d3fc7-0f11-40c6-8697-d18eff2c4cb6"}
[2026-04-27 10:27:06] local.NOTICE: Monitoring start {"correlation_id":"ad0d871a-a1fd-4725-b2f7-ac8f0fc1857e","trace_id":"cb504e84-d924-43a0-82d5-faf1840134ba"}
[2026-04-27 10:27:06] local.NOTICE: Monitoring end {"correlation_id":"ad0d871a-a1fd-4725-b2f7-ac8f0fc1857e","trace_id":"cb504e84-d924-43a0-82d5-faf1840134ba"}
[2026-04-27 10:27:08] local.INFO: Jiminny\Console\Commands\Command::run Memory usage before starting command {"command":"mailbox:skip-lists:refresh","memoryBeforeCommandInMb":60.0,"memoryPeakBeforeCommandInMb":99.727} {"correlation_id":"b8652f0d-1cee-44ae-a0ea-3d3137a882c1","trace_id":"7fe19940-0813-4199-a2a8-958de09bd0ee"}
[2026-04-27 10:27:08] local.INFO: Jiminny\Console\Commands\Command::run Memory usage for command {"command":"mailbox:skip-lists:refresh","memoryBeforeCommandInMb":60.0,"memoryAfterCommandInMB":62.0,"memoryPeakBeforeCommandInMb":99.727,"memoryPeakAfterCommandInMB":99.727} {"correlation_id":"b8652f0d-1cee-44ae-a0ea-3d3137a882c1","trace_id":"7fe19940-0813-4199-a2a8-958de09bd0ee"}
[2026-04-27 10:27:09] local.INFO: Jiminny\Console\Commands\Command::run Memory usage before starting command {"command":"mailbox:batch:process","memoryBeforeCommandInMb":60.0,"memoryPeakBeforeCommandInMb":99.727} {"correlation_id":"06190462-0f47-403f-8e95-ad5d05bff1fe","trace_id":"4aa6bed6-05a1-4661-a9c9-edadc61d84ac"}
[2026-04-27 10:27:09] local.INFO: [EmailSchedule] STARTING batch process {"host":"docker_lamp_1"} {"correlation_id":"06190462-0f47-403f-8e95-ad5d05bff1fe","trace_id":"4aa6bed6-05a1-4661-a9c9-edadc61d84ac"}
[2026-04-27 10:27:09] local.INFO: [EmailSchedule] FINISHED batch process {"host":"docker_lamp_1","processed":0} {"correlation_id":"06190462-0f47-403f-8e95-ad5d05bff1fe","trace_id":"4aa6bed6-05a1-4661-a9c9-edadc61d84ac"}
[2026-04-27 10:27:09] local.INFO: Jiminny\Console\Commands\Command::run Memory usage for command {"command":"mailbox:batch:process","memoryBeforeCommandInMb":60.0,"memoryAfterCommandInMB":62.0,"memoryPeakBeforeCommandInMb":99.727,"memoryPeakAfterCommandInMB":99.727} {"correlation_id":"06190462-0f47-403f-8e95-ad5d05bff1fe","trace_id":"4aa6bed6-05a1-4661-a9c9-edadc61d84ac"}
[2026-04-27 10:27:11] local.INFO: Jiminny\Console\Commands\Command::run Memory usage before starting command {"command":"mailbox:batch:create","memoryBeforeCommandInMb":60.0,"memoryPeakBeforeCommandInMb":99.727} {"correlation_id":"b561c5d5-e6e3-4091-9517-b0458190a360","trace_id":"bf9679fb-b785-40cb-b814-04a1dc385a1c"}
[2026-04-27 10:27:11] local.INFO: [EmailSchedule] STARTING batch create {"host":"docker_lamp_1"} {"correlation_id":"b561c5d5-e6e3-4091-9517-b0458190a360","trace_id":"bf9679fb-b785-40cb-b814-04a1dc385a1c"}
[2026-04-27 10:27:11] local.INFO: [EmailSchedule] FINISHED batch create {"host":"docker_lamp_1"} {"correlation_id":"b561c5d5-e6e3-4091-9517-b0458190a360","trace_id":"bf9679fb-b785-40cb-b814-04a1dc385a1c"}
[2026-04-27 10:27:11] local.INFO: Jiminny\Console\Commands\Command::run Memory usage for command {"command":"mailbox:batch:create","memoryBeforeCommandInMb":60.0,"memoryAfterCommandInMB":62.0,"memoryPeakBeforeCommandInMb":99.727,"memoryPeakAfterCommandInMB":99.727} {"correlation_id":"b561c5d5-e6e3-4091-9517-b0458190a360","trace_id":"bf9679fb-b785-40cb-b814-04a1dc385a1c"}
[2026-04-27 10:27:14] local.INFO: [Jiminny\Jobs\Mailbox\CreateBatches] processed 2 inboxes and created 1 batches {"userId":null,"batchSize":30,"maxBatches":1000} {"correlation_id":"98416296-43c2-4834-927e-4a0e86dc99cf","trace_id":"bf9679fb-b785-40cb-b814-04a1dc385a1c"}
[2026-04-27 10:28:04] local.INFO: Jiminny\Console\Commands\Command::run Memory usage before starting command {"command":"meeting-bot:schedule-bot","memoryBeforeCommandInMb":60.0,"memoryPeakBeforeCommandInMb":99.727} {"correlation_id":"5852bcf1-af92-4d24-8a2d-08a9d4a5cfac","trace_id":"25af5e8c-a148-493e-ac9f-4d53f1960177"}
[2026-04-27 10:28:04] local.INFO: [ScheduleBotCommand] Number of activities to be captured: 0 {"correlation_id":"5852bcf1-af92-4d24-8a2d-08a9d4a5cfac","trace_id":"25af5e8c-a148-493e-ac9f-4d53f1960177"}
[2026-04-27 10:28:04] local.INFO: Jiminny\Console\Commands\Command::run Memory usage for command {"command":"meeting-bot:schedule-bot","memoryBeforeCommandInMb":60.0,"memoryAfterCommandInMB":62.0,"memoryPeakBeforeCommandInMb":99.727,"memoryPeakAfterCommandInMB":99.727} {"correlation_id":"5852bcf1-af92-4d24-8a2d-08a9d4a5cfac","trace_id":"25af5e8c-a148-493e-ac9f-4d53f1960177"}
[2026-04-27 10:28:06] local.INFO: Jiminny\Console\Commands\Command::run Memory usage before starting command {"command":"dialers:monitor-activities","memoryBeforeCommandInMb":60.0,"memoryPeakBeforeCommandInMb":99.727} {"correlation_id":"cc1cff1f-71b4-4bb1-800f-66e27c0f2b3a","trace_id":"c03103a3-9ab8-480b-9753-7c7c6488f246"}
[2026-04-27 10:28:06] local.INFO: Jiminny\Console\Commands\Command::run Memory usage for command {"command":"dialers:monitor-activities","memoryBeforeCommandInMb":60.0,"memoryAfterCommandInMB":62.0,"memoryPeakBeforeCommandInMb":99.727,"memoryPeakAfterCommandInMB":99.727} {"correlation_id":"cc1cff1f-71b4-4bb1-800f-66e27c0f2b3a","trace_id":"c03103a3-9ab8-480b-9753-7c7c6488f246"}
[2026-04-27 10:28:09] local.ERROR: Target class [Jiminny\Repositories\AjReportsRepository] does not exist. {"exception":"[object] (Illuminate\\Contracts\\Container\\BindingResolutionException(code: 0): Target class [Jiminny\\Repositories\\AjReportsRepository] does not exist. at /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php:1124)
[stacktrace]
#0 /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php(933): Illuminate\\Container\\Container->build('Jiminny\\\\Reposit...')
#1 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Application.php(1078): Illuminate\\Container\\Container->resolve('Jiminny\\\\Reposit...', Array, true)
#2 /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php(864): Illuminate\\Foundation\\Application->resolve('Jiminny\\\\Reposit...', Array)
#3 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Application.php(1058): Illuminate\\Container\\Container->make('Jiminny\\\\Reposit...', Array)
#4 /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php(1336): Illuminate\\Foundation\\Application->make('Jiminny\\\\Reposit...')
#5 /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php(1237): Illuminate\\Container\\Container->resolveClass(Object(ReflectionParameter))
#6 /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php(1162): Illuminate\\Container\\Container->resolveDependencies(Array)
#7 /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php(933): Illuminate\\Container\\Container->build('Jiminny\\\\Http\\\\Co...')
#8 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Application.php(1078): Illuminate\\Container\\Container->resolve('Jiminny\\\\Http\\\\Co...', Array, true)
#9 /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php(864): Illuminate\\Foundation\\Application->resolve('Jiminny\\\\Http\\\\Co...', Array)
#10 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Application.php(1058): Illuminate\\Container\\Container->make('Jiminny\\\\Http\\\\Co...', Array)
#11 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Route.php(286): Illuminate\\Foundation\\Application->make('Jiminny\\\\Http\\\\Co...')
#12 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Route.php(1133): Illuminate\\Routing\\Route->getController()
#13 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Route.php(1062): Illuminate\\Routing\\Route->controllerMiddleware()
#14 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Router.php(834): Illuminate\\Routing\\Route->gatherMiddleware()
#15 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Router.php(816): Illuminate\\Routing\\Router->gatherRouteMiddleware(Object(Illuminate\\Routing\\Route))
#16 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Router.php(800): Illuminate\\Routing\\Router->runRouteWithinStack(Object(Illuminate\\Routing\\Route), Object(Illuminate\\Http\\Request))
#17 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Router.php(764): Illuminate\\Routing\\Router->runRoute(Object(Illuminate\\Http\\Request), Object(Illuminate\\Routing\\Route))
#18 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Router.php(753): Illuminate\\Routing\\Router->dispatchToRoute(Object(Illuminate\\Http\\Request))
#19 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Http/Kernel.php(200): Illuminate\\Routing\\Router->dispatch(Object(Illuminate\\Http\\Request))
#20 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(180): Illuminate\\Foundation\\Http\\Kernel->Illuminate\\Foundation\\Http\\{closure}(Object(Illuminate\\Http\\Request))
#21 /home/jiminny/vendor/barryvdh/laravel-debugbar/src/Middleware/InjectDebugbar.php(59): Illuminate\\Pipeline\\Pipeline->Illuminate\\Pipeline\\{closure}(Object(Illuminate\\Http\\Request))
#22 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(219): Barryvdh\\Debugbar\\Middleware\\InjectDebugbar->handle(Object(Illuminate\\Http\\Request), Object(Closure))
#23 /home/jiminny/vendor/laravel/framework/src/Illuminate/Http/Middleware/HandleCors.php(74): Illuminate\\Pipeline\\Pipeline->Illuminate\\Pipeline\\{closure}(Object(Illuminate\\Http\\Request))
#24 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(219): Illuminate\\Http\\Middleware\\HandleCors->handle(Object(Illuminate\\Http\\Request), Object(Closure))
#25 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Http/Middleware/TransformsRequest.php(21): Illuminate\\Pipeline\\Pipeline->Illuminate\\Pipeline\\{closure}(Object(Illuminate\\Http\\Request))
#26 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Http/Middleware/TrimStrings.php(51): Illuminate\\Foundation\\Http\\Middleware\\TransformsRequest->handle(Object(Illuminate\\Http\\Request), Object(Closure))
#27 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(219): Illuminate\\Foundation\\Http\\Middleware\\TrimStrings->handle(Object(Illuminate\\Http\\Request), Object(Closure))
#28 /home/jiminny/vendor/bepsvpt/secure-headers/src/SecureHeadersMiddleware.php(18): Illuminate\\Pipeline\\Pipeline->Illuminate\\Pipeline\\{closure}(Object(Illuminate\\Http\\Request))
#29 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(219): Bepsvpt\\SecureHeaders\\SecureHeadersMiddleware->handle(Object(Illuminate\\Http\\Request), Object(Closure))
#30 /home/jiminny/app/Http/Middleware/SentryContext.php(60): Illuminate\\Pipeline\\Pipeline->Illuminate\\Pipeline\\{closure}(Object(Illuminate\\Http\\Request))
#31 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(219): Jiminny\\Http\\Middleware\\SentryContext->handle(Object(Illuminate\\Http\\Request), Object(Closure))
#32 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Http/Middleware/PreventRequestsDuringMaintenance.php(109): Illuminate\\Pipeline\\Pipeline->Illuminate\\Pipeline\\{closure}(Object(Illuminate\\Http\\Request))
#33 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(219): Illuminate\\Foundation\\Http\\Middleware\\PreventRequestsDuringMaintenance->handle(Object(Illuminate\\Http\\Request), Object(Closure))
#34 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Http/Middleware/InvokeDeferredCallbacks.php(22): Illuminate\\Pipeline\\Pipeline->Illuminate\\Pipeline\\{closure}(Object(Illuminate\\Http\\Request))
#35 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(219): Illuminate\\Foundation\\Http\\Middleware\\InvokeDeferredCallbacks->handle(Object(Illuminate\\Http\\Request), Object(Closure))
#36 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(137): Illuminate\\Pipeline\\Pipeline->Illuminate\\Pipeline\\{closure}(Object(Illuminate\\Http\\Request))
#37 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Http/Kernel.php(175): Illuminate\\Pipeline\\Pipeline->then(Object(Closure))
#38 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Http/Kernel.php(144): Illuminate\\Foundation\\Http\\Kernel->sendRequestThroughRouter(Object(Illuminate\\Http\\Request))
#39 /home/jiminny/public/index.php(51): Illuminate\\Foundation\\Http\\Kernel->handle(Object(Illuminate\\Http\\Request))
#40 {main}
[previous exception] [object] (ReflectionException(code: -1): Class \"Jiminny\\Repositories\\AjReportsRepository\" does not exist at /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php:1122)
[stacktrace]
#0 /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php(1122): ReflectionClass->__construct('Jiminny\\\\Reposit...')
#1 /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php(933): Illuminate\\Container\\Container->build('Jiminny\\\\Reposit...')
#2 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Application.php(1078): Illuminate\\Container\\Container->resolve('Jiminny\\\\Reposit...', Array, true)
#3 /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php(864): Illuminate\\Foundation\\Application->resolve('Jiminny\\\\Reposit...', Array)
#4 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Application.php(1058): Illuminate\\Container\\Container->make('Jiminny\\\\Reposit...', Array)
#5 /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php(1336): Illuminate\\Foundation\\Application->make('Jiminny\\\\Reposit...')
#6 /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php(1237): Illuminate\\Container\\Container->resolveClass(Object(ReflectionParameter))
#7 /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php(1162): Illuminate\\Container\\Container->resolveDependencies(Array)
#8 /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php(933): Illuminate\\Container\\Container->build('Jiminny\\\\Http\\\\Co...')
#9 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Application.php(1078): Illuminate\\Container\\Container->resolve('Jiminny\\\\Http\\\\Co...', Array, true)
#10 /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php(864): Illuminate\\Foundation\\Application->resolve('Jiminny\\\\Http\\\\Co...', Array)
#11 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Application.php(1058): Illuminate\\Container\\Container->make('Jiminny\\\\Http\\\\Co...', Array)
#12 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Route.php(286): Illuminate\\Foundation\\Application->make('Jiminny\\\\Http\\\\Co...')
#13 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Route.php(1133): Illuminate\\Routing\\Route->getController()
#14 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Route.php(1062): Illuminate\\Routing\\Route->controllerMiddleware()
#15 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Router.php(834): Illuminate\\Routing\\Route->gatherMiddleware()
#16 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Router.php(816): Illuminate\\Routing\\Router->gatherRouteMiddleware(Object(Illuminate\\Routing\\Route))
#17 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Router.php(800): Illuminate\\Routing\\Router->runRouteWithinStack(Object(Illuminate\\Routing\\Route), Object(Illuminate\\Http\\Request))
#18 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Router.php(764): Illuminate\\Routing\\Router->runRoute(Object(Illuminate\\Http\\Request), Object(Illuminate\\Routing\\Route))
#19 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Router.php(753): Illuminate\\Routing\\Router->dispatchToRoute(Object(Illuminate\\Http\\Request))
#20 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Http/Kernel.php(200): Illuminate\\Routing\\Router->dispatch(Object(Illuminate\\Http\\Request))
#21 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(180): Illuminate\\Foundation\\Http\\Kernel->Illuminate\\Foundation\\Http\\{closure}(Object(Illuminate\\Http\\Request))
#22 /home/jiminny/vendor/barryvdh/laravel-debugbar/src/Middleware/InjectDebugbar.php(59): Illuminate\\Pipeline\\Pipeline->Illuminate\\Pipeline\\{closure}(Object(Illuminate\\Http\\Request))
#23 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(219): Barryvdh\\Debugbar\\Middleware\\InjectDebugbar->handle(Object(Illuminate\\Http\\Request), Object(Closure))
#24 /home/jiminny/vendor/laravel/framework/src/Illuminate/Http/Middleware/HandleCors.php(74): Illuminate\\Pipeline\\Pipeline->Illuminate\\Pipeline\\{closure}(Object(Illuminate\\Http\\Request))
#25 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(219): Illuminate\\Http\\Middleware\\HandleCors->handle(Object(Illuminate\\Http\\Request), Object(Closure))
#26 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Http/Middleware/TransformsRequest.php(21): Illuminate\\Pipeline\\Pipeline->Illuminate\\Pipeline\\{closure}(Object(Illuminate\\Http\\Request))
#27 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Http/Middleware/TrimStrings.php(51): Illuminate\\Foundation\\Http\\Middleware\\TransformsRequest->handle(Object(Illuminate\\Http\\Request), Object(Closure))
#28 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(219): Illuminate\\Foundation\\Http\\Middleware\\TrimStrings->handle(Object(Illuminate\\Http\\Request), Object(Closure))
#29 /home/jiminny/vendor/bepsvpt/secure-headers/src/SecureHeadersMiddleware.php(18): Illuminate\\Pipeline\\Pipeline->Illuminate\\Pipeline\\{closure}(Object(Illuminate\\Http\\Request))
#30 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(219): Bepsvpt\\SecureHeaders\\SecureHeadersMiddleware->handle(Object(Illuminate\\Http\\Request), Object(Closure))
#31 /home/jiminny/app/Http/Middleware/SentryContext.php(60): Illuminate\\Pipeline\\Pipeline->Illuminate\\Pipeline\\{closure}(Object(Illuminate\\Http\\Request))
#32 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(219): Jiminny\\Http\\Middleware\\SentryContext->handle(Object(Illuminate\\Http\\Request), Object(Closure))
#33 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Http/Middleware/PreventRequestsDuringMaintenance.php(109): Illuminate\\Pipeline\\Pipeline->Illuminate\\Pipeline\\{closure}(Object(Illuminate\\Http\\Request))
#34 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(219): Illuminate\\Foundation\\Http\\Middleware\\PreventRequestsDuringMaintenance->handle(Object(Illuminate\\Http\\Request), Object(Closure))
#35 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Http/Middleware/InvokeDeferredCallbacks.php(22): Illuminate\\Pipeline\\Pipeline->Illuminate\\Pipeline\\{closure}(Object(Illuminate\\Http\\Request))
#36 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(219): Illuminate\\Foundation\\Http\\Middleware\\InvokeDeferredCallbacks->handle(Object(Illuminate\\Http\\Request), Object(Closure))
#37 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(137): Illuminate\\Pipeline\\Pipeline->Illuminate\\Pipeline\\{closure}(Object(Illuminate\\Http\\Request))
#38 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Http/Kernel.php(175): Illuminate\\Pipeline\\Pipeline->then(Object(Closure))
#39 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Http/Kernel.php(144): Illuminate\\Foundation\\Http\\Kernel->sendRequestThroughRouter(Object(Illuminate\\Http\\Request))
#40 /home/jiminny/public/index.php(51): Illuminate\\Foundation\\Http\\Kernel->handle(Object(Illuminate\\Http\\Request))
#41 {main}
"} {"correlation_id":"dfad8817-1dde-4622-8320-f5fc1616a8fe","trace_id":"d21232b2-60fb-4ca0-838c-ba42e61593f9"}
[2026-04-27 10:28:09] local.NOTICE: Monitoring start {"correlation_id":"93744c67-4444-41ff-aaad-6aa9cafe294e","trace_id":"80e396b0-b1ac-4ec8-838c-00021f98072c"}
[2026-04-27 10:28:09] local.NOTICE: Monitoring end {"correlation_id":"93744c67-4444-41ff-aaad-6aa9cafe294e","trace_id":"80e396b0-b1ac-4ec8-838c-00021f98072c"}
[2026-04-27 10:28:10] local.ERROR: Target class [Jiminny\Repositories\AjReportsRepository] does not exist. {"exception":"[object] (Illuminate\\Contracts\\Container\\BindingResolutionException(code: 0): Target class [Jiminny\\Repositories\\AjReportsRepository] does not exist. at /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php:1124)
[stacktrace]
#0 /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php(933): Illuminate\\Container\\Container->build('Jiminny\\\\Reposit...')
#1 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Application.php(1078): Illuminate\\Container\\Container->resolve('Jiminny\\\\Reposit...', Array, true)
#2 /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php(864): Illuminate\\Foundation\\Application->resolve('Jiminny\\\\Reposit...', Array)
#3 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Application.php(1058): Illuminate\\Container\\Container->make('Jiminny\\\\Reposit...', Array)
#4 /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php(1336): Illuminate\\Foundation\\Application->make('Jiminny\\\\Reposit...')
#5 /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php(1237): Illuminate\\Container\\Container->resolveClass(Object(ReflectionParameter))
#6 /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php(1162): Illuminate\\Container\\Container->resolveDependencies(Array)
#7 /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php(933): Illuminate\\Container\\Container->build('Jiminny\\\\Http\\\\Co...')
#8 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Application.php(1078): Illuminate\\Container\\Container->resolve('Jiminny\\\\Http\\\\Co...', Array, true)
#9 /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php(864): Illuminate\\Foundation\\Application->resolve('Jiminny\\\\Http\\\\Co...', Array)
#10 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Application.php(1058): Illuminate\\Container\\Container->make('Jiminny\\\\Http\\\\Co...', Array)
#11 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Route.php(286): Illuminate\\Foundation\\Application->make('Jiminny\\\\Http\\\\Co...')
#12 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Route.php(1133): Illuminate\\Routing\\Route->getController()
#13 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Route.php(1062): Illuminate\\Routing\\Route->controllerMiddleware()
#14 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Router.php(834): Illuminate\\Routing\\Route->gatherMiddleware()
#15 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Router.php(816): Illuminate\\Routing\\Router->gatherRouteMiddleware(Object(Illuminate\\Routing\\Route))
#16 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Router.php(800): Illuminate\\Routing\\Router->runRouteWithinStack(Object(Illuminate\\Routing\\Route), Object(Illuminate\\Http\\Request))
#17 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Router.php(764): Illuminate\\Routing\\Router->runRoute(Object(Illuminate\\Http\\Request), Object(Illuminate\\Routing\\Route))
#18 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Router.php(753): Illuminate\\Routing\\Router->dispatchToRoute(Object(Illuminate\\Http\\Request))
#19 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Http/Kernel.php(200): Illuminate\\Routing\\Router->dispatch(Object(Illuminate\\Http\\Request))
#20 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(180): Illuminate\\Foundation\\Http\\Kernel->Illuminate\\Foundation\\Http\\{closure}(Object(Illuminate\\Http\\Request))
#21 /home/jiminny/vendor/barryvdh/laravel-debugbar/src/Middleware/InjectDebugbar.php(59): Illuminate\\Pipeline\\Pipeline->Illuminate\\Pipeline\\{closure}(Object(Illuminate\\Http\\Request))
#22 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(219): Barryvdh\\Debugbar\\Middleware\\InjectDebugbar->handle(Object(Illuminate\\Http\\Request), Object(Closure))
#23 /home/jiminny/vendor/laravel/framework/src/Illuminate/Http/Middleware/HandleCors.php(74): Illuminate\\Pipeline\\Pipeline->Illuminate\\Pipeline\\{closure}(Object(Illuminate\\Http\\Request))
#24 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(219): Illuminate\\Http\\Middleware\\HandleCors->handle(Object(Illuminate\\Http\\Request), Object(Closure))
#25 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Http/Middleware/TransformsRequest.php(21): Illuminate\\Pipeline\\Pipeline->Illuminate\\Pipeline\\{closure}(Object(Illuminate\\Http\\Request))
#26 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Http/Middleware/TrimStrings.php(51): Illuminate\\Foundation\\Http\\Middleware\\TransformsRequest->handle(Object(Illuminate\\Http\\Request), Object(Closure))
#27 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(219): Illuminate\\Foundation\\Http\\Middleware\\TrimStrings->handle(Object(Illuminate\\Http\\Request), Object(Closure))
#28 /home/jiminny/vendor/bepsvpt/secure-headers/src/SecureHeadersMiddleware.php(18): Illuminate\\Pipeline\\Pipeline->Illuminate\\Pipeline\\{closure}(Object(Illuminate\\Http\\Request))
#29 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(219): Bepsvpt\\SecureHeaders\\SecureHeadersMiddleware->handle(Object(Illuminate\\Http\\Request), Object(Closure))
#30 /home/jiminny/app/Http/Middleware/SentryContext.php(60): Illuminate\\Pipeline\\Pipeline->Illuminate\\Pipeline\\{closure}(Object(Illuminate\\Http\\Request))
#31 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(219): Jiminny\\Http\\Middleware\\SentryContext->handle(Object(Illuminate\\Http\\Request), Object(Closure))
#32 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Http/Middleware/PreventRequestsDuringMaintenance.php(109): Illuminate\\Pipeline\\Pipeline->Illuminate\\Pipeline\\{closure}(Object(Illuminate\\Http\\Request))
#33 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(219): Illuminate\\Foundation\\Http\\Middleware\\PreventRequestsDuringMaintenance->handle(Object(Illuminate\\Http\\Request), Object(Closure))
#34 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Http/Middleware/InvokeDeferredCallbacks.php(22): Illuminate\\Pipeline\\Pipeline->Illuminate\\Pipeline\\{closure}(Object(Illuminate\\Http\\Request))
#35 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(219): Illuminate\\Foundation\\Http\\Middleware\\InvokeDeferredCallbacks->handle(Object(Illuminate\\Http\\Request), Object(Closure))
#36 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(137): Illuminate\\Pipeline\\Pipeline->Illuminate\\Pipeline\\{closure}(Object(Illuminate\\Http\\Request))
#37 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Http/Kernel.php(175): Illuminate\\Pipeline\\Pipeline->then(Object(Closure))
#38 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Http/Kernel.php(144): Illuminate\\Foundation\\Http\\Kernel->sendRequestThroughRouter(Object(Illuminate\\Http\\Request))
#39 /home/jiminny/public/index.php(51): Illuminate\\Foundation\\Http\\Kernel->handle(Object(Illuminate\\Http\\Request))
#40 {main}
[previous exception] [object] (ReflectionException(code: -1): Class \"Jiminny\\Repositories\\AjReportsRepository\" does not exist at /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php:1122)
[stacktrace]
#0 /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php(1122): ReflectionClass->__construct('Jiminny\\\\Reposit...')
#1 /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php(933): Illuminate\\Container\\Container->build('Jiminny\\\\Reposit...')
#2 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Application.php(1078): Illuminate\\Container\\Container->resolve('Jiminny\\\\Reposit...', Array, true)
#3 /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php(864): Illuminate\\Foundation\\Application->resolve('Jiminny\\\\Reposit...', Array)
#4 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Application.php(1058): Illuminate\\Container\\Container->make('Jiminny\\\\Reposit...', Array)
#5 /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php(1336): Illuminate\\Foundation\\Application->make('Jiminny\\\\Reposit...')
#6 /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php(1237): Illuminate\\Container\\Container->resolveClass(Object(ReflectionParameter))
#7 /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php(1162): Illuminate\\Container\\Container->resolveDependencies(Array)
#8 /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php(933): Illuminate\\Container\\Container->build('Jiminny\\\\Http\\\\Co...')
#9 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Application.php(1078): Illuminate\\Container\\Container->resolve('Jiminny\\\\Http\\\\Co...', Array, true)
#10 /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php(864): Illuminate\\Foundation\\Application->resolve('Jiminny\\\\Http\\\\Co...', Array)
#11 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Application.php(1058): Illuminate\\Container\\Container->make('Jiminny\\\\Http\\\\Co...', Array)
#12 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Route.php(286): Illuminate\\Foundation\\Application->make('Jiminny\\\\Http\\\\Co...')
#13 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Route.php(1133): Illuminate\\Routing\\Route->getController()
#14 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Route.php(1062): Illuminate\\Routing\\Route->controllerMiddleware()
#15 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Router.php(834): Illuminate\\Routing\\Route->gatherMiddleware()
#16 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Router.php(816): Illuminate\\Routing\\Router->gatherRouteMiddleware(Object(Illuminate\\Routing\\Route))
#17 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Router.php(800): Illuminate\\Routing\\Router->runRouteWithinStack(Object(Illuminate\\Routing\\Route), Object(Illuminate\\Http\\Request))
#18 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Router.php(764): Illuminate\\Routing\\Router->runRoute(Object(Illuminate\\Http\\Request), Object(Illuminate\\Routing\\Route))
#19 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Router.php(753): Illuminate\\Routing\\Router->dispatchToRoute(Object(Illuminate\\Http\\Request))
#20 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Http/Kernel.php(200): Illuminate\\Routing\\Router->dispatch(Object(Illuminate\\Http\\Request))
#21 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(180): Illuminate\\Foundation\\Http\\Kernel->Illuminate\\Foundation\\Http\\{closure}(Object(Illuminate\\Http\\Request))
#22 /home/jiminny/vendor/barryvdh/laravel-debugbar/src/Middleware/InjectDebugbar.php(59): Illuminate\\Pipeline\\Pipeline->Illuminate\\Pipeline\\{closure}(Object(Illuminate\\Http\\Request))
#23 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(219): Barryvdh\\Debugbar\\Middleware\\InjectDebugbar->handle(Object(Illuminate\\Http\\Request), Object(Closure))
#24 /home/jiminny/vendor/laravel/framework/src/Illuminate/Http/Middleware/HandleCors.php(74): Illuminate\\Pipeline\\Pipeline->Illuminate\\Pipeline\\{closure}(Object(Illuminate\\Http\\Request))
#25 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(219): Illuminate\\Http\\Middleware\\HandleCors->handle(Object(Illuminate\\Http\\Request), Object(Closure))
#26 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Http/Middleware/TransformsRequest.php(21): Illuminate\\Pipeline\\Pipeline->Illuminate\\Pipeline\\{closure}(Object(Illuminate\\Http\\Request))
#27 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Http/Middleware/TrimStrings.php(51): Illuminate\\Foundation\\Http\\Middleware\\TransformsRequest->handle(Object(Illuminate\\Http\\Request), Object(Closure))
#28 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(219): Illuminate\\Foundation\\Http\\Middleware\\TrimStrings->handle(Object(Illuminate\\Http\\Request), Object(Closure))
#29 /home/jiminny/vendor/bepsvpt/secure-headers/src/SecureHeadersMiddleware.php(18): Illuminate\\Pipeline\\Pipeline->Illuminate\\Pipeline\\{closure}(Object(Illuminate\\Http\\Request))
#30 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(219): Bepsvpt\\SecureHeaders\\SecureHeadersMiddleware->handle(Object(Illuminate\\Http\\Request), Object(Closure))
#31 /home/jiminny/app/Http/Middleware/SentryContext.php(60): Illuminate\\Pipeline\\Pipeline->Illuminate\\Pipeline\\{closure}(Object(Illuminate\\Http\\Request))
#32 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(219): Jiminny\\Http\\Middleware\\SentryContext->handle(Object(Illuminate\\Http\\Request), Object(Closure))
#33 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Http/Middleware/PreventRequestsDuringMaintenance.php(109): Illuminate\\Pipeline\\Pipeline->Illuminate\\Pipeline\\{closure}(Object(Illuminate\\Http\\Request))
#34 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(219): Illuminate\\Foundation\\Http\\Middleware\\PreventRequestsDuringMaintenance->handle(Object(Illuminate\\Http\\Request), Object(Closure))
#35 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Http/Middleware/InvokeDeferredCallbacks.php(22): Illuminate\\Pipeline\\Pipeline->Illuminate\\Pipeline\\{closure}(Object(Illuminate\\Http\\Request))
#36 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(219): Illuminate\\Foundation\\Http\\Middleware\\InvokeDeferredCallbacks->handle(Object(Illuminate\\Http\\Request), Object(Closure))
#37 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(137): Illuminate\\Pipeline\\Pipeline->Illuminate\\Pipeline\\{closure}(Object(Illuminate\\Http\\Request))
#38 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Http/Kernel.php(175): Illuminate\\Pipeline\\Pipeline->then(Object(Closure))
#39 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Http/Kernel.php(144): Illuminate\\Foundation\\Http\\Kernel->sendRequestThroughRouter(Object(Illuminate\\Http\\Request))
#40 /home/jiminny/public/index.php(51): Illuminate\\Foundation\\Http\\Kernel->handle(Object(Illuminate\\Http\\Request))
#41 {main}
"} {"correlation_id":"de768781-5c05-418c-baf3-79f77a8c8694","trace_id":"fec74062-3f6d-4cfc-968a-1bb285496db3"}
[2026-04-27 10:28:12] local.INFO: Jiminny\Console\Commands\Command::run Memory usage before starting command {"command":"mailbox:skip-lists:refresh","memoryBeforeCommandInMb":60.0,"memoryPeakBeforeCommandInMb":99.727} {"correlation_id":"907d15ed-8c84-41a1-a473-590eebb55dbc","trace_id":"ec7bd353-516e-491d-89dd-de44136c1672"}
[2026-04-27 10:28:12] local.INFO: Jiminny\Console\Commands\Command::run Memory usage for command {"command":"mailbox:skip-lists:refresh","memoryBeforeCommandInMb":60.0,"memoryAfterCommandInMB":62.0,"memoryPeakBeforeCommandInMb":99.727,"memoryPeakAfterCommandInMB":99.727} {"correlation_id":"907d15ed-8c84-41a1-a473-590eebb55dbc","trace_id":"ec7bd353-516e-491d-89dd-de44136c1672"}
[2026-04-27 10:28:13] local.ERROR: Target class [Jiminny\Repositories\AjReportsRepository] does not exist. {"exception":"[object] (Illuminate\\Contracts\\Container\\BindingResolutionException(code: 0): Target class [Jiminny\\Repositories\\AjReportsRepository] does not exist. at /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php:1124)
[stacktrace]
#0 /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php(933): Illuminate\\Container\\Container->build('Jiminny\\\\Reposit...')
#1 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Application.php(1078): Illuminate\\Container\\Container->resolve('Jiminny\\\\Reposit...', Array, true)
#2 /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php(864): Illuminate\\Foundation\\Application->resolve('Jiminny\\\\Reposit...', Array)
#3 /home/jiminny/vendor/laravel/framewo...
|
2698
|
NULL
|
NULL
|
NULL
|
|
2701
|
112
|
23
|
2026-05-07T11:36:12.309254+00:00
|
/Users/lukas/.screenpipe/data/data/2026-05-07/1778 /Users/lukas/.screenpipe/data/data/2026-05-07/1778153772309_m2.jpg...
|
PhpStorm
|
faVsco.js – laravel.log
|
True
|
NULL
|
monitor_2
|
NULL
|
NULL
|
NULL
|
NULL
|
This view is read-only
text/html
text/html
text/ht This view is read-only
text/html
text/html
text/html
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
[2026-04-27 10:26:25] local.INFO: Jiminny\Console\Commands\Command::run Memory usage before starting command {"command":"mailbox:sync","memoryBeforeCommandInMb":60.0,"memoryPeakBeforeCommandInMb":99.727} {"correlation_id":"7b1ea2fb-8f36-4897-9970-91b4abba2cc7","trace_id":"265be7c0-bf8f-4037-a0d2-57df3ef1d7e1"}
[2026-04-27 10:26:25] local.INFO: [EmailSchedule] STARTING Inbox Sync {"host":"docker_lamp_1"} {"correlation_id":"7b1ea2fb-8f36-4897-9970-91b4abba2cc7","trace_id":"265be7c0-bf8f-4037-a0d2-57df3ef1d7e1"}
[2026-04-27 10:26:25] local.INFO: [EmailSchedule] FINISHED Inbox Sync {"host":"docker_lamp_1","events":2} {"correlation_id":"7b1ea2fb-8f36-4897-9970-91b4abba2cc7","trace_id":"265be7c0-bf8f-4037-a0d2-57df3ef1d7e1"}
[2026-04-27 10:26:25] local.INFO: Jiminny\Console\Commands\Command::run Memory usage for command {"command":"mailbox:sync","memoryBeforeCommandInMb":60.0,"memoryAfterCommandInMB":62.0,"memoryPeakBeforeCommandInMb":99.727,"memoryPeakAfterCommandInMB":99.727} {"correlation_id":"7b1ea2fb-8f36-4897-9970-91b4abba2cc7","trace_id":"265be7c0-bf8f-4037-a0d2-57df3ef1d7e1"}
[2026-04-27 10:26:26] local.INFO: [automated-reports] Started {"correlation_id":"d3f79034-397b-4936-9305-bdd99b23f2dd","trace_id":"aa0f8702-73eb-4947-a765-2e89244599c3"}
[2026-04-27 10:26:26] local.INFO: [automated-reports] Checking conditions {"isMonday":true,"isWeekend":false,"isFirstDayOfMonth":false,"currentMonth":4,"isQuarterlyMonth":true} {"correlation_id":"d3f79034-397b-4936-9305-bdd99b23f2dd","trace_id":"aa0f8702-73eb-4947-a765-2e89244599c3"}
[2026-04-27 10:26:26] local.INFO: [automated-reports] Processing daily reports {"correlation_id":"d3f79034-397b-4936-9305-bdd99b23f2dd","trace_id":"aa0f8702-73eb-4947-a765-2e89244599c3"}
[2026-04-27 10:26:26] local.INFO: [automated-reports] Found 1 daily reports to process {"correlation_id":"d3f79034-397b-4936-9305-bdd99b23f2dd","trace_id":"aa0f8702-73eb-4947-a765-2e89244599c3"}
[2026-04-27 10:26:26] local.INFO: [automated-reports] Dispatching Generate Report job for report {"reportUuid":"4f6ca2b5-1993-48aa-99ad-b66f19f15d43","teamId":1,"frequency":"weekly","type":"ask_jiminny"} {"correlation_id":"d3f79034-397b-4936-9305-bdd99b23f2dd","trace_id":"aa0f8702-73eb-4947-a765-2e89244599c3"}
[2026-04-27 10:26:26] local.INFO: [automated-reports] Processing weekly reports {"correlation_id":"d3f79034-397b-4936-9305-bdd99b23f2dd","trace_id":"aa0f8702-73eb-4947-a765-2e89244599c3"}
[2026-04-27 10:26:26] local.INFO: [automated-reports] Found 1 weekly reports to process {"correlation_id":"d3f79034-397b-4936-9305-bdd99b23f2dd","trace_id":"aa0f8702-73eb-4947-a765-2e89244599c3"}
[2026-04-27 10:26:26] local.INFO: [automated-reports] Dispatching Generate Report job for report {"reportUuid":"4f6ca2b5-1993-48aa-99ad-b66f19f15d43","teamId":1,"frequency":"weekly","type":"ask_jiminny"} {"correlation_id":"d3f79034-397b-4936-9305-bdd99b23f2dd","trace_id":"aa0f8702-73eb-4947-a765-2e89244599c3"}
[2026-04-27 10:26:26] local.INFO: [automated-reports] Completed {"correlation_id":"d3f79034-397b-4936-9305-bdd99b23f2dd","trace_id":"aa0f8702-73eb-4947-a765-2e89244599c3"}
[2026-04-27 10:26:26] local.INFO: [HubSpot Journal Polling] Getting offset from database {"offset":"","jiminny_team_id":1} {"correlation_id":"b453f0f4-0d2c-491c-a935-14c60265ec18","trace_id":"a50202d6-5601-46b4-a02e-cf5ac8d061fa"}
[2026-04-27 10:26:26] local.INFO: [HubSpot Journal API] Fetching latest journal entry {"url":"[URL_WITH_CREDENTIALS] {"correlation_id":"d3e3eb3d-5c83-4d6e-9592-aaa38c1843a4","trace_id":"265be7c0-bf8f-4037-a0d2-57df3ef1d7e1"}
[2026-04-27 10:26:28] local.INFO: [Gmail] imported 7 emails via full sync workflow for inbox 212 {"correlation_id":"d3e3eb3d-5c83-4d6e-9592-aaa38c1843a4","trace_id":"265be7c0-bf8f-4037-a0d2-57df3ef1d7e1"}
[2026-04-27 10:26:28] local.INFO: [Gmail] seeding inbox 212 with last message time : 2026-04-27 10:24:57 {"correlation_id":"d3e3eb3d-5c83-4d6e-9592-aaa38c1843a4","trace_id":"265be7c0-bf8f-4037-a0d2-57df3ef1d7e1"}
[2026-04-27 10:26:28] local.INFO: [Sync Mailbox] Sync complete {"inbox_id":212} {"correlation_id":"d3e3eb3d-5c83-4d6e-9592-aaa38c1843a4","trace_id":"265be7c0-bf8f-4037-a0d2-57df3ef1d7e1"}
[2026-04-27 10:27:04] local.INFO: Jiminny\Console\Commands\Command::run Memory usage before starting command {"command":"meeting-bot:schedule-bot","memoryBeforeCommandInMb":60.0,"memoryPeakBeforeCommandInMb":99.727} {"correlation_id":"f55fbf77-7e5f-4c87-accd-011697ca0fa3","trace_id":"9bd1c431-26de-4e31-83d8-3129ed9b3b79"}
[2026-04-27 10:27:04] local.INFO: [ScheduleBotCommand] Number of activities to be captured: 0 {"correlation_id":"f55fbf77-7e5f-4c87-accd-011697ca0fa3","trace_id":"9bd1c431-26de-4e31-83d8-3129ed9b3b79"}
[2026-04-27 10:27:04] local.INFO: Jiminny\Console\Commands\Command::run Memory usage for command {"command":"meeting-bot:schedule-bot","memoryBeforeCommandInMb":60.0,"memoryAfterCommandInMB":62.0,"memoryPeakBeforeCommandInMb":99.727,"memoryPeakAfterCommandInMB":99.727} {"correlation_id":"f55fbf77-7e5f-4c87-accd-011697ca0fa3","trace_id":"9bd1c431-26de-4e31-83d8-3129ed9b3b79"}
[2026-04-27 10:27:05] local.INFO: Jiminny\Console\Commands\Command::run Memory usage before starting command {"command":"dialers:monitor-activities","memoryBeforeCommandInMb":60.0,"memoryPeakBeforeCommandInMb":99.727} {"correlation_id":"da241053-5f3b-4d02-9504-4ef2987cdbd7","trace_id":"364d3fc7-0f11-40c6-8697-d18eff2c4cb6"}
[2026-04-27 10:27:05] local.INFO: Jiminny\Console\Commands\Command::run Memory usage for command {"command":"dialers:monitor-activities","memoryBeforeCommandInMb":60.0,"memoryAfterCommandInMB":62.0,"memoryPeakBeforeCommandInMb":99.727,"memoryPeakAfterCommandInMB":99.727} {"correlation_id":"da241053-5f3b-4d02-9504-4ef2987cdbd7","trace_id":"364d3fc7-0f11-40c6-8697-d18eff2c4cb6"}
[2026-04-27 10:27:06] local.NOTICE: Monitoring start {"correlation_id":"ad0d871a-a1fd-4725-b2f7-ac8f0fc1857e","trace_id":"cb504e84-d924-43a0-82d5-faf1840134ba"}
[2026-04-27 10:27:06] local.NOTICE: Monitoring end {"correlation_id":"ad0d871a-a1fd-4725-b2f7-ac8f0fc1857e","trace_id":"cb504e84-d924-43a0-82d5-faf1840134ba"}
[2026-04-27 10:27:08] local.INFO: Jiminny\Console\Commands\Command::run Memory usage before starting command {"command":"mailbox:skip-lists:refresh","memoryBeforeCommandInMb":60.0,"memoryPeakBeforeCommandInMb":99.727} {"correlation_id":"b8652f0d-1cee-44ae-a0ea-3d3137a882c1","trace_id":"7fe19940-0813-4199-a2a8-958de09bd0ee"}
[2026-04-27 10:27:08] local.INFO: Jiminny\Console\Commands\Command::run Memory usage for command {"command":"mailbox:skip-lists:refresh","memoryBeforeCommandInMb":60.0,"memoryAfterCommandInMB":62.0,"memoryPeakBeforeCommandInMb":99.727,"memoryPeakAfterCommandInMB":99.727} {"correlation_id":"b8652f0d-1cee-44ae-a0ea-3d3137a882c1","trace_id":"7fe19940-0813-4199-a2a8-958de09bd0ee"}
[2026-04-27 10:27:09] local.INFO: Jiminny\Console\Commands\Command::run Memory usage before starting command {"command":"mailbox:batch:process","memoryBeforeCommandInMb":60.0,"memoryPeakBeforeCommandInMb":99.727} {"correlation_id":"06190462-0f47-403f-8e95-ad5d05bff1fe","trace_id":"4aa6bed6-05a1-4661-a9c9-edadc61d84ac"}
[2026-04-27 10:27:09] local.INFO: [EmailSchedule] STARTING batch process {"host":"docker_lamp_1"} {"correlation_id":"06190462-0f47-403f-8e95-ad5d05bff1fe","trace_id":"4aa6bed6-05a1-4661-a9c9-edadc61d84ac"}
[2026-04-27 10:27:09] local.INFO: [EmailSchedule] FINISHED batch process {"host":"docker_lamp_1","processed":0} {"correlation_id":"06190462-0f47-403f-8e95-ad5d05bff1fe","trace_id":"4aa6bed6-05a1-4661-a9c9-edadc61d84ac"}
[2026-04-27 10:27:09] local.INFO: Jiminny\Console\Commands\Command::run Memory usage for command {"command":"mailbox:batch:process","memoryBeforeCommandInMb":60.0,"memoryAfterCommandInMB":62.0,"memoryPeakBeforeCommandInMb":99.727,"memoryPeakAfterCommandInMB":99.727} {"correlation_id":"06190462-0f47-403f-8e95-ad5d05bff1fe","trace_id":"4aa6bed6-05a1-4661-a9c9-edadc61d84ac"}
[2026-04-27 10:27:11] local.INFO: Jiminny\Console\Commands\Command::run Memory usage before starting command {"command":"mailbox:batch:create","memoryBeforeCommandInMb":60.0,"memoryPeakBeforeCommandInMb":99.727} {"correlation_id":"b561c5d5-e6e3-4091-9517-b0458190a360","trace_id":"bf9679fb-b785-40cb-b814-04a1dc385a1c"}
[2026-04-27 10:27:11] local.INFO: [EmailSchedule] STARTING batch create {"host":"docker_lamp_1"} {"correlation_id":"b561c5d5-e6e3-4091-9517-b0458190a360","trace_id":"bf9679fb-b785-40cb-b814-04a1dc385a1c"}
[2026-04-27 10:27:11] local.INFO: [EmailSchedule] FINISHED batch create {"host":"docker_lamp_1"} {"correlation_id":"b561c5d5-e6e3-4091-9517-b0458190a360","trace_id":"bf9679fb-b785-40cb-b814-04a1dc385a1c"}
[2026-04-27 10:27:11] local.INFO: Jiminny\Console\Commands\Command::run Memory usage for command {"command":"mailbox:batch:create","memoryBeforeCommandInMb":60.0,"memoryAfterCommandInMB":62.0,"memoryPeakBeforeCommandInMb":99.727,"memoryPeakAfterCommandInMB":99.727} {"correlation_id":"b561c5d5-e6e3-4091-9517-b0458190a360","trace_id":"bf9679fb-b785-40cb-b814-04a1dc385a1c"}
[2026-04-27 10:27:14] local.INFO: [Jiminny\Jobs\Mailbox\CreateBatches] processed 2 inboxes and created 1 batches {"userId":null,"batchSize":30,"maxBatches":1000} {"correlation_id":"98416296-43c2-4834-927e-4a0e86dc99cf","trace_id":"bf9679fb-b785-40cb-b814-04a1dc385a1c"}
[2026-04-27 10:28:04] local.INFO: Jiminny\Console\Commands\Command::run Memory usage before starting command {"command":"meeting-bot:schedule-bot","memoryBeforeCommandInMb":60.0,"memoryPeakBeforeCommandInMb":99.727} {"correlation_id":"5852bcf1-af92-4d24-8a2d-08a9d4a5cfac","trace_id":"25af5e8c-a148-493e-ac9f-4d53f1960177"}
[2026-04-27 10:28:04] local.INFO: [ScheduleBotCommand] Number of activities to be captured: 0 {"correlation_id":"5852bcf1-af92-4d24-8a2d-08a9d4a5cfac","trace_id":"25af5e8c-a148-493e-ac9f-4d53f1960177"}
[2026-04-27 10:28:04] local.INFO: Jiminny\Console\Commands\Command::run Memory usage for command {"command":"meeting-bot:schedule-bot","memoryBeforeCommandInMb":60.0,"memoryAfterCommandInMB":62.0,"memoryPeakBeforeCommandInMb":99.727,"memoryPeakAfterCommandInMB":99.727} {"correlation_id":"5852bcf1-af92-4d24-8a2d-08a9d4a5cfac","trace_id":"25af5e8c-a148-493e-ac9f-4d53f1960177"}
[2026-04-27 10:28:06] local.INFO: Jiminny\Console\Commands\Command::run Memory usage before starting command {"command":"dialers:monitor-activities","memoryBeforeCommandInMb":60.0,"memoryPeakBeforeCommandInMb":99.727} {"correlation_id":"cc1cff1f-71b4-4bb1-800f-66e27c0f2b3a","trace_id":"c03103a3-9ab8-480b-9753-7c7c6488f246"}
[2026-04-27 10:28:06] local.INFO: Jiminny\Console\Commands\Command::run Memory usage for command {"command":"dialers:monitor-activities","memoryBeforeCommandInMb":60.0,"memoryAfterCommandInMB":62.0,"memoryPeakBeforeCommandInMb":99.727,"memoryPeakAfterCommandInMB":99.727} {"correlation_id":"cc1cff1f-71b4-4bb1-800f-66e27c0f2b3a","trace_id":"c03103a3-9ab8-480b-9753-7c7c6488f246"}
[2026-04-27 10:28:09] local.ERROR: Target class [Jiminny\Repositories\AjReportsRepository] does not exist. {"exception":"[object] (Illuminate\\Contracts\\Container\\BindingResolutionException(code: 0): Target class [Jiminny\\Repositories\\AjReportsRepository] does not exist. at /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php:1124)
[stacktrace]
#0 /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php(933): Illuminate\\Container\\Container->build('Jiminny\\\\Reposit...')
#1 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Application.php(1078): Illuminate\\Container\\Container->resolve('Jiminny\\\\Reposit...', Array, true)
#2 /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php(864): Illuminate\\Foundation\\Application->resolve('Jiminny\\\\Reposit...', Array)
#3 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Application.php(1058): Illuminate\\Container\\Container->make('Jiminny\\\\Reposit...', Array)
#4 /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php(1336): Illuminate\\Foundation\\Application->make('Jiminny\\\\Reposit...')
#5 /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php(1237): Illuminate\\Container\\Container->resolveClass(Object(ReflectionParameter))
#6 /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php(1162): Illuminate\\Container\\Container->resolveDependencies(Array)
#7 /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php(933): Illuminate\\Container\\Container->build('Jiminny\\\\Http\\\\Co...')
#8 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Application.php(1078): Illuminate\\Container\\Container->resolve('Jiminny\\\\Http\\\\Co...', Array, true)
#9 /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php(864): Illuminate\\Foundation\\Application->resolve('Jiminny\\\\Http\\\\Co...', Array)
#10 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Application.php(1058): Illuminate\\Container\\Container->make('Jiminny\\\\Http\\\\Co...', Array)
#11 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Route.php(286): Illuminate\\Foundation\\Application->make('Jiminny\\\\Http\\\\Co...')
#12 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Route.php(1133): Illuminate\\Routing\\Route->getController()
#13 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Route.php(1062): Illuminate\\Routing\\Route->controllerMiddleware()
#14 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Router.php(834): Illuminate\\Routing\\Route->gatherMiddleware()
#15 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Router.php(816): Illuminate\\Routing\\Router->gatherRouteMiddleware(Object(Illuminate\\Routing\\Route))
#16 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Router.php(800): Illuminate\\Routing\\Router->runRouteWithinStack(Object(Illuminate\\Routing\\Route), Object(Illuminate\\Http\\Request))
#17 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Router.php(764): Illuminate\\Routing\\Router->runRoute(Object(Illuminate\\Http\\Request), Object(Illuminate\\Routing\\Route))
#18 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Router.php(753): Illuminate\\Routing\\Router->dispatchToRoute(Object(Illuminate\\Http\\Request))
#19 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Http/Kernel.php(200): Illuminate\\Routing\\Router->dispatch(Object(Illuminate\\Http\\Request))
#20 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(180): Illuminate\\Foundation\\Http\\Kernel->Illuminate\\Foundation\\Http\\{closure}(Object(Illuminate\\Http\\Request))
#21 /home/jiminny/vendor/barryvdh/laravel-debugbar/src/Middleware/InjectDebugbar.php(59): Illuminate\\Pipeline\\Pipeline->Illuminate\\Pipeline\\{closure}(Object(Illuminate\\Http\\Request))
#22 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(219): Barryvdh\\Debugbar\\Middleware\\InjectDebugbar->handle(Object(Illuminate\\Http\\Request), Object(Closure))
#23 /home/jiminny/vendor/laravel/framework/src/Illuminate/Http/Middleware/HandleCors.php(74): Illuminate\\Pipeline\\Pipeline->Illuminate\\Pipeline\\{closure}(Object(Illuminate\\Http\\Request))
#24 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(219): Illuminate\\Http\\Middleware\\HandleCors->handle(Object(Illuminate\\Http\\Request), Object(Closure))
#25 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Http/Middleware/TransformsRequest.php(21): Illuminate\\Pipeline\\Pipeline->Illuminate\\Pipeline\\{closure}(Object(Illuminate\\Http\\Request))
#26 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Http/Middleware/TrimStrings.php(51): Illuminate\\Foundation\\Http\\Middleware\\TransformsRequest->handle(Object(Illuminate\\Http\\Request), Object(Closure))
#27 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(219): Illuminate\\Foundation\\Http\\Middleware\\TrimStrings->handle(Object(Illuminate\\Http\\Request), Object(Closure))
#28 /home/jiminny/vendor/bepsvpt/secure-headers/src/SecureHeadersMiddleware.php(18): Illuminate\\Pipeline\\Pipeline->Illuminate\\Pipeline\\{closure}(Object(Illuminate\\Http\\Request))
#29 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(219): Bepsvpt\\SecureHeaders\\SecureHeadersMiddleware->handle(Object(Illuminate\\Http\\Request), Object(Closure))
#30 /home/jiminny/app/Http/Middleware/SentryContext.php(60): Illuminate\\Pipeline\\Pipeline->Illuminate\\Pipeline\\{closure}(Object(Illuminate\\Http\\Request))
#31 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(219): Jiminny\\Http\\Middleware\\SentryContext->handle(Object(Illuminate\\Http\\Request), Object(Closure))
#32 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Http/Middleware/PreventRequestsDuringMaintenance.php(109): Illuminate\\Pipeline\\Pipeline->Illuminate\\Pipeline\\{closure}(Object(Illuminate\\Http\\Request))
#33 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(219): Illuminate\\Foundation\\Http\\Middleware\\PreventRequestsDuringMaintenance->handle(Object(Illuminate\\Http\\Request), Object(Closure))
#34 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Http/Middleware/InvokeDeferredCallbacks.php(22): Illuminate\\Pipeline\\Pipeline->Illuminate\\Pipeline\\{closure}(Object(Illuminate\\Http\\Request))
#35 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(219): Illuminate\\Foundation\\Http\\Middleware\\InvokeDeferredCallbacks->handle(Object(Illuminate\\Http\\Request), Object(Closure))
#36 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(137): Illuminate\\Pipeline\\Pipeline->Illuminate\\Pipeline\\{closure}(Object(Illuminate\\Http\\Request))
#37 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Http/Kernel.php(175): Illuminate\\Pipeline\\Pipeline->then(Object(Closure))
#38 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Http/Kernel.php(144): Illuminate\\Foundation\\Http\\Kernel->sendRequestThroughRouter(Object(Illuminate\\Http\\Request))
#39 /home/jiminny/public/index.php(51): Illuminate\\Foundation\\Http\\Kernel->handle(Object(Illuminate\\Http\\Request))
#40 {main}
[previous exception] [object] (ReflectionException(code: -1): Class \"Jiminny\\Repositories\\AjReportsRepository\" does not exist at /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php:1122)
[stacktrace]
#0 /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php(1122): ReflectionClass->__construct('Jiminny\\\\Reposit...')
#1 /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php(933): Illuminate\\Container\\Container->build('Jiminny\\\\Reposit...')
#2 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Application.php(1078): Illuminate\\Container\\Container->resolve('Jiminny\\\\Reposit...', Array, true)
#3 /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php(864): Illuminate\\Foundation\\Application->resolve('Jiminny\\\\Reposit...', Array)
#4 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Application.php(1058): Illuminate\\Container\\Container->make('Jiminny\\\\Reposit...', Array)
#5 /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php(1336): Illuminate\\Foundation\\Application->make('Jiminny\\\\Reposit...')
#6 /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php(1237): Illuminate\\Container\\Container->resolveClass(Object(ReflectionParameter))
#7 /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php(1162): Illuminate\\Container\\Container->resolveDependencies(Array)
#8 /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php(933): Illuminate\\Container\\Container->build('Jiminny\\\\Http\\\\Co...')
#9 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Application.php(1078): Illuminate\\Container\\Container->resolve('Jiminny\\\\Http\\\\Co...', Array, true)
#10 /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php(864): Illuminate\\Foundation\\Application->resolve('Jiminny\\\\Http\\\\Co...', Array)
#11 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Application.php(1058): Illuminate\\Container\\Container->make('Jiminny\\\\Http\\\\Co...', Array)
#12 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Route.php(286): Illuminate\\Foundation\\Application->make('Jiminny\\\\Http\\\\Co...')
#13 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Route.php(1133): Illuminate\\Routing\\Route->getController()
#14 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Route.php(1062): Illuminate\\Routing\\Route->controllerMiddleware()
#15 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Router.php(834): Illuminate\\Routing\\Route->gatherMiddleware()
#16 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Router.php(816): Illuminate\\Routing\\Router->gatherRouteMiddleware(Object(Illuminate\\Routing\\Route))
#17 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Router.php(800): Illuminate\\Routing\\Router->runRouteWithinStack(Object(Illuminate\\Routing\\Route), Object(Illuminate\\Http\\Request))
#18 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Router.php(764): Illuminate\\Routing\\Router->runRoute(Object(Illuminate\\Http\\Request), Object(Illuminate\\Routing\\Route))
#19 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Router.php(753): Illuminate\\Routing\\Router->dispatchToRoute(Object(Illuminate\\Http\\Request))
#20 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Http/Kernel.php(200): Illuminate\\Routing\\Router->dispatch(Object(Illuminate\\Http\\Request))
#21 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(180): Illuminate\\Foundation\\Http\\Kernel->Illuminate\\Foundation\\Http\\{closure}(Object(Illuminate\\Http\\Request))
#22 /home/jiminny/vendor/barryvdh/laravel-debugbar/src/Middleware/InjectDebugbar.php(59): Illuminate\\Pipeline\\Pipeline->Illuminate\\Pipeline\\{closure}(Object(Illuminate\\Http\\Request))
#23 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(219): Barryvdh\\Debugbar\\Middleware\\InjectDebugbar->handle(Object(Illuminate\\Http\\Request), Object(Closure))
#24 /home/jiminny/vendor/laravel/framework/src/Illuminate/Http/Middleware/HandleCors.php(74): Illuminate\\Pipeline\\Pipeline->Illuminate\\Pipeline\\{closure}(Object(Illuminate\\Http\\Request))
#25 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(219): Illuminate\\Http\\Middleware\\HandleCors->handle(Object(Illuminate\\Http\\Request), Object(Closure))
#26 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Http/Middleware/TransformsRequest.php(21): Illuminate\\Pipeline\\Pipeline->Illuminate\\Pipeline\\{closure}(Object(Illuminate\\Http\\Request))
#27 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Http/Middleware/TrimStrings.php(51): Illuminate\\Foundation\\Http\\Middleware\\TransformsRequest->handle(Object(Illuminate\\Http\\Request), Object(Closure))
#28 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(219): Illuminate\\Foundation\\Http\\Middleware\\TrimStrings->handle(Object(Illuminate\\Http\\Request), Object(Closure))
#29 /home/jiminny/vendor/bepsvpt/secure-headers/src/SecureHeadersMiddleware.php(18): Illuminate\\Pipeline\\Pipeline->Illuminate\\Pipeline\\{closure}(Object(Illuminate\\Http\\Request))
#30 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(219): Bepsvpt\\SecureHeaders\\SecureHeadersMiddleware->handle(Object(Illuminate\\Http\\Request), Object(Closure))
#31 /home/jiminny/app/Http/Middleware/SentryContext.php(60): Illuminate\\Pipeline\\Pipeline->Illuminate\\Pipeline\\{closure}(Object(Illuminate\\Http\\Request))
#32 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(219): Jiminny\\Http\\Middleware\\SentryContext->handle(Object(Illuminate\\Http\\Request), Object(Closure))
#33 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Http/Middleware/PreventRequestsDuringMaintenance.php(109): Illuminate\\Pipeline\\Pipeline->Illuminate\\Pipeline\\{closure}(Object(Illuminate\\Http\\Request))
#34 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(219): Illuminate\\Foundation\\Http\\Middleware\\PreventRequestsDuringMaintenance->handle(Object(Illuminate\\Http\\Request), Object(Closure))
#35 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Http/Middleware/InvokeDeferredCallbacks.php(22): Illuminate\\Pipeline\\Pipeline->Illuminate\\Pipeline\\{closure}(Object(Illuminate\\Http\\Request))
#36 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(219): Illuminate\\Foundation\\Http\\Middleware\\InvokeDeferredCallbacks->handle(Object(Illuminate\\Http\\Request), Object(Closure))
#37 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(137): Illuminate\\Pipeline\\Pipeline->Illuminate\\Pipeline\\{closure}(Object(Illuminate\\Http\\Request))
#38 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Http/Kernel.php(175): Illuminate\\Pipeline\\Pipeline->then(Object(Closure))
#39 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Http/Kernel.php(144): Illuminate\\Foundation\\Http\\Kernel->sendRequestThroughRouter(Object(Illuminate\\Http\\Request))
#40 /home/jiminny/public/index.php(51): Illuminate\\Foundation\\Http\\Kernel->handle(Object(Illuminate\\Http\\Request))
#41 {main}
"} {"correlation_id":"dfad8817-1dde-4622-8320-f5fc1616a8fe","trace_id":"d21232b2-60fb-4ca0-838c-ba42e61593f9"}
[2026-04-27 10:28:09] local.NOTICE: Monitoring start {"correlation_id":"93744c67-4444-41ff-aaad-6aa9cafe294e","trace_id":"80e396b0-b1ac-4ec8-838c-00021f98072c"}
[2026-04-27 10:28:09] local.NOTICE: Monitoring end {"correlation_id":"93744c67-4444-41ff-aaad-6aa9cafe294e","trace_id":"80e396b0-b1ac-4ec8-838c-00021f98072c"}
[2026-04-27 10:28:10] local.ERROR: Target class [Jiminny\Repositories\AjReportsRepository] does not exist. {"exception":"[object] (Illuminate\\Contracts\\Container\\BindingResolutionException(code: 0): Target class [Jiminny\\Repositories\\AjReportsRepository] does not exist. at /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php:1124)
[stacktrace]
#0 /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php(933): Illuminate\\Container\\Container->build('Jiminny\\\\Reposit...')
#1 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Application.php(1078): Illuminate\\Container\\Container->resolve('Jiminny\\\\Reposit...', Array, true)
#2 /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php(864): Illuminate\\Foundation\\Application->resolve('Jiminny\\\\Reposit...', Array)
#3 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Application.php(1058): Illuminate\\Container\\Container->make('Jiminny\\\\Reposit...', Array)
#4 /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php(1336): Illuminate\\Foundation\\Application->make('Jiminny\\\\Reposit...')
#5 /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php(1237): Illuminate\\Container\\Container->resolveClass(Object(ReflectionParameter))
#6 /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php(1162): Illuminate\\Container\\Container->resolveDependencies(Array)
#7 /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php(933): Illuminate\\Container\\Container->build('Jiminny\\\\Http\\\\Co...')
#8 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Application.php(1078): Illuminate\\Container\\Container->resolve('Jiminny\\\\Http\\\\Co...', Array, true)
#9 /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php(864): Illuminate\\Foundation\\Application->resolve('Jiminny\\\\Http\\\\Co...', Array)
#10 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Application.php(1058): Illuminate\\Container\\Container->make('Jiminny\\\\Http\\\\Co...', Array)
#11 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Route.php(286): Illuminate\\Foundation\\Application->make('Jiminny\\\\Http\\\\Co...')
#12 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Route.php(1133): Illuminate\\Routing\\Route->getController()
#13 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Route.php(1062): Illuminate\\Routing\\Route->controllerMiddleware()
#14 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Router.php(834): Illuminate\\Routing\\Route->gatherMiddleware()
#15 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Router.php(816): Illuminate\\Routing\\Router->gatherRouteMiddleware(Object(Illuminate\\Routing\\Route))
#16 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Router.php(800): Illuminate\\Routing\\Router->runRouteWithinStack(Object(Illuminate\\Routing\\Route), Object(Illuminate\\Http\\Request))
#17 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Router.php(764): Illuminate\\Routing\\Router->runRoute(Object(Illuminate\\Http\\Request), Object(Illuminate\\Routing\\Route))
#18 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Router.php(753): Illuminate\\Routing\\Router->dispatchToRoute(Object(Illuminate\\Http\\Request))
#19 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Http/Kernel.php(200): Illuminate\\Routing\\Router->dispatch(Object(Illuminate\\Http\\Request))
#20 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(180): Illuminate\\Foundation\\Http\\Kernel->Illuminate\\Foundation\\Http\\{closure}(Object(Illuminate\\Http\\Request))
#21 /home/jiminny/vendor/barryvdh/laravel-debugbar/src/Middleware/InjectDebugbar.php(59): Illuminate\\Pipeline\\Pipeline->Illuminate\\Pipeline\\{closure}(Object(Illuminate\\Http\\Request))
#22 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(219): Barryvdh\\Debugbar\\Middleware\\InjectDebugbar->handle(Object(Illuminate\\Http\\Request), Object(Closure))
#23 /home/jiminny/vendor/laravel/framework/src/Illuminate/Http/Middleware/HandleCors.php(74): Illuminate\\Pipeline\\Pipeline->Illuminate\\Pipeline\\{closure}(Object(Illuminate\\Http\\Request))
#24 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(219): Illuminate\\Http\\Middleware\\HandleCors->handle(Object(Illuminate\\Http\\Request), Object(Closure))
#25 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Http/Middleware/TransformsRequest.php(21): Illuminate\\Pipeline\\Pipeline->Illuminate\\Pipeline\\{closure}(Object(Illuminate\\Http\\Request))
#26 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Http/Middleware/TrimStrings.php(51): Illuminate\\Foundation\\Http\\Middleware\\TransformsRequest->handle(Object(Illuminate\\Http\\Request), Object(Closure))
#27 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(219): Illuminate\\Foundation\\Http\\Middleware\\TrimStrings->handle(Object(Illuminate\\Http\\Request), Object(Closure))
#28 /home/jiminny/vendor/bepsvpt/secure-headers/src/SecureHeadersMiddleware.php(18): Illuminate\\Pipeline\\Pipeline->Illuminate\\Pipeline\\{closure}(Object(Illuminate\\Http\\Request))
#29 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(219): Bepsvpt\\SecureHeaders\\SecureHeadersMiddleware->handle(Object(Illuminate\\Http\\Request), Object(Closure))
#30 /home/jiminny/app/Http/Middleware/SentryContext.php(60): Illuminate\\Pipeline\\Pipeline->Illuminate\\Pipeline\\{closure}(Object(Illuminate\\Http\\Request))
#31 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(219): Jiminny\\Http\\Middleware\\SentryContext->handle(Object(Illuminate\\Http\\Request), Object(Closure))
#32 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Http/Middleware/PreventRequestsDuringMaintenance.php(109): Illuminate\\Pipeline\\Pipeline->Illuminate\\Pipeline\\{closure}(Object(Illuminate\\Http\\Request))
#33 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(219): Illuminate\\Foundation\\Http\\Middleware\\PreventRequestsDuringMaintenance->handle(Object(Illuminate\\Http\\Request), Object(Closure))
#34 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Http/Middleware/InvokeDeferredCallbacks.php(22): Illuminate\\Pipeline\\Pipeline->Illuminate\\Pipeline\\{closure}(Object(Illuminate\\Http\\Request))
#35 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(219): Illuminate\\Foundation\\Http\\Middleware\\InvokeDeferredCallbacks->handle(Object(Illuminate\\Http\\Request), Object(Closure))
#36 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(137): Illuminate\\Pipeline\\Pipeline->Illuminate\\Pipeline\\{closure}(Object(Illuminate\\Http\\Request))
#37 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Http/Kernel.php(175): Illuminate\\Pipeline\\Pipeline->then(Object(Closure))
#38 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Http/Kernel.php(144): Illuminate\\Foundation\\Http\\Kernel->sendRequestThroughRouter(Object(Illuminate\\Http\\Request))
#39 /home/jiminny/public/index.php(51): Illuminate\\Foundation\\Http\\Kernel->handle(Object(Illuminate\\Http\\Request))
#40 {main}
[previous exception] [object] (ReflectionException(code: -1): Class \"Jiminny\\Repositories\\AjReportsRepository\" does not exist at /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php:1122)
[stacktrace]
#0 /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php(1122): ReflectionClass->__construct('Jiminny\\\\Reposit...')
#1 /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php(933): Illuminate\\Container\\Container->build('Jiminny\\\\Reposit...')
#2 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Application.php(1078): Illuminate\\Container\\Container->resolve('Jiminny\\\\Reposit...', Array, true)
#3 /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php(864): Illuminate\\Foundation\\Application->resolve('Jiminny\\\\Reposit...', Array)
#4 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Application.php(1058): Illuminate\\Container\\Container->make('Jiminny\\\\Reposit...', Array)
#5 /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php(1336): Illuminate\\Foundation\\Application->make('Jiminny\\\\Reposit...')
#6 /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php(1237): Illuminate\\Container\\Container->resolveClass(Object(ReflectionParameter))
#7 /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php(1162): Illuminate\\Container\\Container->resolveDependencies(Array)
#8 /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php(933): Illuminate\\Container\\Container->build('Jiminny\\\\Http\\\\Co...')
#9 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Application.php(1078): Illuminate\\Container\\Container->resolve('Jiminny\\\\Http\\\\Co...', Array, true)
#10 /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php(864): Illuminate\\Foundation\\Application->resolve('Jiminny\\\\Http\\\\Co...', Array)
#11 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Application.php(1058): Illuminate\\Container\\Container->make('Jiminny\\\\Http\\\\Co...', Array)
#12 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Route.php(286): Illuminate\\Foundation\\Application->make('Jiminny\\\\Http\\\\Co...')
#13 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Route.php(1133): Illuminate\\Routing\\Route->getController()
#14 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Route.php(1062): Illuminate\\Routing\\Route->controllerMiddleware()
#15 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Router.php(834): Illuminate\\Routing\\Route->gatherMiddleware()
#16 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Router.php(816): Illuminate\\Routing\\Router->gatherRouteMiddleware(Object(Illuminate\\Routing\\Route))
#17 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Router.php(800): Illuminate\\Routing\\Router->runRouteWithinStack(Object(Illuminate\\Routing\\Route), Object(Illuminate\\Http\\Request))
#18 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Router.php(764): Illuminate\\Routing\\Router->runRoute(Object(Illuminate\\Http\\Request), Object(Illuminate\\Routing\\Route))
#19 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Router.php(753): Illuminate\\Routing\\Router->dispatchToRoute(Object(Illuminate\\Http\\Request))
#20 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Http/Kernel.php(200): Illuminate\\Routing\\Router->dispatch(Object(Illuminate\\Http\\Request))
#21 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(180): Illuminate\\Foundation\\Http\\Kernel->Illuminate\\Foundation\\Http\\{closure}(Object(Illuminate\\Http\\Request))
#22 /home/jiminny/vendor/barryvdh/laravel-debugbar/src/Middleware/InjectDebugbar.php(59): Illuminate\\Pipeline\\Pipeline->Illuminate\\Pipeline\\{closure}(Object(Illuminate\\Http\\Request))
#23 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(219): Barryvdh\\Debugbar\\Middleware\\InjectDebugbar->handle(Object(Illuminate\\Http\\Request), Object(Closure))
#24 /home/jiminny/vendor/laravel/framework/src/Illuminate/Http/Middleware/HandleCors.php(74): Illuminate\\Pipeline\\Pipeline->Illuminate\\Pipeline\\{closure}(Object(Illuminate\\Http\\Request))
#25 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(219): Illuminate\\Http\\Middleware\\HandleCors->handle(Object(Illuminate\\Http\\Request), Object(Closure))
#26 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Http/Middleware/TransformsRequest.php(21): Illuminate\\Pipeline\\Pipeline->Illuminate\\Pipeline\\{closure}(Object(Illuminate\\Http\\Request))
#27 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Http/Middleware/TrimStrings.php(51): Illuminate\\Foundation\\Http\\Middleware\\TransformsRequest->handle(Object(Illuminate\\Http\\Request), Object(Closure))
#28 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(219): Illuminate\\Foundation\\Http\\Middleware\\TrimStrings->handle(Object(Illuminate\\Http\\Request), Object(Closure))
#29 /home/jiminny/vendor/bepsvpt/secure-headers/src/SecureHeadersMiddleware.php(18): Illuminate\\Pipeline\\Pipeline->Illuminate\\Pipeline\\{closure}(Object(Illuminate\\Http\\Request))
#30 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(219): Bepsvpt\\SecureHeaders\\SecureHeadersMiddleware->handle(Object(Illuminate\\Http\\Request), Object(Closure))
#31 /home/jiminny/app/Http/Middleware/SentryContext.php(60): Illuminate\\Pipeline\\Pipeline->Illuminate\\Pipeline\\{closure}(Object(Illuminate\\Http\\Request))
#32 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(219): Jiminny\\Http\\Middleware\\SentryContext->handle(Object(Illuminate\\Http\\Request), Object(Closure))
#33 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Http/Middleware/PreventRequestsDuringMaintenance.php(109): Illuminate\\Pipeline\\Pipeline->Illuminate\\Pipeline\\{closure}(Object(Illuminate\\Http\\Request))
#34 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(219): Illuminate\\Foundation\\Http\\Middleware\\PreventRequestsDuringMaintenance->handle(Object(Illuminate\\Http\\Request), Object(Closure))
#35 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Http/Middleware/InvokeDeferredCallbacks.php(22): Illuminate\\Pipeline\\Pipeline->Illuminate\\Pipeline\\{closure}(Object(Illuminate\\Http\\Request))
#36 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(219): Illuminate\\Foundation\\Http\\Middleware\\InvokeDeferredCallbacks->handle(Object(Illuminate\\Http\\Request), Object(Closure))
#37 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(137): Illuminate\\Pipeline\\Pipeline->Illuminate\\Pipeline\\{closure}(Object(Illuminate\\Http\\Request))
#38 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Http/Kernel.php(175): Illuminate\\Pipeline\\Pipeline->then(Object(Closure))
#39 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Http/Kernel.php(144): Illuminate\\Foundation\\Http\\Kernel->sendRequestThroughRouter(Object(Illuminate\\Http\\Request))
#40 /home/jiminny/public/index.php(51): Illuminate\\Foundation\\Http\\Kernel->handle(Object(Illuminate\\Http\\Request))
#41 {main}
"} {"correlation_id":"de768781-5c05-418c-baf3-79f77a8c8694","trace_id":"fec74062-3f6d-4cfc-968a-1bb285496db3"}
[2026-04-27 10:28:12] local.INFO: Jiminny\Console\Commands\Command::run Memory usage before starting command {"command":"mailbox:skip-lists:refresh","memoryBeforeCommandInMb":60.0,"memoryPeakBeforeCommandInMb":99.727} {"correlation_id":"907d15ed-8c84-41a1-a473-590eebb55dbc","trace_id":"ec7bd353-516e-491d-89dd-de44136c1672"}
[2026-04-27 10:28:12] local.INFO: Jiminny\Console\Commands\Command::run Memory usage for command {"command":"mailbox:skip-lists:refresh","memoryBeforeCommandInMb":60.0,"memoryAfterCommandInMB":62.0,"memoryPeakBeforeCommandInMb":99.727,"memoryPeakAfterCommandInMB":99.727} {"correlation_id":"907d15ed-8c84-41a1-a473-590eebb55dbc","trace_id":"ec7bd353-516e-491d-89dd-de44136c1672"}
[2026-04-27 10:28:13] local.ERROR: Target class [Jiminny\Repositories\AjReportsRepository] does not exist. {"exception":"[object] (Illuminate\\Contracts\\Container\\BindingResolutionException(code: 0): Target class [Jiminny\\Repositories\\AjReportsRepository] does not exist. at /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php:1124)
[stacktrace]
#0 /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php(933): Illuminate\\Container\\Container->build('Jiminny\\\\Reposit...')
#1 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Application.php(1078): Illuminate\\Container\\Container->resolve('Jiminny\\\\Reposit...', Array, true)
#2 /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php(864): Illuminate\\Foundation\\Application->resolve('Jiminny\\\\Reposi...
|
[{"role":"AXTextField","text [{"role":"AXTextField","text":"This view is read-only","depth":2,"bounds":{"left":0.5794548,"top":0.16280925,"width":0.04488032,"height":0.013567438},"on_screen":true,"value":"This view is read-only","help_text":"text/html","role_description":"text field","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXTextField","text":"text/html","depth":3,"on_screen":false,"help_text":"text/html","role_description":"text field","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXTextField","text":"text/html","depth":3,"bounds":{"left":0.5794548,"top":0.16280925,"width":0.04488032,"height":0.013567438},"on_screen":true,"help_text":"text/html","role_description":"text field","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXTextField","text":"text/html","depth":3,"on_screen":false,"help_text":"text/html","role_description":"text field","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Project: faVsco.js, menu","depth":5,"bounds":{"left":0.025930852,"top":0.019952115,"width":0.03856383,"height":0.025538707},"on_screen":true,"help_text":"~/jiminny/app","role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"master, menu","depth":5,"bounds":{"left":0.064494684,"top":0.019952115,"width":0.034242023,"height":0.025538707},"on_screen":true,"help_text":"Git Branch: master","role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Start Listening for PHP Debug Connections","depth":5,"bounds":{"left":0.8081782,"top":0.019952115,"width":0.011303191,"height":0.025538707},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"AskJiminnyReportActivityServiceTest","depth":6,"bounds":{"left":0.8234708,"top":0.019952115,"width":0.09208777,"height":0.025538707},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Run 'AskJiminnyReportActivityServiceTest'","depth":6,"bounds":{"left":0.9155585,"top":0.019952115,"width":0.011303191,"height":0.025538707},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Debug 'AskJiminnyReportActivityServiceTest'","depth":6,"bounds":{"left":0.9268617,"top":0.019952115,"width":0.011303191,"height":0.025538707},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"More Actions","depth":6,"bounds":{"left":0.9381649,"top":0.019952115,"width":0.011303191,"height":0.025538707},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"JetBrains AI","depth":5,"bounds":{"left":0.96609044,"top":0.019952115,"width":0.011303191,"height":0.025538707},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Search Everywhere","depth":5,"bounds":{"left":0.9773936,"top":0.019952115,"width":0.011303191,"height":0.025538707},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"IDE and Project Settings","depth":5,"bounds":{"left":0.9886968,"top":0.019952115,"width":0.011303186,"height":0.025538707},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXTextArea","text":"[2026-04-27 10:26:25] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage before starting command {\"command\":\"mailbox:sync\",\"memoryBeforeCommandInMb\":60.0,\"memoryPeakBeforeCommandInMb\":99.727} {\"correlation_id\":\"7b1ea2fb-8f36-4897-9970-91b4abba2cc7\",\"trace_id\":\"265be7c0-bf8f-4037-a0d2-57df3ef1d7e1\"}\n[2026-04-27 10:26:25] local.INFO: [EmailSchedule] STARTING Inbox Sync {\"host\":\"docker_lamp_1\"} {\"correlation_id\":\"7b1ea2fb-8f36-4897-9970-91b4abba2cc7\",\"trace_id\":\"265be7c0-bf8f-4037-a0d2-57df3ef1d7e1\"}\n[2026-04-27 10:26:25] local.INFO: [EmailSchedule] FINISHED Inbox Sync {\"host\":\"docker_lamp_1\",\"events\":2} {\"correlation_id\":\"7b1ea2fb-8f36-4897-9970-91b4abba2cc7\",\"trace_id\":\"265be7c0-bf8f-4037-a0d2-57df3ef1d7e1\"}\n[2026-04-27 10:26:25] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage for command {\"command\":\"mailbox:sync\",\"memoryBeforeCommandInMb\":60.0,\"memoryAfterCommandInMB\":62.0,\"memoryPeakBeforeCommandInMb\":99.727,\"memoryPeakAfterCommandInMB\":99.727} {\"correlation_id\":\"7b1ea2fb-8f36-4897-9970-91b4abba2cc7\",\"trace_id\":\"265be7c0-bf8f-4037-a0d2-57df3ef1d7e1\"}\n[2026-04-27 10:26:26] local.INFO: [automated-reports] Started {\"correlation_id\":\"d3f79034-397b-4936-9305-bdd99b23f2dd\",\"trace_id\":\"aa0f8702-73eb-4947-a765-2e89244599c3\"}\n[2026-04-27 10:26:26] local.INFO: [automated-reports] Checking conditions {\"isMonday\":true,\"isWeekend\":false,\"isFirstDayOfMonth\":false,\"currentMonth\":4,\"isQuarterlyMonth\":true} {\"correlation_id\":\"d3f79034-397b-4936-9305-bdd99b23f2dd\",\"trace_id\":\"aa0f8702-73eb-4947-a765-2e89244599c3\"}\n[2026-04-27 10:26:26] local.INFO: [automated-reports] Processing daily reports {\"correlation_id\":\"d3f79034-397b-4936-9305-bdd99b23f2dd\",\"trace_id\":\"aa0f8702-73eb-4947-a765-2e89244599c3\"}\n[2026-04-27 10:26:26] local.INFO: [automated-reports] Found 1 daily reports to process {\"correlation_id\":\"d3f79034-397b-4936-9305-bdd99b23f2dd\",\"trace_id\":\"aa0f8702-73eb-4947-a765-2e89244599c3\"}\n[2026-04-27 10:26:26] local.INFO: [automated-reports] Dispatching Generate Report job for report {\"reportUuid\":\"4f6ca2b5-1993-48aa-99ad-b66f19f15d43\",\"teamId\":1,\"frequency\":\"weekly\",\"type\":\"ask_jiminny\"} {\"correlation_id\":\"d3f79034-397b-4936-9305-bdd99b23f2dd\",\"trace_id\":\"aa0f8702-73eb-4947-a765-2e89244599c3\"}\n[2026-04-27 10:26:26] local.INFO: [automated-reports] Processing weekly reports {\"correlation_id\":\"d3f79034-397b-4936-9305-bdd99b23f2dd\",\"trace_id\":\"aa0f8702-73eb-4947-a765-2e89244599c3\"}\n[2026-04-27 10:26:26] local.INFO: [automated-reports] Found 1 weekly reports to process {\"correlation_id\":\"d3f79034-397b-4936-9305-bdd99b23f2dd\",\"trace_id\":\"aa0f8702-73eb-4947-a765-2e89244599c3\"}\n[2026-04-27 10:26:26] local.INFO: [automated-reports] Dispatching Generate Report job for report {\"reportUuid\":\"4f6ca2b5-1993-48aa-99ad-b66f19f15d43\",\"teamId\":1,\"frequency\":\"weekly\",\"type\":\"ask_jiminny\"} {\"correlation_id\":\"d3f79034-397b-4936-9305-bdd99b23f2dd\",\"trace_id\":\"aa0f8702-73eb-4947-a765-2e89244599c3\"}\n[2026-04-27 10:26:26] local.INFO: [automated-reports] Completed {\"correlation_id\":\"d3f79034-397b-4936-9305-bdd99b23f2dd\",\"trace_id\":\"aa0f8702-73eb-4947-a765-2e89244599c3\"}\n[2026-04-27 10:26:26] local.INFO: [HubSpot Journal Polling] Getting offset from database {\"offset\":\"\",\"jiminny_team_id\":1} {\"correlation_id\":\"b453f0f4-0d2c-491c-a935-14c60265ec18\",\"trace_id\":\"a50202d6-5601-46b4-a02e-cf5ac8d061fa\"}\n[2026-04-27 10:26:26] local.INFO: [HubSpot Journal API] Fetching latest journal entry {\"url\":\"https://api.hubapi.com/webhooks/v4/journal/latest\"} {\"correlation_id\":\"b453f0f4-0d2c-491c-a935-14c60265ec18\",\"trace_id\":\"a50202d6-5601-46b4-a02e-cf5ac8d061fa\"}\n[2026-04-27 10:26:26] local.INFO: [AskJiminnyReport:Generate] Started {\"automatedReportUuid\":\"4f6ca2b5-1993-48aa-99ad-b66f19f15d43\"} {\"correlation_id\":\"c6277ddd-09a6-4b04-8d42-bd395fc07d0a\",\"trace_id\":\"aa0f8702-73eb-4947-a765-2e89244599c3\"}\n[2026-04-27 10:26:26] local.INFO: [HubSpot Journal Polling] No data {\"correlation_id\":\"b453f0f4-0d2c-491c-a935-14c60265ec18\",\"trace_id\":\"a50202d6-5601-46b4-a02e-cf5ac8d061fa\"}\n[2026-04-27 10:26:26] local.WARNING: [HubSpot Journal Polling] Maximum empty results reached, stopping {\"empty_results\":5,\"max_empty_results\":5} {\"correlation_id\":\"b453f0f4-0d2c-491c-a935-14c60265ec18\",\"trace_id\":\"a50202d6-5601-46b4-a02e-cf5ac8d061fa\"}\n[2026-04-27 10:26:26] local.WARNING: [HubSpot Journal Polling] Maximum empty results reached, stopping {\"empty_results\":5,\"max_empty_results\":5} {\"correlation_id\":\"b453f0f4-0d2c-491c-a935-14c60265ec18\",\"trace_id\":\"a50202d6-5601-46b4-a02e-cf5ac8d061fa\"}\n[2026-04-27 10:26:26] local.INFO: [HubSpot Journal Polling] Service ending {\"runtime_seconds\":56,\"total_cycles\":5,\"files_downloaded\":0,\"empty_files\":0,\"other_portal_skipped\":0,\"total_events\":0,\"events_per_file\":0,\"avg_api_ms\":273.7,\"avg_download_ms\":0.0,\"avg_transform_ms\":0.0,\"avg_process_ms\":0.0,\"peak_memory_mb\":99.73} {\"correlation_id\":\"b453f0f4-0d2c-491c-a935-14c60265ec18\",\"trace_id\":\"a50202d6-5601-46b4-a02e-cf5ac8d061fa\"}\n[2026-04-27 10:26:26] local.INFO: [HubSpot Journal Polling] Released polling lock {\"correlation_id\":\"b453f0f4-0d2c-491c-a935-14c60265ec18\",\"trace_id\":\"a50202d6-5601-46b4-a02e-cf5ac8d061fa\"}\n[2026-04-27 10:26:27] local.INFO: [AskJiminnyReport] Fetched activity IDs for saved search {\"saved_search_id\":1977,\"user_id\":143,\"activity_count\":0} {\"correlation_id\":\"c6277ddd-09a6-4b04-8d42-bd395fc07d0a\",\"trace_id\":\"aa0f8702-73eb-4947-a765-2e89244599c3\"}\n[2026-04-27 10:26:27] local.INFO: [AskJiminnyReport:Generate] Fetched activity IDs {\"automatedReportUuid\":\"4f6ca2b5-1993-48aa-99ad-b66f19f15d43\",\"activityCount\":0} {\"correlation_id\":\"c6277ddd-09a6-4b04-8d42-bd395fc07d0a\",\"trace_id\":\"aa0f8702-73eb-4947-a765-2e89244599c3\"}\n[2026-04-27 10:26:27] local.INFO: [AskJiminnyReport:Generate] Not enough activities, skipped {\"automatedReportUuid\":\"4f6ca2b5-1993-48aa-99ad-b66f19f15d43\",\"activityCount\":0} {\"correlation_id\":\"c6277ddd-09a6-4b04-8d42-bd395fc07d0a\",\"trace_id\":\"aa0f8702-73eb-4947-a765-2e89244599c3\"}\n[2026-04-27 10:26:27] local.INFO: [AskJiminnyReport:Generate] Dispatched not-generated notifications {\"automatedReportUuid\":\"4f6ca2b5-1993-48aa-99ad-b66f19f15d43\",\"recipientsCount\":1} {\"correlation_id\":\"c6277ddd-09a6-4b04-8d42-bd395fc07d0a\",\"trace_id\":\"aa0f8702-73eb-4947-a765-2e89244599c3\"}\n[2026-04-27 10:26:27] local.INFO: [AskJiminnyReport:Generate] Started {\"automatedReportUuid\":\"4f6ca2b5-1993-48aa-99ad-b66f19f15d43\"} {\"correlation_id\":\"f1d3e4df-9591-487f-ac2a-a43ddb89a466\",\"trace_id\":\"aa0f8702-73eb-4947-a765-2e89244599c3\"}\n[2026-04-27 10:26:27] local.INFO: [AskJiminnyReport] Fetched activity IDs for saved search {\"saved_search_id\":1977,\"user_id\":143,\"activity_count\":0} {\"correlation_id\":\"f1d3e4df-9591-487f-ac2a-a43ddb89a466\",\"trace_id\":\"aa0f8702-73eb-4947-a765-2e89244599c3\"}\n[2026-04-27 10:26:27] local.INFO: [AskJiminnyReport:Generate] Fetched activity IDs {\"automatedReportUuid\":\"4f6ca2b5-1993-48aa-99ad-b66f19f15d43\",\"activityCount\":0} {\"correlation_id\":\"f1d3e4df-9591-487f-ac2a-a43ddb89a466\",\"trace_id\":\"aa0f8702-73eb-4947-a765-2e89244599c3\"}\n[2026-04-27 10:26:27] local.INFO: [AskJiminnyReport:Generate] Not enough activities, skipped {\"automatedReportUuid\":\"4f6ca2b5-1993-48aa-99ad-b66f19f15d43\",\"activityCount\":0} {\"correlation_id\":\"f1d3e4df-9591-487f-ac2a-a43ddb89a466\",\"trace_id\":\"aa0f8702-73eb-4947-a765-2e89244599c3\"}\n[2026-04-27 10:26:27] local.INFO: [AskJiminnyReport:Generate] Dispatched not-generated notifications {\"automatedReportUuid\":\"4f6ca2b5-1993-48aa-99ad-b66f19f15d43\",\"recipientsCount\":1} {\"correlation_id\":\"f1d3e4df-9591-487f-ac2a-a43ddb89a466\",\"trace_id\":\"aa0f8702-73eb-4947-a765-2e89244599c3\"}\n[2026-04-27 10:26:27] local.INFO: [Send Report Not Generated Mail] Email sent {\"uuid\":\"cd5b5081-22f8-4d1a-9d6f-f82aca1121f6\",\"email\":\"lukas.kovalik@jiminny.com\"} {\"correlation_id\":\"4255983f-07a4-4872-9ea6-5491fa32bdd5\",\"trace_id\":\"aa0f8702-73eb-4947-a765-2e89244599c3\"}\n[2026-04-27 10:26:28] local.INFO: [Sync Mailbox] Sync start {\"inbox_id\":59} {\"correlation_id\":\"280d5bf6-c34c-45ea-bea7-05d9c028e9d3\",\"trace_id\":\"265be7c0-bf8f-4037-a0d2-57df3ef1d7e1\"}\n[2026-04-27 10:26:28] local.INFO: [Inbox service] Skipping METADATA SYNC for inbox 59 due to unauthorized access to the mailbox {\"correlation_id\":\"280d5bf6-c34c-45ea-bea7-05d9c028e9d3\",\"trace_id\":\"265be7c0-bf8f-4037-a0d2-57df3ef1d7e1\"}\n[2026-04-27 10:26:28] local.INFO: [Sync Mailbox] Sync complete {\"inbox_id\":59} {\"correlation_id\":\"280d5bf6-c34c-45ea-bea7-05d9c028e9d3\",\"trace_id\":\"265be7c0-bf8f-4037-a0d2-57df3ef1d7e1\"}\n[2026-04-27 10:26:28] local.INFO: [Sync Mailbox] Sync start {\"inbox_id\":212} {\"correlation_id\":\"d3e3eb3d-5c83-4d6e-9592-aaa38c1843a4\",\"trace_id\":\"265be7c0-bf8f-4037-a0d2-57df3ef1d7e1\"}\n[2026-04-27 10:26:28] local.INFO: [SocialAccountService] Fetching token {\"socialAccountId\":1500,\"provider\":\"salesforce\"} {\"correlation_id\":\"d3e3eb3d-5c83-4d6e-9592-aaa38c1843a4\",\"trace_id\":\"265be7c0-bf8f-4037-a0d2-57df3ef1d7e1\"}\n[2026-04-27 10:26:28] local.INFO: [SocialAccountService] Token retrieved {\"socialAccountId\":1500,\"provider\":\"salesforce\"} {\"correlation_id\":\"d3e3eb3d-5c83-4d6e-9592-aaa38c1843a4\",\"trace_id\":\"265be7c0-bf8f-4037-a0d2-57df3ef1d7e1\"}\n[2026-04-27 10:26:28] local.INFO: [EncryptedTokenManager] Generating access token. {\"mode\":\"legacy\"} {\"correlation_id\":\"d3e3eb3d-5c83-4d6e-9592-aaa38c1843a4\",\"trace_id\":\"265be7c0-bf8f-4037-a0d2-57df3ef1d7e1\"}\n[2026-04-27 10:26:28] local.INFO: [CrmOwnerResolver] Integration owner matched as CRM Owner {\"crm_provider\":\"salesforce\",\"crm_owner\":143,\"team_id\":1} {\"correlation_id\":\"d3e3eb3d-5c83-4d6e-9592-aaa38c1843a4\",\"trace_id\":\"265be7c0-bf8f-4037-a0d2-57df3ef1d7e1\"}\n[2026-04-27 10:26:28] local.INFO: [SocialAccountService] Fetching token {\"socialAccountId\":1354,\"provider\":\"google\"} {\"correlation_id\":\"d3e3eb3d-5c83-4d6e-9592-aaa38c1843a4\",\"trace_id\":\"265be7c0-bf8f-4037-a0d2-57df3ef1d7e1\"}\n[2026-04-27 10:26:28] local.INFO: [SocialAccountService] Token retrieved {\"socialAccountId\":1354,\"provider\":\"google\"} {\"correlation_id\":\"d3e3eb3d-5c83-4d6e-9592-aaa38c1843a4\",\"trace_id\":\"265be7c0-bf8f-4037-a0d2-57df3ef1d7e1\"}\n[2026-04-27 10:26:28] local.INFO: [EncryptedTokenManager] Generating access token. {\"mode\":\"legacy\"} {\"correlation_id\":\"d3e3eb3d-5c83-4d6e-9592-aaa38c1843a4\",\"trace_id\":\"265be7c0-bf8f-4037-a0d2-57df3ef1d7e1\"}\n[2026-04-27 10:26:28] local.INFO: [Gmail] Performing incremental sync for inbox 212 using history ID: @1777284288 {\"correlation_id\":\"d3e3eb3d-5c83-4d6e-9592-aaa38c1843a4\",\"trace_id\":\"265be7c0-bf8f-4037-a0d2-57df3ef1d7e1\"}\n[2026-04-27 10:26:28] local.INFO: [Gmail] imported 7 emails via full sync workflow for inbox 212 {\"correlation_id\":\"d3e3eb3d-5c83-4d6e-9592-aaa38c1843a4\",\"trace_id\":\"265be7c0-bf8f-4037-a0d2-57df3ef1d7e1\"}\n[2026-04-27 10:26:28] local.INFO: [Gmail] seeding inbox 212 with last message time : 2026-04-27 10:24:57 {\"correlation_id\":\"d3e3eb3d-5c83-4d6e-9592-aaa38c1843a4\",\"trace_id\":\"265be7c0-bf8f-4037-a0d2-57df3ef1d7e1\"}\n[2026-04-27 10:26:28] local.INFO: [Sync Mailbox] Sync complete {\"inbox_id\":212} {\"correlation_id\":\"d3e3eb3d-5c83-4d6e-9592-aaa38c1843a4\",\"trace_id\":\"265be7c0-bf8f-4037-a0d2-57df3ef1d7e1\"}\n[2026-04-27 10:27:04] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage before starting command {\"command\":\"meeting-bot:schedule-bot\",\"memoryBeforeCommandInMb\":60.0,\"memoryPeakBeforeCommandInMb\":99.727} {\"correlation_id\":\"f55fbf77-7e5f-4c87-accd-011697ca0fa3\",\"trace_id\":\"9bd1c431-26de-4e31-83d8-3129ed9b3b79\"}\n[2026-04-27 10:27:04] local.INFO: [ScheduleBotCommand] Number of activities to be captured: 0 {\"correlation_id\":\"f55fbf77-7e5f-4c87-accd-011697ca0fa3\",\"trace_id\":\"9bd1c431-26de-4e31-83d8-3129ed9b3b79\"}\n[2026-04-27 10:27:04] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage for command {\"command\":\"meeting-bot:schedule-bot\",\"memoryBeforeCommandInMb\":60.0,\"memoryAfterCommandInMB\":62.0,\"memoryPeakBeforeCommandInMb\":99.727,\"memoryPeakAfterCommandInMB\":99.727} {\"correlation_id\":\"f55fbf77-7e5f-4c87-accd-011697ca0fa3\",\"trace_id\":\"9bd1c431-26de-4e31-83d8-3129ed9b3b79\"}\n[2026-04-27 10:27:05] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage before starting command {\"command\":\"dialers:monitor-activities\",\"memoryBeforeCommandInMb\":60.0,\"memoryPeakBeforeCommandInMb\":99.727} {\"correlation_id\":\"da241053-5f3b-4d02-9504-4ef2987cdbd7\",\"trace_id\":\"364d3fc7-0f11-40c6-8697-d18eff2c4cb6\"}\n[2026-04-27 10:27:05] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage for command {\"command\":\"dialers:monitor-activities\",\"memoryBeforeCommandInMb\":60.0,\"memoryAfterCommandInMB\":62.0,\"memoryPeakBeforeCommandInMb\":99.727,\"memoryPeakAfterCommandInMB\":99.727} {\"correlation_id\":\"da241053-5f3b-4d02-9504-4ef2987cdbd7\",\"trace_id\":\"364d3fc7-0f11-40c6-8697-d18eff2c4cb6\"}\n[2026-04-27 10:27:06] local.NOTICE: Monitoring start {\"correlation_id\":\"ad0d871a-a1fd-4725-b2f7-ac8f0fc1857e\",\"trace_id\":\"cb504e84-d924-43a0-82d5-faf1840134ba\"}\n[2026-04-27 10:27:06] local.NOTICE: Monitoring end {\"correlation_id\":\"ad0d871a-a1fd-4725-b2f7-ac8f0fc1857e\",\"trace_id\":\"cb504e84-d924-43a0-82d5-faf1840134ba\"}\n[2026-04-27 10:27:08] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage before starting command {\"command\":\"mailbox:skip-lists:refresh\",\"memoryBeforeCommandInMb\":60.0,\"memoryPeakBeforeCommandInMb\":99.727} {\"correlation_id\":\"b8652f0d-1cee-44ae-a0ea-3d3137a882c1\",\"trace_id\":\"7fe19940-0813-4199-a2a8-958de09bd0ee\"}\n[2026-04-27 10:27:08] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage for command {\"command\":\"mailbox:skip-lists:refresh\",\"memoryBeforeCommandInMb\":60.0,\"memoryAfterCommandInMB\":62.0,\"memoryPeakBeforeCommandInMb\":99.727,\"memoryPeakAfterCommandInMB\":99.727} {\"correlation_id\":\"b8652f0d-1cee-44ae-a0ea-3d3137a882c1\",\"trace_id\":\"7fe19940-0813-4199-a2a8-958de09bd0ee\"}\n[2026-04-27 10:27:09] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage before starting command {\"command\":\"mailbox:batch:process\",\"memoryBeforeCommandInMb\":60.0,\"memoryPeakBeforeCommandInMb\":99.727} {\"correlation_id\":\"06190462-0f47-403f-8e95-ad5d05bff1fe\",\"trace_id\":\"4aa6bed6-05a1-4661-a9c9-edadc61d84ac\"}\n[2026-04-27 10:27:09] local.INFO: [EmailSchedule] STARTING batch process {\"host\":\"docker_lamp_1\"} {\"correlation_id\":\"06190462-0f47-403f-8e95-ad5d05bff1fe\",\"trace_id\":\"4aa6bed6-05a1-4661-a9c9-edadc61d84ac\"}\n[2026-04-27 10:27:09] local.INFO: [EmailSchedule] FINISHED batch process {\"host\":\"docker_lamp_1\",\"processed\":0} {\"correlation_id\":\"06190462-0f47-403f-8e95-ad5d05bff1fe\",\"trace_id\":\"4aa6bed6-05a1-4661-a9c9-edadc61d84ac\"}\n[2026-04-27 10:27:09] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage for command {\"command\":\"mailbox:batch:process\",\"memoryBeforeCommandInMb\":60.0,\"memoryAfterCommandInMB\":62.0,\"memoryPeakBeforeCommandInMb\":99.727,\"memoryPeakAfterCommandInMB\":99.727} {\"correlation_id\":\"06190462-0f47-403f-8e95-ad5d05bff1fe\",\"trace_id\":\"4aa6bed6-05a1-4661-a9c9-edadc61d84ac\"}\n[2026-04-27 10:27:11] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage before starting command {\"command\":\"mailbox:batch:create\",\"memoryBeforeCommandInMb\":60.0,\"memoryPeakBeforeCommandInMb\":99.727} {\"correlation_id\":\"b561c5d5-e6e3-4091-9517-b0458190a360\",\"trace_id\":\"bf9679fb-b785-40cb-b814-04a1dc385a1c\"}\n[2026-04-27 10:27:11] local.INFO: [EmailSchedule] STARTING batch create {\"host\":\"docker_lamp_1\"} {\"correlation_id\":\"b561c5d5-e6e3-4091-9517-b0458190a360\",\"trace_id\":\"bf9679fb-b785-40cb-b814-04a1dc385a1c\"}\n[2026-04-27 10:27:11] local.INFO: [EmailSchedule] FINISHED batch create {\"host\":\"docker_lamp_1\"} {\"correlation_id\":\"b561c5d5-e6e3-4091-9517-b0458190a360\",\"trace_id\":\"bf9679fb-b785-40cb-b814-04a1dc385a1c\"}\n[2026-04-27 10:27:11] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage for command {\"command\":\"mailbox:batch:create\",\"memoryBeforeCommandInMb\":60.0,\"memoryAfterCommandInMB\":62.0,\"memoryPeakBeforeCommandInMb\":99.727,\"memoryPeakAfterCommandInMB\":99.727} {\"correlation_id\":\"b561c5d5-e6e3-4091-9517-b0458190a360\",\"trace_id\":\"bf9679fb-b785-40cb-b814-04a1dc385a1c\"}\n[2026-04-27 10:27:14] local.INFO: [Jiminny\\Jobs\\Mailbox\\CreateBatches] processed 2 inboxes and created 1 batches {\"userId\":null,\"batchSize\":30,\"maxBatches\":1000} {\"correlation_id\":\"98416296-43c2-4834-927e-4a0e86dc99cf\",\"trace_id\":\"bf9679fb-b785-40cb-b814-04a1dc385a1c\"}\n[2026-04-27 10:28:04] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage before starting command {\"command\":\"meeting-bot:schedule-bot\",\"memoryBeforeCommandInMb\":60.0,\"memoryPeakBeforeCommandInMb\":99.727} {\"correlation_id\":\"5852bcf1-af92-4d24-8a2d-08a9d4a5cfac\",\"trace_id\":\"25af5e8c-a148-493e-ac9f-4d53f1960177\"}\n[2026-04-27 10:28:04] local.INFO: [ScheduleBotCommand] Number of activities to be captured: 0 {\"correlation_id\":\"5852bcf1-af92-4d24-8a2d-08a9d4a5cfac\",\"trace_id\":\"25af5e8c-a148-493e-ac9f-4d53f1960177\"}\n[2026-04-27 10:28:04] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage for command {\"command\":\"meeting-bot:schedule-bot\",\"memoryBeforeCommandInMb\":60.0,\"memoryAfterCommandInMB\":62.0,\"memoryPeakBeforeCommandInMb\":99.727,\"memoryPeakAfterCommandInMB\":99.727} {\"correlation_id\":\"5852bcf1-af92-4d24-8a2d-08a9d4a5cfac\",\"trace_id\":\"25af5e8c-a148-493e-ac9f-4d53f1960177\"}\n[2026-04-27 10:28:06] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage before starting command {\"command\":\"dialers:monitor-activities\",\"memoryBeforeCommandInMb\":60.0,\"memoryPeakBeforeCommandInMb\":99.727} {\"correlation_id\":\"cc1cff1f-71b4-4bb1-800f-66e27c0f2b3a\",\"trace_id\":\"c03103a3-9ab8-480b-9753-7c7c6488f246\"}\n[2026-04-27 10:28:06] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage for command {\"command\":\"dialers:monitor-activities\",\"memoryBeforeCommandInMb\":60.0,\"memoryAfterCommandInMB\":62.0,\"memoryPeakBeforeCommandInMb\":99.727,\"memoryPeakAfterCommandInMB\":99.727} {\"correlation_id\":\"cc1cff1f-71b4-4bb1-800f-66e27c0f2b3a\",\"trace_id\":\"c03103a3-9ab8-480b-9753-7c7c6488f246\"}\n[2026-04-27 10:28:09] local.ERROR: Target class [Jiminny\\Repositories\\AjReportsRepository] does not exist. {\"exception\":\"[object] (Illuminate\\\\Contracts\\\\Container\\\\BindingResolutionException(code: 0): Target class [Jiminny\\\\Repositories\\\\AjReportsRepository] does not exist. at /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php:1124)\n[stacktrace]\n#0 /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php(933): Illuminate\\\\Container\\\\Container->build('Jiminny\\\\\\\\Reposit...')\n#1 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Application.php(1078): Illuminate\\\\Container\\\\Container->resolve('Jiminny\\\\\\\\Reposit...', Array, true)\n#2 /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php(864): Illuminate\\\\Foundation\\\\Application->resolve('Jiminny\\\\\\\\Reposit...', Array)\n#3 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Application.php(1058): Illuminate\\\\Container\\\\Container->make('Jiminny\\\\\\\\Reposit...', Array)\n#4 /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php(1336): Illuminate\\\\Foundation\\\\Application->make('Jiminny\\\\\\\\Reposit...')\n#5 /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php(1237): Illuminate\\\\Container\\\\Container->resolveClass(Object(ReflectionParameter))\n#6 /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php(1162): Illuminate\\\\Container\\\\Container->resolveDependencies(Array)\n#7 /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php(933): Illuminate\\\\Container\\\\Container->build('Jiminny\\\\\\\\Http\\\\\\\\Co...')\n#8 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Application.php(1078): Illuminate\\\\Container\\\\Container->resolve('Jiminny\\\\\\\\Http\\\\\\\\Co...', Array, true)\n#9 /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php(864): Illuminate\\\\Foundation\\\\Application->resolve('Jiminny\\\\\\\\Http\\\\\\\\Co...', Array)\n#10 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Application.php(1058): Illuminate\\\\Container\\\\Container->make('Jiminny\\\\\\\\Http\\\\\\\\Co...', Array)\n#11 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Route.php(286): Illuminate\\\\Foundation\\\\Application->make('Jiminny\\\\\\\\Http\\\\\\\\Co...')\n#12 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Route.php(1133): Illuminate\\\\Routing\\\\Route->getController()\n#13 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Route.php(1062): Illuminate\\\\Routing\\\\Route->controllerMiddleware()\n#14 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Router.php(834): Illuminate\\\\Routing\\\\Route->gatherMiddleware()\n#15 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Router.php(816): Illuminate\\\\Routing\\\\Router->gatherRouteMiddleware(Object(Illuminate\\\\Routing\\\\Route))\n#16 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Router.php(800): Illuminate\\\\Routing\\\\Router->runRouteWithinStack(Object(Illuminate\\\\Routing\\\\Route), Object(Illuminate\\\\Http\\\\Request))\n#17 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Router.php(764): Illuminate\\\\Routing\\\\Router->runRoute(Object(Illuminate\\\\Http\\\\Request), Object(Illuminate\\\\Routing\\\\Route))\n#18 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Router.php(753): Illuminate\\\\Routing\\\\Router->dispatchToRoute(Object(Illuminate\\\\Http\\\\Request))\n#19 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Http/Kernel.php(200): Illuminate\\\\Routing\\\\Router->dispatch(Object(Illuminate\\\\Http\\\\Request))\n#20 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(180): Illuminate\\\\Foundation\\\\Http\\\\Kernel->Illuminate\\\\Foundation\\\\Http\\\\{closure}(Object(Illuminate\\\\Http\\\\Request))\n#21 /home/jiminny/vendor/barryvdh/laravel-debugbar/src/Middleware/InjectDebugbar.php(59): Illuminate\\\\Pipeline\\\\Pipeline->Illuminate\\\\Pipeline\\\\{closure}(Object(Illuminate\\\\Http\\\\Request))\n#22 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(219): Barryvdh\\\\Debugbar\\\\Middleware\\\\InjectDebugbar->handle(Object(Illuminate\\\\Http\\\\Request), Object(Closure))\n#23 /home/jiminny/vendor/laravel/framework/src/Illuminate/Http/Middleware/HandleCors.php(74): Illuminate\\\\Pipeline\\\\Pipeline->Illuminate\\\\Pipeline\\\\{closure}(Object(Illuminate\\\\Http\\\\Request))\n#24 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(219): Illuminate\\\\Http\\\\Middleware\\\\HandleCors->handle(Object(Illuminate\\\\Http\\\\Request), Object(Closure))\n#25 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Http/Middleware/TransformsRequest.php(21): Illuminate\\\\Pipeline\\\\Pipeline->Illuminate\\\\Pipeline\\\\{closure}(Object(Illuminate\\\\Http\\\\Request))\n#26 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Http/Middleware/TrimStrings.php(51): Illuminate\\\\Foundation\\\\Http\\\\Middleware\\\\TransformsRequest->handle(Object(Illuminate\\\\Http\\\\Request), Object(Closure))\n#27 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(219): Illuminate\\\\Foundation\\\\Http\\\\Middleware\\\\TrimStrings->handle(Object(Illuminate\\\\Http\\\\Request), Object(Closure))\n#28 /home/jiminny/vendor/bepsvpt/secure-headers/src/SecureHeadersMiddleware.php(18): Illuminate\\\\Pipeline\\\\Pipeline->Illuminate\\\\Pipeline\\\\{closure}(Object(Illuminate\\\\Http\\\\Request))\n#29 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(219): Bepsvpt\\\\SecureHeaders\\\\SecureHeadersMiddleware->handle(Object(Illuminate\\\\Http\\\\Request), Object(Closure))\n#30 /home/jiminny/app/Http/Middleware/SentryContext.php(60): Illuminate\\\\Pipeline\\\\Pipeline->Illuminate\\\\Pipeline\\\\{closure}(Object(Illuminate\\\\Http\\\\Request))\n#31 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(219): Jiminny\\\\Http\\\\Middleware\\\\SentryContext->handle(Object(Illuminate\\\\Http\\\\Request), Object(Closure))\n#32 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Http/Middleware/PreventRequestsDuringMaintenance.php(109): Illuminate\\\\Pipeline\\\\Pipeline->Illuminate\\\\Pipeline\\\\{closure}(Object(Illuminate\\\\Http\\\\Request))\n#33 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(219): Illuminate\\\\Foundation\\\\Http\\\\Middleware\\\\PreventRequestsDuringMaintenance->handle(Object(Illuminate\\\\Http\\\\Request), Object(Closure))\n#34 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Http/Middleware/InvokeDeferredCallbacks.php(22): Illuminate\\\\Pipeline\\\\Pipeline->Illuminate\\\\Pipeline\\\\{closure}(Object(Illuminate\\\\Http\\\\Request))\n#35 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(219): Illuminate\\\\Foundation\\\\Http\\\\Middleware\\\\InvokeDeferredCallbacks->handle(Object(Illuminate\\\\Http\\\\Request), Object(Closure))\n#36 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(137): Illuminate\\\\Pipeline\\\\Pipeline->Illuminate\\\\Pipeline\\\\{closure}(Object(Illuminate\\\\Http\\\\Request))\n#37 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Http/Kernel.php(175): Illuminate\\\\Pipeline\\\\Pipeline->then(Object(Closure))\n#38 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Http/Kernel.php(144): Illuminate\\\\Foundation\\\\Http\\\\Kernel->sendRequestThroughRouter(Object(Illuminate\\\\Http\\\\Request))\n#39 /home/jiminny/public/index.php(51): Illuminate\\\\Foundation\\\\Http\\\\Kernel->handle(Object(Illuminate\\\\Http\\\\Request))\n#40 {main}\n\n[previous exception] [object] (ReflectionException(code: -1): Class \\\"Jiminny\\\\Repositories\\\\AjReportsRepository\\\" does not exist at /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php:1122)\n[stacktrace]\n#0 /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php(1122): ReflectionClass->__construct('Jiminny\\\\\\\\Reposit...')\n#1 /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php(933): Illuminate\\\\Container\\\\Container->build('Jiminny\\\\\\\\Reposit...')\n#2 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Application.php(1078): Illuminate\\\\Container\\\\Container->resolve('Jiminny\\\\\\\\Reposit...', Array, true)\n#3 /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php(864): Illuminate\\\\Foundation\\\\Application->resolve('Jiminny\\\\\\\\Reposit...', Array)\n#4 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Application.php(1058): Illuminate\\\\Container\\\\Container->make('Jiminny\\\\\\\\Reposit...', Array)\n#5 /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php(1336): Illuminate\\\\Foundation\\\\Application->make('Jiminny\\\\\\\\Reposit...')\n#6 /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php(1237): Illuminate\\\\Container\\\\Container->resolveClass(Object(ReflectionParameter))\n#7 /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php(1162): Illuminate\\\\Container\\\\Container->resolveDependencies(Array)\n#8 /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php(933): Illuminate\\\\Container\\\\Container->build('Jiminny\\\\\\\\Http\\\\\\\\Co...')\n#9 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Application.php(1078): Illuminate\\\\Container\\\\Container->resolve('Jiminny\\\\\\\\Http\\\\\\\\Co...', Array, true)\n#10 /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php(864): Illuminate\\\\Foundation\\\\Application->resolve('Jiminny\\\\\\\\Http\\\\\\\\Co...', Array)\n#11 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Application.php(1058): Illuminate\\\\Container\\\\Container->make('Jiminny\\\\\\\\Http\\\\\\\\Co...', Array)\n#12 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Route.php(286): Illuminate\\\\Foundation\\\\Application->make('Jiminny\\\\\\\\Http\\\\\\\\Co...')\n#13 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Route.php(1133): Illuminate\\\\Routing\\\\Route->getController()\n#14 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Route.php(1062): Illuminate\\\\Routing\\\\Route->controllerMiddleware()\n#15 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Router.php(834): Illuminate\\\\Routing\\\\Route->gatherMiddleware()\n#16 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Router.php(816): Illuminate\\\\Routing\\\\Router->gatherRouteMiddleware(Object(Illuminate\\\\Routing\\\\Route))\n#17 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Router.php(800): Illuminate\\\\Routing\\\\Router->runRouteWithinStack(Object(Illuminate\\\\Routing\\\\Route), Object(Illuminate\\\\Http\\\\Request))\n#18 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Router.php(764): Illuminate\\\\Routing\\\\Router->runRoute(Object(Illuminate\\\\Http\\\\Request), Object(Illuminate\\\\Routing\\\\Route))\n#19 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Router.php(753): Illuminate\\\\Routing\\\\Router->dispatchToRoute(Object(Illuminate\\\\Http\\\\Request))\n#20 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Http/Kernel.php(200): Illuminate\\\\Routing\\\\Router->dispatch(Object(Illuminate\\\\Http\\\\Request))\n#21 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(180): Illuminate\\\\Foundation\\\\Http\\\\Kernel->Illuminate\\\\Foundation\\\\Http\\\\{closure}(Object(Illuminate\\\\Http\\\\Request))\n#22 /home/jiminny/vendor/barryvdh/laravel-debugbar/src/Middleware/InjectDebugbar.php(59): Illuminate\\\\Pipeline\\\\Pipeline->Illuminate\\\\Pipeline\\\\{closure}(Object(Illuminate\\\\Http\\\\Request))\n#23 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(219): Barryvdh\\\\Debugbar\\\\Middleware\\\\InjectDebugbar->handle(Object(Illuminate\\\\Http\\\\Request), Object(Closure))\n#24 /home/jiminny/vendor/laravel/framework/src/Illuminate/Http/Middleware/HandleCors.php(74): Illuminate\\\\Pipeline\\\\Pipeline->Illuminate\\\\Pipeline\\\\{closure}(Object(Illuminate\\\\Http\\\\Request))\n#25 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(219): Illuminate\\\\Http\\\\Middleware\\\\HandleCors->handle(Object(Illuminate\\\\Http\\\\Request), Object(Closure))\n#26 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Http/Middleware/TransformsRequest.php(21): Illuminate\\\\Pipeline\\\\Pipeline->Illuminate\\\\Pipeline\\\\{closure}(Object(Illuminate\\\\Http\\\\Request))\n#27 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Http/Middleware/TrimStrings.php(51): Illuminate\\\\Foundation\\\\Http\\\\Middleware\\\\TransformsRequest->handle(Object(Illuminate\\\\Http\\\\Request), Object(Closure))\n#28 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(219): Illuminate\\\\Foundation\\\\Http\\\\Middleware\\\\TrimStrings->handle(Object(Illuminate\\\\Http\\\\Request), Object(Closure))\n#29 /home/jiminny/vendor/bepsvpt/secure-headers/src/SecureHeadersMiddleware.php(18): Illuminate\\\\Pipeline\\\\Pipeline->Illuminate\\\\Pipeline\\\\{closure}(Object(Illuminate\\\\Http\\\\Request))\n#30 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(219): Bepsvpt\\\\SecureHeaders\\\\SecureHeadersMiddleware->handle(Object(Illuminate\\\\Http\\\\Request), Object(Closure))\n#31 /home/jiminny/app/Http/Middleware/SentryContext.php(60): Illuminate\\\\Pipeline\\\\Pipeline->Illuminate\\\\Pipeline\\\\{closure}(Object(Illuminate\\\\Http\\\\Request))\n#32 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(219): Jiminny\\\\Http\\\\Middleware\\\\SentryContext->handle(Object(Illuminate\\\\Http\\\\Request), Object(Closure))\n#33 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Http/Middleware/PreventRequestsDuringMaintenance.php(109): Illuminate\\\\Pipeline\\\\Pipeline->Illuminate\\\\Pipeline\\\\{closure}(Object(Illuminate\\\\Http\\\\Request))\n#34 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(219): Illuminate\\\\Foundation\\\\Http\\\\Middleware\\\\PreventRequestsDuringMaintenance->handle(Object(Illuminate\\\\Http\\\\Request), Object(Closure))\n#35 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Http/Middleware/InvokeDeferredCallbacks.php(22): Illuminate\\\\Pipeline\\\\Pipeline->Illuminate\\\\Pipeline\\\\{closure}(Object(Illuminate\\\\Http\\\\Request))\n#36 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(219): Illuminate\\\\Foundation\\\\Http\\\\Middleware\\\\InvokeDeferredCallbacks->handle(Object(Illuminate\\\\Http\\\\Request), Object(Closure))\n#37 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(137): Illuminate\\\\Pipeline\\\\Pipeline->Illuminate\\\\Pipeline\\\\{closure}(Object(Illuminate\\\\Http\\\\Request))\n#38 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Http/Kernel.php(175): Illuminate\\\\Pipeline\\\\Pipeline->then(Object(Closure))\n#39 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Http/Kernel.php(144): Illuminate\\\\Foundation\\\\Http\\\\Kernel->sendRequestThroughRouter(Object(Illuminate\\\\Http\\\\Request))\n#40 /home/jiminny/public/index.php(51): Illuminate\\\\Foundation\\\\Http\\\\Kernel->handle(Object(Illuminate\\\\Http\\\\Request))\n#41 {main}\n\"} {\"correlation_id\":\"dfad8817-1dde-4622-8320-f5fc1616a8fe\",\"trace_id\":\"d21232b2-60fb-4ca0-838c-ba42e61593f9\"}\n[2026-04-27 10:28:09] local.NOTICE: Monitoring start {\"correlation_id\":\"93744c67-4444-41ff-aaad-6aa9cafe294e\",\"trace_id\":\"80e396b0-b1ac-4ec8-838c-00021f98072c\"}\n[2026-04-27 10:28:09] local.NOTICE: Monitoring end {\"correlation_id\":\"93744c67-4444-41ff-aaad-6aa9cafe294e\",\"trace_id\":\"80e396b0-b1ac-4ec8-838c-00021f98072c\"}\n[2026-04-27 10:28:10] local.ERROR: Target class [Jiminny\\Repositories\\AjReportsRepository] does not exist. {\"exception\":\"[object] (Illuminate\\\\Contracts\\\\Container\\\\BindingResolutionException(code: 0): Target class [Jiminny\\\\Repositories\\\\AjReportsRepository] does not exist. at /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php:1124)\n[stacktrace]\n#0 /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php(933): Illuminate\\\\Container\\\\Container->build('Jiminny\\\\\\\\Reposit...')\n#1 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Application.php(1078): Illuminate\\\\Container\\\\Container->resolve('Jiminny\\\\\\\\Reposit...', Array, true)\n#2 /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php(864): Illuminate\\\\Foundation\\\\Application->resolve('Jiminny\\\\\\\\Reposit...', Array)\n#3 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Application.php(1058): Illuminate\\\\Container\\\\Container->make('Jiminny\\\\\\\\Reposit...', Array)\n#4 /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php(1336): Illuminate\\\\Foundation\\\\Application->make('Jiminny\\\\\\\\Reposit...')\n#5 /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php(1237): Illuminate\\\\Container\\\\Container->resolveClass(Object(ReflectionParameter))\n#6 /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php(1162): Illuminate\\\\Container\\\\Container->resolveDependencies(Array)\n#7 /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php(933): Illuminate\\\\Container\\\\Container->build('Jiminny\\\\\\\\Http\\\\\\\\Co...')\n#8 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Application.php(1078): Illuminate\\\\Container\\\\Container->resolve('Jiminny\\\\\\\\Http\\\\\\\\Co...', Array, true)\n#9 /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php(864): Illuminate\\\\Foundation\\\\Application->resolve('Jiminny\\\\\\\\Http\\\\\\\\Co...', Array)\n#10 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Application.php(1058): Illuminate\\\\Container\\\\Container->make('Jiminny\\\\\\\\Http\\\\\\\\Co...', Array)\n#11 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Route.php(286): Illuminate\\\\Foundation\\\\Application->make('Jiminny\\\\\\\\Http\\\\\\\\Co...')\n#12 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Route.php(1133): Illuminate\\\\Routing\\\\Route->getController()\n#13 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Route.php(1062): Illuminate\\\\Routing\\\\Route->controllerMiddleware()\n#14 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Router.php(834): Illuminate\\\\Routing\\\\Route->gatherMiddleware()\n#15 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Router.php(816): Illuminate\\\\Routing\\\\Router->gatherRouteMiddleware(Object(Illuminate\\\\Routing\\\\Route))\n#16 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Router.php(800): Illuminate\\\\Routing\\\\Router->runRouteWithinStack(Object(Illuminate\\\\Routing\\\\Route), Object(Illuminate\\\\Http\\\\Request))\n#17 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Router.php(764): Illuminate\\\\Routing\\\\Router->runRoute(Object(Illuminate\\\\Http\\\\Request), Object(Illuminate\\\\Routing\\\\Route))\n#18 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Router.php(753): Illuminate\\\\Routing\\\\Router->dispatchToRoute(Object(Illuminate\\\\Http\\\\Request))\n#19 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Http/Kernel.php(200): Illuminate\\\\Routing\\\\Router->dispatch(Object(Illuminate\\\\Http\\\\Request))\n#20 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(180): Illuminate\\\\Foundation\\\\Http\\\\Kernel->Illuminate\\\\Foundation\\\\Http\\\\{closure}(Object(Illuminate\\\\Http\\\\Request))\n#21 /home/jiminny/vendor/barryvdh/laravel-debugbar/src/Middleware/InjectDebugbar.php(59): Illuminate\\\\Pipeline\\\\Pipeline->Illuminate\\\\Pipeline\\\\{closure}(Object(Illuminate\\\\Http\\\\Request))\n#22 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(219): Barryvdh\\\\Debugbar\\\\Middleware\\\\InjectDebugbar->handle(Object(Illuminate\\\\Http\\\\Request), Object(Closure))\n#23 /home/jiminny/vendor/laravel/framework/src/Illuminate/Http/Middleware/HandleCors.php(74): Illuminate\\\\Pipeline\\\\Pipeline->Illuminate\\\\Pipeline\\\\{closure}(Object(Illuminate\\\\Http\\\\Request))\n#24 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(219): Illuminate\\\\Http\\\\Middleware\\\\HandleCors->handle(Object(Illuminate\\\\Http\\\\Request), Object(Closure))\n#25 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Http/Middleware/TransformsRequest.php(21): Illuminate\\\\Pipeline\\\\Pipeline->Illuminate\\\\Pipeline\\\\{closure}(Object(Illuminate\\\\Http\\\\Request))\n#26 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Http/Middleware/TrimStrings.php(51): Illuminate\\\\Foundation\\\\Http\\\\Middleware\\\\TransformsRequest->handle(Object(Illuminate\\\\Http\\\\Request), Object(Closure))\n#27 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(219): Illuminate\\\\Foundation\\\\Http\\\\Middleware\\\\TrimStrings->handle(Object(Illuminate\\\\Http\\\\Request), Object(Closure))\n#28 /home/jiminny/vendor/bepsvpt/secure-headers/src/SecureHeadersMiddleware.php(18): Illuminate\\\\Pipeline\\\\Pipeline->Illuminate\\\\Pipeline\\\\{closure}(Object(Illuminate\\\\Http\\\\Request))\n#29 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(219): Bepsvpt\\\\SecureHeaders\\\\SecureHeadersMiddleware->handle(Object(Illuminate\\\\Http\\\\Request), Object(Closure))\n#30 /home/jiminny/app/Http/Middleware/SentryContext.php(60): Illuminate\\\\Pipeline\\\\Pipeline->Illuminate\\\\Pipeline\\\\{closure}(Object(Illuminate\\\\Http\\\\Request))\n#31 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(219): Jiminny\\\\Http\\\\Middleware\\\\SentryContext->handle(Object(Illuminate\\\\Http\\\\Request), Object(Closure))\n#32 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Http/Middleware/PreventRequestsDuringMaintenance.php(109): Illuminate\\\\Pipeline\\\\Pipeline->Illuminate\\\\Pipeline\\\\{closure}(Object(Illuminate\\\\Http\\\\Request))\n#33 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(219): Illuminate\\\\Foundation\\\\Http\\\\Middleware\\\\PreventRequestsDuringMaintenance->handle(Object(Illuminate\\\\Http\\\\Request), Object(Closure))\n#34 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Http/Middleware/InvokeDeferredCallbacks.php(22): Illuminate\\\\Pipeline\\\\Pipeline->Illuminate\\\\Pipeline\\\\{closure}(Object(Illuminate\\\\Http\\\\Request))\n#35 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(219): Illuminate\\\\Foundation\\\\Http\\\\Middleware\\\\InvokeDeferredCallbacks->handle(Object(Illuminate\\\\Http\\\\Request), Object(Closure))\n#36 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(137): Illuminate\\\\Pipeline\\\\Pipeline->Illuminate\\\\Pipeline\\\\{closure}(Object(Illuminate\\\\Http\\\\Request))\n#37 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Http/Kernel.php(175): Illuminate\\\\Pipeline\\\\Pipeline->then(Object(Closure))\n#38 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Http/Kernel.php(144): Illuminate\\\\Foundation\\\\Http\\\\Kernel->sendRequestThroughRouter(Object(Illuminate\\\\Http\\\\Request))\n#39 /home/jiminny/public/index.php(51): Illuminate\\\\Foundation\\\\Http\\\\Kernel->handle(Object(Illuminate\\\\Http\\\\Request))\n#40 {main}\n\n[previous exception] [object] (ReflectionException(code: -1): Class \\\"Jiminny\\\\Repositories\\\\AjReportsRepository\\\" does not exist at /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php:1122)\n[stacktrace]\n#0 /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php(1122): ReflectionClass->__construct('Jiminny\\\\\\\\Reposit...')\n#1 /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php(933): Illuminate\\\\Container\\\\Container->build('Jiminny\\\\\\\\Reposit...')\n#2 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Application.php(1078): Illuminate\\\\Container\\\\Container->resolve('Jiminny\\\\\\\\Reposit...', Array, true)\n#3 /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php(864): Illuminate\\\\Foundation\\\\Application->resolve('Jiminny\\\\\\\\Reposit...', Array)\n#4 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Application.php(1058): Illuminate\\\\Container\\\\Container->make('Jiminny\\\\\\\\Reposit...', Array)\n#5 /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php(1336): Illuminate\\\\Foundation\\\\Application->make('Jiminny\\\\\\\\Reposit...')\n#6 /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php(1237): Illuminate\\\\Container\\\\Container->resolveClass(Object(ReflectionParameter))\n#7 /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php(1162): Illuminate\\\\Container\\\\Container->resolveDependencies(Array)\n#8 /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php(933): Illuminate\\\\Container\\\\Container->build('Jiminny\\\\\\\\Http\\\\\\\\Co...')\n#9 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Application.php(1078): Illuminate\\\\Container\\\\Container->resolve('Jiminny\\\\\\\\Http\\\\\\\\Co...', Array, true)\n#10 /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php(864): Illuminate\\\\Foundation\\\\Application->resolve('Jiminny\\\\\\\\Http\\\\\\\\Co...', Array)\n#11 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Application.php(1058): Illuminate\\\\Container\\\\Container->make('Jiminny\\\\\\\\Http\\\\\\\\Co...', Array)\n#12 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Route.php(286): Illuminate\\\\Foundation\\\\Application->make('Jiminny\\\\\\\\Http\\\\\\\\Co...')\n#13 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Route.php(1133): Illuminate\\\\Routing\\\\Route->getController()\n#14 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Route.php(1062): Illuminate\\\\Routing\\\\Route->controllerMiddleware()\n#15 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Router.php(834): Illuminate\\\\Routing\\\\Route->gatherMiddleware()\n#16 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Router.php(816): Illuminate\\\\Routing\\\\Router->gatherRouteMiddleware(Object(Illuminate\\\\Routing\\\\Route))\n#17 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Router.php(800): Illuminate\\\\Routing\\\\Router->runRouteWithinStack(Object(Illuminate\\\\Routing\\\\Route), Object(Illuminate\\\\Http\\\\Request))\n#18 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Router.php(764): Illuminate\\\\Routing\\\\Router->runRoute(Object(Illuminate\\\\Http\\\\Request), Object(Illuminate\\\\Routing\\\\Route))\n#19 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Router.php(753): Illuminate\\\\Routing\\\\Router->dispatchToRoute(Object(Illuminate\\\\Http\\\\Request))\n#20 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Http/Kernel.php(200): Illuminate\\\\Routing\\\\Router->dispatch(Object(Illuminate\\\\Http\\\\Request))\n#21 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(180): Illuminate\\\\Foundation\\\\Http\\\\Kernel->Illuminate\\\\Foundation\\\\Http\\\\{closure}(Object(Illuminate\\\\Http\\\\Request))\n#22 /home/jiminny/vendor/barryvdh/laravel-debugbar/src/Middleware/InjectDebugbar.php(59): Illuminate\\\\Pipeline\\\\Pipeline->Illuminate\\\\Pipeline\\\\{closure}(Object(Illuminate\\\\Http\\\\Request))\n#23 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(219): Barryvdh\\\\Debugbar\\\\Middleware\\\\InjectDebugbar->handle(Object(Illuminate\\\\Http\\\\Request), Object(Closure))\n#24 /home/jiminny/vendor/laravel/framework/src/Illuminate/Http/Middleware/HandleCors.php(74): Illuminate\\\\Pipeline\\\\Pipeline->Illuminate\\\\Pipeline\\\\{closure}(Object(Illuminate\\\\Http\\\\Request))\n#25 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(219): Illuminate\\\\Http\\\\Middleware\\\\HandleCors->handle(Object(Illuminate\\\\Http\\\\Request), Object(Closure))\n#26 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Http/Middleware/TransformsRequest.php(21): Illuminate\\\\Pipeline\\\\Pipeline->Illuminate\\\\Pipeline\\\\{closure}(Object(Illuminate\\\\Http\\\\Request))\n#27 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Http/Middleware/TrimStrings.php(51): Illuminate\\\\Foundation\\\\Http\\\\Middleware\\\\TransformsRequest->handle(Object(Illuminate\\\\Http\\\\Request), Object(Closure))\n#28 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(219): Illuminate\\\\Foundation\\\\Http\\\\Middleware\\\\TrimStrings->handle(Object(Illuminate\\\\Http\\\\Request), Object(Closure))\n#29 /home/jiminny/vendor/bepsvpt/secure-headers/src/SecureHeadersMiddleware.php(18): Illuminate\\\\Pipeline\\\\Pipeline->Illuminate\\\\Pipeline\\\\{closure}(Object(Illuminate\\\\Http\\\\Request))\n#30 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(219): Bepsvpt\\\\SecureHeaders\\\\SecureHeadersMiddleware->handle(Object(Illuminate\\\\Http\\\\Request), Object(Closure))\n#31 /home/jiminny/app/Http/Middleware/SentryContext.php(60): Illuminate\\\\Pipeline\\\\Pipeline->Illuminate\\\\Pipeline\\\\{closure}(Object(Illuminate\\\\Http\\\\Request))\n#32 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(219): Jiminny\\\\Http\\\\Middleware\\\\SentryContext->handle(Object(Illuminate\\\\Http\\\\Request), Object(Closure))\n#33 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Http/Middleware/PreventRequestsDuringMaintenance.php(109): Illuminate\\\\Pipeline\\\\Pipeline->Illuminate\\\\Pipeline\\\\{closure}(Object(Illuminate\\\\Http\\\\Request))\n#34 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(219): Illuminate\\\\Foundation\\\\Http\\\\Middleware\\\\PreventRequestsDuringMaintenance->handle(Object(Illuminate\\\\Http\\\\Request), Object(Closure))\n#35 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Http/Middleware/InvokeDeferredCallbacks.php(22): Illuminate\\\\Pipeline\\\\Pipeline->Illuminate\\\\Pipeline\\\\{closure}(Object(Illuminate\\\\Http\\\\Request))\n#36 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(219): Illuminate\\\\Foundation\\\\Http\\\\Middleware\\\\InvokeDeferredCallbacks->handle(Object(Illuminate\\\\Http\\\\Request), Object(Closure))\n#37 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(137): Illuminate\\\\Pipeline\\\\Pipeline->Illuminate\\\\Pipeline\\\\{closure}(Object(Illuminate\\\\Http\\\\Request))\n#38 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Http/Kernel.php(175): Illuminate\\\\Pipeline\\\\Pipeline->then(Object(Closure))\n#39 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Http/Kernel.php(144): Illuminate\\\\Foundation\\\\Http\\\\Kernel->sendRequestThroughRouter(Object(Illuminate\\\\Http\\\\Request))\n#40 /home/jiminny/public/index.php(51): Illuminate\\\\Foundation\\\\Http\\\\Kernel->handle(Object(Illuminate\\\\Http\\\\Request))\n#41 {main}\n\"} {\"correlation_id\":\"de768781-5c05-418c-baf3-79f77a8c8694\",\"trace_id\":\"fec74062-3f6d-4cfc-968a-1bb285496db3\"}\n[2026-04-27 10:28:12] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage before starting command {\"command\":\"mailbox:skip-lists:refresh\",\"memoryBeforeCommandInMb\":60.0,\"memoryPeakBeforeCommandInMb\":99.727} {\"correlation_id\":\"907d15ed-8c84-41a1-a473-590eebb55dbc\",\"trace_id\":\"ec7bd353-516e-491d-89dd-de44136c1672\"}\n[2026-04-27 10:28:12] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage for command {\"command\":\"mailbox:skip-lists:refresh\",\"memoryBeforeCommandInMb\":60.0,\"memoryAfterCommandInMB\":62.0,\"memoryPeakBeforeCommandInMb\":99.727,\"memoryPeakAfterCommandInMB\":99.727} {\"correlation_id\":\"907d15ed-8c84-41a1-a473-590eebb55dbc\",\"trace_id\":\"ec7bd353-516e-491d-89dd-de44136c1672\"}\n[2026-04-27 10:28:13] local.ERROR: Target class [Jiminny\\Repositories\\AjReportsRepository] does not exist. {\"exception\":\"[object] (Illuminate\\\\Contracts\\\\Container\\\\BindingResolutionException(code: 0): Target class [Jiminny\\\\Repositories\\\\AjReportsRepository] does not exist. at /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php:1124)\n[stacktrace]\n#0 /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php(933): Illuminate\\\\Container\\\\Container->build('Jiminny\\\\\\\\Reposit...')\n#1 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Application.php(1078): Illuminate\\\\Container\\\\Container->resolve('Jiminny\\\\\\\\Reposit...', Array, true)\n#2 /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php(864): Illuminate\\\\Foundation\\\\Application->resolve('Jiminny\\\\\\\\Reposit...', Array)\n#3 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Application.php(1058): Illuminate\\\\Container\\\\Container->make('Jiminny\\\\\\\\Reposit...', Array)\n#4 /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php(1336): Illuminate\\\\Foundation\\\\Application->make('Jiminny\\\\\\\\Reposit...')\n#5 /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php(1237): Illuminate\\\\Container\\\\Container->resolveClass(Object(ReflectionParameter))\n#6 /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php(1162): Illuminate\\\\Container\\\\Container->resolveDependencies(Array)\n#7 /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php(933): Illuminate\\\\Container\\\\Container->build('Jiminny\\\\\\\\Http\\\\\\\\Co...')\n#8 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Application.php(1078): Illuminate\\\\Container\\\\Container->resolve('Jiminny\\\\\\\\Http\\\\\\\\Co...', Array, true)\n#9 /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php(864): Illuminate\\\\Foundation\\\\Application->resolve('Jiminny\\\\\\\\Http\\\\\\\\Co...', Array)\n#10 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Application.php(1058): Illuminate\\\\Container\\\\Container->make('Jiminny\\\\\\\\Http\\\\\\\\Co...', Array)\n#11 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Route.php(286): Illuminate\\\\Foundation\\\\Application->make('Jiminny\\\\\\\\Http\\\\\\\\Co...')\n#12 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Route.php(1133): Illuminate\\\\Routing\\\\Route->getController()\n#13 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Route.php(1062): Illuminate\\\\Routing\\\\Route->controllerMiddleware()\n#14 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Router.php(834): Illuminate\\\\Routing\\\\Route->gatherMiddleware()\n#15 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Router.php(816): Illuminate\\\\Routing\\\\Router->gatherRouteMiddleware(Object(Illuminate\\\\Routing\\\\Route))\n#16 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Router.php(800): Illuminate\\\\Routing\\\\Router->runRouteWithinStack(Object(Illuminate\\\\Routing\\\\Route), Object(Illuminate\\\\Http\\\\Request))\n#17 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Router.php(764): Illuminate\\\\Routing\\\\Router->runRoute(Object(Illuminate\\\\Http\\\\Request), Object(Illuminate\\\\Routing\\\\Route))\n#18 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Router.php(753): Illuminate\\\\Routing\\\\Router->dispatchToRoute(Object(Illuminate\\\\Http\\\\Request))\n#19 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Http/Kernel.php(200): Illuminate\\\\Routing\\\\Router->dispatch(Object(Illuminate\\\\Http\\\\Request))\n#20 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(180): Illuminate\\\\Foundation\\\\Http\\\\Kernel->Illuminate\\\\Foundation\\\\Http\\\\{closure}(Object(Illuminate\\\\Http\\\\Request))\n#21 /home/jiminny/vendor/barryvdh/laravel-debugbar/src/Middleware/InjectDebugbar.php(59): Illuminate\\\\Pipeline\\\\Pipeline->Illuminate\\\\Pipeline\\\\{closure}(Object(Illuminate\\\\Http\\\\Request))\n#22 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(219): Barryvdh\\\\Debugbar\\\\Middleware\\\\InjectDebugbar->handle(Object(Illuminate\\\\Http\\\\Request), Object(Closure))\n#23 /home/jiminny/vendor/laravel/framework/src/Illuminate/Http/Middleware/HandleCors.php(74): Illuminate\\\\Pipeline\\\\Pipeline->Illuminate\\\\Pipeline\\\\{closure}(Object(Illuminate\\\\Http\\\\Request))\n#24 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(219): Illuminate\\\\Http\\\\Middleware\\\\HandleCors->handle(Object(Illuminate\\\\Http\\\\Request), Object(Closure))\n#25 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Http/Middleware/TransformsRequest.php(21): Illuminate\\\\Pipeline\\\\Pipeline->Illuminate\\\\Pipeline\\\\{closure}(Object(Illuminate\\\\Http\\\\Request))\n#26 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Http/Middleware/TrimStrings.php(51): Illuminate\\\\Foundation\\\\Http\\\\Middleware\\\\TransformsRequest->handle(Object(Illuminate\\\\Http\\\\Request), Object(Closure))\n#27 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(219): Illuminate\\\\Foundation\\\\Http\\\\Middleware\\\\TrimStrings->handle(Object(Illuminate\\\\Http\\\\Request), Object(Closure))\n#28 /home/jiminny/vendor/bepsvpt/secure-headers/src/SecureHeadersMiddleware.php(18): Illuminate\\\\Pipeline\\\\Pipeline->Illuminate\\\\Pipeline\\\\{closure}(Object(Illuminate\\\\Http\\\\Request))\n#29 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(219): Bepsvpt\\\\SecureHeaders\\\\SecureHeadersMiddleware->handle(Object(Illuminate\\\\Http\\\\Request), Object(Closure))\n#30 /home/jiminny/app/Http/Middleware/SentryContext.php(60): Illuminate\\\\Pipeline\\\\Pipeline->Illuminate\\\\Pipeline\\\\{closure}(Object(Illuminate\\\\Http\\\\Request))\n#31 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(219): Jiminny\\\\Http\\\\Middleware\\\\SentryContext->handle(Object(Illuminate\\\\Http\\\\Request), Object(Closure))\n#32 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Http/Middleware/PreventRequestsDuringMaintenance.php(109): Illuminate\\\\Pipeline\\\\Pipeline->Illuminate\\\\Pipeline\\\\{closure}(Object(Illuminate\\\\Http\\\\Request))\n#33 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(219): Illuminate\\\\Foundation\\\\Http\\\\Middleware\\\\PreventRequestsDuringMaintenance->handle(Object(Illuminate\\\\Http\\\\Request), Object(Closure))\n#34 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Http/Middleware/InvokeDeferredCallbacks.php(22): Illuminate\\\\Pipeline\\\\Pipeline->Illuminate\\\\Pipeline\\\\{closure}(Object(Illuminate\\\\Http\\\\Request))\n#35 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(219): Illuminate\\\\Foundation\\\\Http\\\\Middleware\\\\InvokeDeferredCallbacks->handle(Object(Illuminate\\\\Http\\\\Request), Object(Closure))\n#36 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(137): Illuminate\\\\Pipeline\\\\Pipeline->Illuminate\\\\Pipeline\\\\{closure}(Object(Illuminate\\\\Http\\\\Request))\n#37 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Http/Kernel.php(175): Illuminate\\\\Pipeline\\\\Pipeline->then(Object(Closure))\n#38 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Http/Kernel.php(144): Illuminate\\\\Foundation\\\\Http\\\\Kernel->sendRequestThroughRouter(Object(Illuminate\\\\Http\\\\Request))\n#39 /home/jiminny/public/index.php(51): Illuminate\\\\Foundation\\\\Http\\\\Kernel->handle(Object(Illuminate\\\\Http\\\\Request))\n#40 {main}\n\n[previous exception] [object] (ReflectionException(code: -1): Class \\\"Jiminny\\\\Repositories\\\\AjReportsRepository\\\" does not exist at /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php:1122)\n[stacktrace]\n#0 /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php(1122): ReflectionClass->__construct('Jiminny\\\\\\\\Reposit...')\n#1 /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php(933): Illuminate\\\\Container\\\\Container->build('Jiminny\\\\\\\\Reposit...')\n#2 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Application.php(1078): Illuminate\\\\Container\\\\Container->resolve('Jiminny\\\\\\\\Reposit...', Array, true)\n#3 /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php(864): Illuminate\\\\Foundation\\\\Application->resolve('Jiminny\\\\\\\\Reposit...', Array)\n#4 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Application.php(1058): Illuminate\\\\Container\\\\Container->make('Jiminny\\\\\\\\Reposit...', Array)\n#5 /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php(1336): Illuminate\\\\Foundation\\\\Application->make('Jiminny\\\\\\\\Reposit...')\n#6 /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php(1237): Illuminate\\\\Container\\\\Container->resolveClass(Object(ReflectionParameter))\n#7 /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php(1162): Illuminate\\\\Container\\\\Container->resolveDependencies(Array)\n#8 /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php(933): Illuminate\\\\Container\\\\Container->build('Jiminny\\\\\\\\Http\\\\\\\\Co...')\n#9 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Application.php(1078): Illuminate\\\\Container\\\\Container->resolve('Jiminny\\\\\\\\Http\\\\\\\\Co...', Array, true)\n#10 /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php(864): Illuminate\\\\Foundation\\\\Application->resolve('Jiminny\\\\\\\\Http\\\\\\\\Co...', Array)\n#11 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Application.php(1058): Illuminate\\\\Container\\\\Container->make('Jiminny\\\\\\\\Http\\\\\\\\Co...', Array)\n#12 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Route.php(286): Illuminate\\\\Foundation\\\\Application->make('Jiminny\\\\\\\\Http\\\\\\\\Co...')\n#13 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Route.php(1133): Illuminate\\\\Routing\\\\Route->getController()\n#14 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Route.php(1062): Illuminate\\\\Routing\\\\Route->controllerMiddleware()\n#15 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Router.php(834): Illuminate\\\\Routing\\\\Route->gatherMiddleware()\n#16 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Router.php(816): Illuminate\\\\Routing\\\\Router->gatherRouteMiddleware(Object(Illuminate\\\\Routing\\\\Route))\n#17 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Router.php(800): Illuminate\\\\Routing\\\\Router->runRouteWithinStack(Object(Illuminate\\\\Routing\\\\Route), Object(Illuminate\\\\Http\\\\Request))\n#18 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Router.php(764): Illuminate\\\\Routing\\\\Router->runRoute(Object(Illuminate\\\\Http\\\\Request), Object(Illuminate\\\\Routing\\\\Route))\n#19 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Router.php(753): Illuminate\\\\Routing\\\\Router->dispatchToRoute(Object(Illuminate\\\\Http\\\\Request))\n#20 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Http/Kernel.php(200): Illuminate\\\\Routing\\\\Router->dispatch(Object(Illuminate\\\\Http\\\\Request))\n#21 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(180): Illuminate\\\\Foundation\\\\Http\\\\Kernel->Illuminate\\\\Foundation\\\\Http\\\\{closure}(Object(Illuminate\\\\Http\\\\Request))\n#22 /home/jiminny/vendor/barryvdh/laravel-debugbar/src/Middleware/InjectDebugbar.php(59): Illuminate\\\\Pipeline\\\\Pipeline->Illuminate\\\\Pipeline\\\\{closure}(Object(Illuminate\\\\Http\\\\Request))\n#23 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(219): Barryvdh\\\\Debugbar\\\\Middleware\\\\InjectDebugbar->handle(Object(Illuminate\\\\Http\\\\Request), Object(Closure))\n#24 /home/jiminny/vendor/laravel/framework/src/Illuminate/Http/Middleware/HandleCors.php(74): Illuminate\\\\Pipeline\\\\Pipeline->Illuminate\\\\Pipeline\\\\{closure}(Object(Illuminate\\\\Http\\\\Request))\n#25 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(219): Illuminate\\\\Http\\\\Middleware\\\\HandleCors->handle(Object(Illuminate\\\\Http\\\\Request), Object(Closure))\n#26 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Http/Middleware/TransformsRequest.php(21): Illuminate\\\\Pipeline\\\\Pipeline->Illuminate\\\\Pipeline\\\\{closure}(Object(Illuminate\\\\Http\\\\Request))\n#27 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Http/Middleware/TrimStrings.php(51): Illuminate\\\\Foundation\\\\Http\\\\Middleware\\\\TransformsRequest->handle(Object(Illuminate\\\\Http\\\\Request), Object(Closure))\n#28 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(219): Illuminate\\\\Foundation\\\\Http\\\\Middleware\\\\TrimStrings->handle(Object(Illuminate\\\\Http\\\\Request), Object(Closure))\n#29 /home/jiminny/vendor/bepsvpt/secure-headers/src/SecureHeadersMiddleware.php(18): Illuminate\\\\Pipeline\\\\Pipeline->Illuminate\\\\Pipeline\\\\{closure}(Object(Illuminate\\\\Http\\\\Request))\n#30 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(219): Bepsvpt\\\\SecureHeaders\\\\SecureHeadersMiddleware->handle(Object(Illuminate\\\\Http\\\\Request), Object(Closure))\n#31 /home/jiminny/app/Http/Middleware/SentryContext.php(60): Illuminate\\\\Pipeline\\\\Pipeline->Illuminate\\\\Pipeline\\\\{closure}(Object(Illuminate\\\\Http\\\\Request))\n#32 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(219): Jiminny\\\\Http\\\\Middleware\\\\SentryContext->handle(Object(Illuminate\\\\Http\\\\Request), Object(Closure))\n#33 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Http/Middleware/PreventRequestsDuringMaintenance.php(109): Illuminate\\\\Pipeline\\\\Pipeline->Illuminate\\\\Pipeline\\\\{closure}(Object(Illuminate\\\\Http\\\\Request))\n#34 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(219): Illuminate\\\\Foundation\\\\Http\\\\Middleware\\\\PreventRequestsDuringMaintenance->handle(Object(Illuminate\\\\Http\\\\Request), Object(Closure))\n#35 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Http/Middleware/InvokeDeferredCallbacks.php(22): Illuminate\\\\Pipeline\\\\Pipeline->Illuminate\\\\Pipeline\\\\{closure}(Object(Illuminate\\\\Http\\\\Request))\n#36 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(219): Illuminate\\\\Foundation\\\\Http\\\\Middleware\\\\InvokeDeferredCallbacks->handle(Object(Illuminate\\\\Http\\\\Request), Object(Closure))\n#37 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(137): Illuminate\\\\Pipeline\\\\Pipeline->Illuminate\\\\Pipeline\\\\{closure}(Object(Illuminate\\\\Http\\\\Request))\n#38 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Http/Kernel.php(175): Illuminate\\\\Pipeline\\\\Pipeline->then(Object(Closure))\n#39 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Http/Kernel.php(144): Illuminate\\\\Foundation\\\\Http\\\\Kernel->sendRequestThroughRouter(Object(Illuminate\\\\Http\\\\Request))\n#40 /home/jiminny/public/index.php(51): Illuminate\\\\Foundation\\\\Http\\\\Kernel->handle(Object(Illuminate\\\\Http\\\\Request))\n#41 {main}\n\"} {\"correlation_id\":\"d8709bb9-0f8c-4798-a8c7-e68e24ab6265\",\"trace_id\":\"c5bea1cc-53fe-4bda-a8e2-bec002030bc2\"}\n[2026-04-27 10:28:14] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage before starting command {\"command\":\"mailbox:batch:process\",\"memoryBeforeCommandInMb\":60.0,\"memoryPeakBeforeCommandInMb\":99.727} {\"correlation_id\":\"5fb3c8c8-3160-4964-ac56-02d6e2cb998c\",\"trace_id\":\"3a6d5e64-ecf2-4700-98cd-3edd56a1dc34\"}\n[2026-04-27 10:28:14] local.INFO: [EmailSchedule] STARTING batch process {\"host\":\"docker_lamp_1\"} {\"correlation_id\":\"5fb3c8c8-3160-4964-ac56-02d6e2cb998c\",\"trace_id\":\"3a6d5e64-ecf2-4700-98cd-3edd56a1dc34\"}\n[2026-04-27 10:28:14] local.INFO: Processing email batch 98446 for inbox 212 {\"correlation_id\":\"5fb3c8c8-3160-4964-ac56-02d6e2cb998c\",\"trace_id\":\"3a6d5e64-ecf2-4700-98cd-3edd56a1dc34\"}\n[2026-04-27 10:28:14] local.INFO: [SocialAccountService] Fetching token {\"socialAccountId\":1500,\"provider\":\"salesforce\"} {\"correlation_id\":\"5fb3c8c8-3160-4964-ac56-02d6e2cb998c\",\"trace_id\":\"3a6d5e64-ecf2-4700-98cd-3edd56a1dc34\"}\n[2026-04-27 10:28:14] local.INFO: [SocialAccountService] Token retrieved {\"socialAccountId\":1500,\"provider\":\"salesforce\"} {\"correlation_id\":\"5fb3c8c8-3160-4964-ac56-02d6e2cb998c\",\"trace_id\":\"3a6d5e64-ecf2-4700-98cd-3edd56a1dc34\"}\n[2026-04-27 10:28:14] local.INFO: [EncryptedTokenManager] Generating access token. {\"mode\":\"legacy\"} {\"correlation_id\":\"5fb3c8c8-3160-4964-ac56-02d6e2cb998c\",\"trace_id\":\"3a6d5e64-ecf2-4700-98cd-3edd56a1dc34\"}\n[2026-04-27 10:28:14] local.INFO: [CrmOwnerResolver] Integration owner matched as CRM Owner {\"crm_provider\":\"salesforce\",\"crm_owner\":143,\"team_id\":1} {\"correlation_id\":\"5fb3c8c8-3160-4964-ac56-02d6e2cb998c\",\"trace_id\":\"3a6d5e64-ecf2-4700-98cd-3edd56a1dc34\"}\n[2026-04-27 10:28:14] local.INFO: [SocialAccountService] Fetching token {\"socialAccountId\":1354,\"provider\":\"google\"} {\"correlation_id\":\"5fb3c8c8-3160-4964-ac56-02d6e2cb998c\",\"trace_id\":\"3a6d5e64-ecf2-4700-98cd-3edd56a1dc34\"}\n[2026-04-27 10:28:14] local.INFO: [SocialAccountService] Token retrieved {\"socialAccountId\":1354,\"provider\":\"google\"} {\"correlation_id\":\"5fb3c8c8-3160-4964-ac56-02d6e2cb998c\",\"trace_id\":\"3a6d5e64-ecf2-4700-98cd-3edd56a1dc34\"}\n[2026-04-27 10:28:14] local.INFO: [EncryptedTokenManager] Generating access token. {\"mode\":\"legacy\"} {\"correlation_id\":\"5fb3c8c8-3160-4964-ac56-02d6e2cb998c\",\"trace_id\":\"3a6d5e64-ecf2-4700-98cd-3edd56a1dc34\"}\n[2026-04-27 10:28:15] local.INFO: Processing an email from inbox batch {\"batch\":98446,\"inbox_id\":212,\"email\":\"lukas.kovalik@jiminny.com\",\"email_id\":\"19dce788ac964bbf\",\"from\":\"Nikolay Yankov <notifications@github.com>\",\"to\":\"\\\"jiminny/app\\\" <app@noreply.github.com>\",\"cc\":\"Push <push@noreply.github.com>\"} {\"correlation_id\":\"5fb3c8c8-3160-4964-ac56-02d6e2cb998c\",\"trace_id\":\"3a6d5e64-ecf2-4700-98cd-3edd56a1dc34\"}\n[2026-04-27 10:28:15] local.INFO: [SocialAccountService] Fetching token {\"socialAccountId\":1500,\"provider\":\"salesforce\"} {\"correlation_id\":\"5fb3c8c8-3160-4964-ac56-02d6e2cb998c\",\"trace_id\":\"3a6d5e64-ecf2-4700-98cd-3edd56a1dc34\"}\n[2026-04-27 10:28:15] local.INFO: [SocialAccountService] Token retrieved {\"socialAccountId\":1500,\"provider\":\"salesforce\"} {\"correlation_id\":\"5fb3c8c8-3160-4964-ac56-02d6e2cb998c\",\"trace_id\":\"3a6d5e64-ecf2-4700-98cd-3edd56a1dc34\"}\n[2026-04-27 10:28:15] local.INFO: [EncryptedTokenManager] Generating access token. {\"mode\":\"legacy\"} {\"correlation_id\":\"5fb3c8c8-3160-4964-ac56-02d6e2cb998c\",\"trace_id\":\"3a6d5e64-ecf2-4700-98cd-3edd56a1dc34\"}\n[2026-04-27 10:28:15] local.INFO: [CrmOwnerResolver] Integration owner matched as CRM Owner {\"crm_provider\":\"salesforce\",\"crm_owner\":143,\"team_id\":1} {\"correlation_id\":\"5fb3c8c8-3160-4964-ac56-02d6e2cb998c\",\"trace_id\":\"3a6d5e64-ecf2-4700-98cd-3edd56a1dc34\"}\n[2026-04-27 10:28:15] local.INFO: [EmailImport\\ParticipantsResolver] The sender email is blacklisted, skipping {\"email\":\"notifications@github.com\",\"inbox_id\":212,\"message_provider_id\":\"19dce788ac964bbf\",\"team_id\":1} {\"correlation_id\":\"5fb3c8c8-3160-4964-ac56-02d6e2cb998c\",\"trace_id\":\"3a6d5e64-ecf2-4700-98cd-3edd56a1dc34\"}\n[2026-04-27 10:28:15] local.INFO: [EmailImport\\ParticipantsValidator] Email participants are less than 2 {\"inbox_id\":212,\"message_provider_id\":\"19dce788ac964bbf\",\"message_id\":\"<jiminny/app/pull/11998/before/2b8a97d6be8b053441d5a845b72dd04803cbb53c/after/8a68a94d39cc76ae546e19f354e8b0dabd577314@github.com>\"} {\"correlation_id\":\"5fb3c8c8-3160-4964-ac56-02d6e2cb998c\",\"trace_id\":\"3a6d5e64-ecf2-4700-98cd-3edd56a1dc34\"}\n[2026-04-27 10:28:15] local.INFO: Processing an email from inbox batch {\"batch\":98446,\"inbox_id\":212,\"email\":\"lukas.kovalik@jiminny.com\",\"email_id\":\"19dce7883f2fea19\",\"from\":\"The Jiminny Team <no-reply@dev.jiminny.com>\",\"to\":\"lukas.kovalik@jiminny.com\",\"cc\":null} {\"correlation_id\":\"5fb3c8c8-3160-4964-ac56-02d6e2cb998c\",\"trace_id\":\"3a6d5e64-ecf2-4700-98cd-3edd56a1dc34\"}\n[2026-04-27 10:28:15] local.INFO: [SocialAccountService] Fetching token {\"socialAccountId\":1500,\"provider\":\"salesforce\"} {\"correlation_id\":\"5fb3c8c8-3160-4964-ac56-02d6e2cb998c\",\"trace_id\":\"3a6d5e64-ecf2-4700-98cd-3edd56a1dc34\"}\n[2026-04-27 10:28:15] local.INFO: [SocialAccountService] Token retrieved {\"socialAccountId\":1500,\"provider\":\"salesforce\"} {\"correlation_id\":\"5fb3c8c8-3160-4964-ac56-02d6e2cb998c\",\"trace_id\":\"3a6d5e64-ecf2-4700-98cd-3edd56a1dc34\"}\n[2026-04-27 10:28:15] local.INFO: [EncryptedTokenManager] Generating access token. {\"mode\":\"legacy\"} {\"correlation_id\":\"5fb3c8c8-3160-4964-ac56-02d6e2cb998c\",\"trace_id\":\"3a6d5e64-ecf2-4700-98cd-3edd56a1dc34\"}\n[2026-04-27 10:28:15] local.INFO: [CrmOwnerResolver] Integration owner matched as CRM Owner {\"crm_provider\":\"salesforce\",\"crm_owner\":143,\"team_id\":1} {\"correlation_id\":\"5fb3c8c8-3160-4964-ac56-02d6e2cb998c\",\"trace_id\":\"3a6d5e64-ecf2-4700-98cd-3edd56a1dc34\"}\n[2026-04-27 10:28:15] local.INFO: [EmailImport\\ParticipantsResolver] The sender email is blacklisted, skipping {\"email\":\"no-reply@dev.jiminny.com\",\"inbox_id\":212,\"message_provider_id\":\"19dce7883f2fea19\",\"team_id\":1} {\"correlation_id\":\"5fb3c8c8-3160-4964-ac56-02d6e2cb998c\",\"trace_id\":\"3a6d5e64-ecf2-4700-98cd-3edd56a1dc34\"}\n[2026-04-27 10:28:15] local.INFO: [EmailImport\\ParticipantsValidator] Email participants are less than 2 {\"inbox_id\":212,\"message_provider_id\":\"19dce7883f2fea19\",\"message_id\":\"<d70689e8-7b14-42ac-a3bf-5f1e81da943e@mtasv.net>\"} {\"correlation_id\":\"5fb3c8c8-3160-4964-ac56-02d6e2cb998c\",\"trace_id\":\"3a6d5e64-ecf2-4700-98cd-3edd56a1dc34\"}\n[2026-04-27 10:28:15] local.INFO: Processing an email from inbox batch {\"batch\":98446,\"inbox_id\":212,\"email\":\"lukas.kovalik@jiminny.com\",\"email_id\":\"19dce761cb049fba\",\"from\":\"The Jiminny Team <no-reply@dev.jiminny.com>\",\"to\":\"lukas.kovalik@jiminny.com\",\"cc\":null} {\"correlation_id\":\"5fb3c8c8-3160-4964-ac56-02d6e2cb998c\",\"trace_id\":\"3a6d5e64-ecf2-4700-98cd-3edd56a1dc34\"}\n[2026-04-27 10:28:15] local.INFO: [SocialAccountService] Fetching token {\"socialAccountId\":1500,\"provider\":\"salesforce\"} {\"correlation_id\":\"5fb3c8c8-3160-4964-ac56-02d6e2cb998c\",\"trace_id\":\"3a6d5e64-ecf2-4700-98cd-3edd56a1dc34\"}\n[2026-04-27 10:28:15] local.INFO: [SocialAccountService] Token retrieved {\"socialAccountId\":1500,\"provider\":\"salesforce\"} {\"correlation_id\":\"5fb3c8c8-3160-4964-ac56-02d6e2cb998c\",\"trace_id\":\"3a6d5e64-ecf2-4700-98cd-3edd56a1dc34\"}\n[2026-04-27 10:28:15] local.INFO: [EncryptedTokenManager] Generating access token. {\"mode\":\"legacy\"} {\"correlation_id\":\"5fb3c8c8-3160-4964-ac56-02d6e2cb998c\",\"trace_id\":\"3a6d5e64-ecf2-4700-98cd-3edd56a1dc34\"}\n[2026-04-27 10:28:15] local.INFO: [CrmOwnerResolver] Integration owner matched as CRM Owner {\"crm_provider\":\"salesforce\",\"crm_owner\":143,\"team_id\":1} {\"correlation_id\":\"5fb3c8c8-3160-4964-ac56-02d6e2cb998c\",\"trace_id\":\"3a6d5e64-ecf2-4700-98cd-3edd56a1dc34\"}\n[2026-04-27 10:28:15] local.INFO: [EmailImport\\ParticipantsResolver] The sender email is blacklisted, skipping {\"email\":\"no-reply@dev.jiminny.com\",\"inbox_id\":212,\"message_provider_id\":\"19dce761cb049fba\",\"team_id\":1} {\"correlation_id\":\"5fb3c8c8-3160-4964-ac56-02d6e2cb998c\",\"trace_id\":\"3a6d5e64-ecf2-4700-98cd-3edd56a1dc34\"}\n[2026-04-27 10:28:15] local.INFO: [EmailImport\\ParticipantsValidator] Email participants are less than 2 {\"inbox_id\":212,\"message_provider_id\":\"19dce761cb049fba\",\"message_id\":\"<017f0e66-9989-446e-9788-7ae83a99137e@mtasv.net>\"} {\"correlation_id\":\"5fb3c8c8-3160-4964-ac56-02d6e2cb998c\",\"trace_id\":\"3a6d5e64-ecf2-4700-98cd-3edd56a1dc34\"}\n[2026-04-27 10:28:15] local.INFO: Processing an email from inbox batch {\"batch\":98446,\"inbox_id\":212,\"email\":\"lukas.kovalik@jiminny.com\",\"email_id\":\"19dce74ed398d2c1\",\"from\":\"\\\"sonarqubecloud[bot]\\\" <notifications@github.com>\",\"to\":\"\\\"jiminny/app\\\" <app@noreply.github.com>\",\"cc\":\"Lukas Kovalik <kovaliklukas@gmail.com>, Author <author@noreply.github.com>\"} {\"correlation_id\":\"5fb3c8c8-3160-4964-ac56-02d6e2cb998c\",\"trace_id\":\"3a6d5e64-ecf2-4700-98cd-3edd56a1dc34\"}\n[2026-04-27 10:28:15] local.INFO: [SocialAccountService] Fetching token {\"socialAccountId\":1500,\"provider\":\"salesforce\"} {\"correlation_id\":\"5fb3c8c8-3160-4964-ac56-02d6e2cb998c\",\"trace_id\":\"3a6d5e64-ecf2-4700-98cd-3edd56a1dc34\"}\n[2026-04-27 10:28:15] local.INFO: [SocialAccountService] Token retrieved {\"socialAccountId\":1500,\"provider\":\"salesforce\"} {\"correlation_id\":\"5fb3c8c8-3160-4964-ac56-02d6e2cb998c\",\"trace_id\":\"3a6d5e64-ecf2-4700-98cd-3edd56a1dc34\"}\n[2026-04-27 10:28:15] local.INFO: [EncryptedTokenManager] Generating access token. {\"mode\":\"legacy\"} {\"correlation_id\":\"5fb3c8c8-3160-4964-ac56-02d6e2cb998c\",\"trace_id\":\"3a6d5e64-ecf2-4700-98cd-3edd56a1dc34\"}\n[2026-04-27 10:28:15] local.INFO: [CrmOwnerResolver] Integration owner matched as CRM Owner {\"crm_provider\":\"salesforce\",\"crm_owner\":143,\"team_id\":1} {\"correlation_id\":\"5fb3c8c8-3160-4964-ac56-02d6e2cb998c\",\"trace_id\":\"3a6d5e64-ecf2-4700-98cd-3edd56a1dc34\"}\n[2026-04-27 10:28:15] local.INFO: [EmailImport\\ParticipantsResolver] The sender email is blacklisted, skipping {\"email\":\"notifications@github.com\",\"inbox_id\":212,\"message_provider_id\":\"19dce74ed398d2c1\",\"team_id\":1} {\"correlation_id\":\"5fb3c8c8-3160-4964-ac56-02d6e2cb998c\",\"trace_id\":\"3a6d5e64-ecf2-4700-98cd-3edd56a1dc34\"}\n[2026-04-27 10:28:15] local.INFO: [EmailImport\\ParticipantsValidator] Email participants are less than 2 {\"inbox_id\":212,\"message_provider_id\":\"19dce74ed398d2c1\",\"message_id\":\"<jiminny/app/pull/12016/c4326142661@github.com>\"} {\"correlation_id\":\"5fb3c8c8-3160-4964-ac56-02d6e2cb998c\",\"trace_id\":\"3a6d5e64-ecf2-4700-98cd-3edd56a1dc34\"}\n[2026-04-27 10:28:15] local.INFO: Processing an email from inbox batch {\"batch\":98446,\"inbox_id\":212,\"email\":\"lukas.kovalik@jiminny.com\",\"email_id\":\"19dce713dbecf41f\",\"from\":\"\\\"sonarqubecloud[bot]\\\" <notifications@github.com>\",\"to\":\"\\\"jiminny/infrastructure\\\" <infrastructure@noreply.github.com>\",\"cc\":\"Subscribed <subscribed@noreply.github.com>\"} {\"correlation_id\":\"5fb3c8c8-3160-4964-ac56-02d6e2cb998c\",\"trace_id\":\"3a6d5e64-ecf2-4700-98cd-3edd56a1dc34\"}\n[2026-04-27 10:28:15] local.INFO: [SocialAccountService] Fetching token {\"socialAccountId\":1500,\"provider\":\"salesforce\"} {\"correlation_id\":\"5fb3c8c8-3160-4964-ac56-02d6e2cb998c\",\"trace_id\":\"3a6d5e64-ecf2-4700-98cd-3edd56a1dc34\"}\n[2026-04-27 10:28:15] local.INFO: [SocialAccountService] Token retrieved {\"socialAccountId\":1500,\"provider\":\"salesforce\"} {\"correlation_id\":\"5fb3c8c8-3160-4964-ac56-02d6e2cb998c\",\"trace_id\":\"3a6d5e64-ecf2-4700-98cd-3edd56a1dc34\"}\n[2026-04-27 10:28:15] local.INFO: [EncryptedTokenManager] Generating access token. {\"mode\":\"legacy\"} {\"correlation_id\":\"5fb3c8c8-3160-4964-ac56-02d6e2cb998c\",\"trace_id\":\"3a6d5e64-ecf2-4700-98cd-3edd56a1dc34\"}\n[2026-04-27 10:28:15] local.INFO: [CrmOwnerResolver] Integration owner matched as CRM Owner {\"crm_provider\":\"salesforce\",\"crm_owner\":143,\"team_id\":1} {\"correlation_id\":\"5fb3c8c8-3160-4964-ac56-02d6e2cb998c\",\"trace_id\":\"3a6d5e64-ecf2-4700-98cd-3edd56a1dc34\"}\n[2026-04-27 10:28:15] local.INFO: [EmailImport\\ParticipantsResolver] The sender email is blacklisted, skipping {\"email\":\"notifications@github.com\",\"inbox_id\":212,\"message_provider_id\":\"19dce713dbecf41f\",\"team_id\":1} {\"correlation_id\":\"5fb3c8c8-3160-4964-ac56-02d6e2cb998c\",\"trace_id\":\"3a6d5e64-ecf2-4700-98cd-3edd56a1dc34\"}\n[2026-04-27 10:28:15] local.INFO: [EmailImport\\ParticipantsValidator] Email participants are less than 2 {\"inbox_id\":212,\"message_provider_id\":\"19dce713dbecf41f\",\"message_id\":\"<jiminny/infrastructure/pull/725/c4326108250@github.com>\"} {\"correlation_id\":\"5fb3c8c8-3160-4964-ac56-02d6e2cb998c\",\"trace_id\":\"3a6d5e64-ecf2-4700-98cd-3edd56a1dc34\"}\n[2026-04-27 10:28:15] local.INFO: Processing an email from inbox batch {\"batch\":98446,\"inbox_id\":212,\"email\":\"lukas.kovalik@jiminny.com\",\"email_id\":\"19dce708ed8b5acb\",\"from\":\"Veselin Kulov <notifications@github.com>\",\"to\":\"\\\"jiminny/infrastructure\\\" <infrastructure@noreply.github.com>\",\"cc\":\"Push <push@noreply.github.com>\"} {\"correlation_id\":\"5fb3c8c8-3160-4964-ac56-02d6e2cb998c\",\"trace_id\":\"3a6d5e64-ecf2-4700-98cd-3edd56a1dc34\"}\n[2026-04-27 10:28:15] local.INFO: [SocialAccountService] Fetching token {\"socialAccountId\":1500,\"provider\":\"salesforce\"} {\"correlation_id\":\"5fb3c8c8-3160-4964-ac56-02d6e2cb998c\",\"trace_id\":\"3a6d5e64-ecf2-4700-98cd-3edd56a1dc34\"}\n[2026-04-27 10:28:15] local.INFO: [SocialAccountService] Token retrieved {\"socialAccountId\":1500,\"provider\":\"salesforce\"} {\"correlation_id\":\"5fb3c8c8-3160-4964-ac56-02d6e2cb998c\",\"trace_id\":\"3a6d5e64-ecf2-4700-98cd-3edd56a1dc34\"}\n[2026-04-27 10:28:15] local.INFO: [EncryptedTokenManager] Generating access token. {\"mode\":\"legacy\"} {\"correlation_id\":\"5fb3c8c8-3160-4964-ac56-02d6e2cb998c\",\"trace_id\":\"3a6d5e64-ecf2-4700-98cd-3edd56a1dc34\"}\n[2026-04-27 10:28:15] local.INFO: [CrmOwnerResolver] Integration owner matched as CRM Owner {\"crm_provider\":\"salesforce\",\"crm_owner\":143,\"team_id\":1} {\"correlation_id\":\"5fb3c8c8-3160-4964-ac56-02d6e2cb998c\",\"trace_id\":\"3a6d5e64-ecf2-4700-98cd-3edd56a1dc34\"}\n[2026-04-27 10:28:15] local.INFO: [EmailImport\\ParticipantsResolver] The sender email is blacklisted, skipping {\"email\":\"notifications@github.com\",\"inbox_id\":212,\"message_provider_id\":\"19dce708ed8b5acb\",\"team_id\":1} {\"correlation_id\":\"5fb3c8c8-3160-4964-ac56-02d6e2cb998c\",\"trace_id\":\"3a6d5e64-ecf2-4700-98cd-3edd56a1dc34\"}\n[2026-04-27 10:28:15] local.INFO: [EmailImport\\ParticipantsValidator] Email participants are less than 2 {\"inbox_id\":212,\"message_provider_id\":\"19dce708ed8b5acb\",\"message_id\":\"<jiminny/infrastructure/pull/725/before/186952cc584428fcca17aa364f6cc535d65e804a/after/078246a06b25fb93ad61c7f14472ecb755483f4d@github.com>\"} {\"correlation_id\":\"5fb3c8c8-3160-4964-ac56-02d6e2cb998c\",\"trace_id\":\"3a6d5e64-ecf2-4700-98cd-3edd56a1dc34\"}\n[2026-04-27 10:28:15] local.INFO: Processing an email from inbox batch {\"batch\":98446,\"inbox_id\":212,\"email\":\"lukas.kovalik@jiminny.com\",\"email_id\":\"19dce6eea657f2fa\",\"from\":\"\\\"sonarqubecloud[bot]\\\" <notifications@github.com>\",\"to\":\"\\\"jiminny/app\\\" <app@noreply.github.com>\",\"cc\":\"Subscribed <subscribed@noreply.github.com>\"} {\"correlation_id\":\"5fb3c8c8-3160-4964-ac56-02d6e2cb998c\",\"trace_id\":\"3a6d5e64-ecf2-4700-98cd-3edd56a1dc34\"}\n[2026-04-27 10:28:15] local.INFO: [SocialAccountService] Fetching token {\"socialAccountId\":1500,\"provider\":\"salesforce\"} {\"correlation_id\":\"5fb3c8c8-3160-4964-ac56-02d6e2cb998c\",\"trace_id\":\"3a6d5e64-ecf2-4700-98cd-3edd56a1dc34\"}\n[2026-04-27 10:28:15] local.INFO: [SocialAccountService] Token retrieved {\"socialAccountId\":1500,\"provider\":\"salesforce\"} {\"correlation_id\":\"5fb3c8c8-3160-4964-ac56-02d6e2cb998c\",\"trace_id\":\"3a6d5e64-ecf2-4700-98cd-3edd56a1dc34\"}\n[2026-04-27 10:28:15] local.INFO: [EncryptedTokenManager] Generating access token. {\"mode\":\"legacy\"} {\"correlation_id\":\"5fb3c8c8-3160-4964-ac56-02d6e2cb998c\",\"trace_id\":\"3a6d5e64-ecf2-4700-98cd-3edd56a1dc34\"}\n[2026-04-27 10:28:15] local.INFO: [CrmOwnerResolver] Integration owner matched as CRM Owner {\"crm_provider\":\"salesforce\",\"crm_owner\":143,\"team_id\":1} {\"correlation_id\":\"5fb3c8c8-3160-4964-ac56-02d6e2cb998c\",\"trace_id\":\"3a6d5e64-ecf2-4700-98cd-3edd56a1dc34\"}\n[2026-04-27 10:28:15] local.INFO: [EmailImport\\ParticipantsResolver] The sender email is blacklisted, skipping {\"email\":\"notifications@github.com\",\"inbox_id\":212,\"message_provider_id\":\"19dce6eea657f2fa\",\"team_id\":1} {\"correlation_id\":\"5fb3c8c8-3160-4964-ac56-02d6e2cb998c\",\"trace_id\":\"3a6d5e64-ecf2-4700-98cd-3edd56a1dc34\"}\n[2026-04-27 10:28:15] local.INFO: [EmailImport\\ParticipantsValidator] Email participants are less than 2 {\"inbox_id\":212,\"message_provider_id\":\"19dce6eea657f2fa\",\"message_id\":\"<jiminny/app/pull/11998/c4326088534@github.com>\"} {\"correlation_id\":\"5fb3c8c8-3160-4964-ac56-02d6e2cb998c\",\"trace_id\":\"3a6d5e64-ecf2-4700-98cd-3edd56a1dc34\"}\n[2026-04-27 10:28:15] local.INFO: Deleting successfully processed batch 98446 for inbox 212 {\"correlation_id\":\"5fb3c8c8-3160-4964-ac56-02d6e2cb998c\",\"trace_id\":\"3a6d5e64-ecf2-4700-98cd-3edd56a1dc34\"}\n[2026-04-27 10:28:15] local.INFO: [EmailSchedule] FINISHED batch process {\"host\":\"docker_lamp_1\",\"processed\":1} {\"correlation_id\":\"5fb3c8c8-3160-4964-ac56-02d6e2cb998c\",\"trace_id\":\"3a6d5e64-ecf2-4700-98cd-3edd56a1dc34\"}\n[2026-04-27 10:28:15] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage for command {\"command\":\"mailbox:batch:process\",\"memoryBeforeCommandInMb\":60.0,\"memoryAfterCommandInMB\":62.0,\"memoryPeakBeforeCommandInMb\":99.727,\"memoryPeakAfterCommandInMB\":99.727} {\"correlation_id\":\"5fb3c8c8-3160-4964-ac56-02d6e2cb998c\",\"trace_id\":\"3a6d5e64-ecf2-4700-98cd-3edd56a1dc34\"}\n[2026-04-27 10:28:16] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage before starting command {\"command\":\"conference:monitor:count\",\"memoryBeforeCommandInMb\":60.0,\"memoryPeakBeforeCommandInMb\":99.727} {\"correlation_id\":\"ef5b3e1a-a06a-4527-9120-b2adfe4e1846\",\"trace_id\":\"d932d64a-3f63-4aca-8abf-bb13e0892cba\"}\n[2026-04-27 10:28:16] local.INFO: Running conference:monitor:count command for activities in (2026-04-27 10:26:00, 2026-04-27 10:28:00] {\"correlation_id\":\"ef5b3e1a-a06a-4527-9120-b2adfe4e1846\",\"trace_id\":\"d932d64a-3f63-4aca-8abf-bb13e0892cba\"}\n[2026-04-27 10:28:16] local.INFO: [conference:monitor:count] No activities found in (2026-04-27 10:26:00, 2026-04-27 10:28:00] {\"correlation_id\":\"ef5b3e1a-a06a-4527-9120-b2adfe4e1846\",\"trace_id\":\"d932d64a-3f63-4aca-8abf-bb13e0892cba\"}\n[2026-04-27 10:28:16] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage for command {\"command\":\"conference:monitor:count\",\"memoryBeforeCommandInMb\":60.0,\"memoryAfterCommandInMB\":62.0,\"memoryPeakBeforeCommandInMb\":99.727,\"memoryPeakAfterCommandInMB\":99.727} {\"correlation_id\":\"ef5b3e1a-a06a-4527-9120-b2adfe4e1846\",\"trace_id\":\"d932d64a-3f63-4aca-8abf-bb13e0892cba\"}\n[2026-04-27 10:28:17] local.ERROR: Target class [Jiminny\\Repositories\\AjReportsRepository] does not exist. {\"exception\":\"[object] (Illuminate\\\\Contracts\\\\Container\\\\BindingResolutionException(code: 0): Target class [Jiminny\\\\Repositories\\\\AjReportsRepository] does not exist. at /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php:1124)\n[stacktrace]\n#0 /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php(933): Illuminate\\\\Container\\\\Container->build('Jiminny\\\\\\\\Reposit...')\n#1 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Application.php(1078): Illuminate\\\\Container\\\\Container->resolve('Jiminny\\\\\\\\Reposit...', Array, true)\n#2 /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php(864): Illuminate\\\\Foundation\\\\Application->resolve('Jiminny\\\\\\\\Reposit...', Array)\n#3 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Application.php(1058): Illuminate\\\\Container\\\\Container->make('Jiminny\\\\\\\\Reposit...', Array)\n#4 /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php(1336): Illuminate\\\\Foundation\\\\Application->make('Jiminny\\\\\\\\Reposit...')\n#5 /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php(1237): Illuminate\\\\Container\\\\Container->resolveClass(Object(ReflectionParameter))\n#6 /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php(1162): Illuminate\\\\Container\\\\Container->resolveDependencies(Array)\n#7 /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php(933): Illuminate\\\\Container\\\\Container->build('Jiminny\\\\\\\\Http\\\\\\\\Co...')\n#8 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Application.php(1078): Illuminate\\\\Container\\\\Container->resolve('Jiminny\\\\\\\\Http\\\\\\\\Co...', Array, true)\n#9 /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php(864): Illuminate\\\\Foundation\\\\Application->resolve('Jiminny\\\\\\\\Http\\\\\\\\Co...', Array)\n#10 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Application.php(1058): Illuminate\\\\Container\\\\Container->make('Jiminny\\\\\\\\Http\\\\\\\\Co...', Array)\n#11 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Route.php(286): Illuminate\\\\Foundation\\\\Application->make('Jiminny\\\\\\\\Http\\\\\\\\Co...')\n#12 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Route.php(1133): Illuminate\\\\Routing\\\\Route->getController()\n#13 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Route.php(1062): Illuminate\\\\Routing\\\\Route->controllerMiddleware()\n#14 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Router.php(834): Illuminate\\\\Routing\\\\Route->gatherMiddleware()\n#15 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Router.php(816): Illuminate\\\\Routing\\\\Router->gatherRouteMiddleware(Object(Illuminate\\\\Routing\\\\Route))\n#16 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Router.php(800): Illuminate\\\\Routing\\\\Router->runRouteWithinStack(Object(Illuminate\\\\Routing\\\\Route), Object(Illuminate\\\\Http\\\\Request))\n#17 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Router.php(764): Illuminate\\\\Routing\\\\Router->runRoute(Object(Illuminate\\\\Http\\\\Request), Object(Illuminate\\\\Routing\\\\Route))\n#18 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Router.php(753): Illuminate\\\\Routing\\\\Router->dispatchToRoute(Object(Illuminate\\\\Http\\\\Request))\n#19 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Http/Kernel.php(200): Illuminate\\\\Routing\\\\Router->dispatch(Object(Illuminate\\\\Http\\\\Request))\n#20 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(180): Illuminate\\\\Foundation\\\\Http\\\\Kernel->Illuminate\\\\Foundation\\\\Http\\\\{closure}(Object(Illuminate\\\\Http\\\\Request))\n#21 /home/jiminny/vendor/barryvdh/laravel-debugbar/src/Middleware/InjectDebugbar.php(59): Illuminate\\\\Pipeline\\\\Pipeline->Illuminate\\\\Pipeline\\\\{closure}(Object(Illuminate\\\\Http\\\\Request))\n#22 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(219): Barryvdh\\\\Debugbar\\\\Middleware\\\\InjectDebugbar->handle(Object(Illuminate\\\\Http\\\\Request), Object(Closure))\n#23 /home/jiminny/vendor/laravel/framework/src/Illuminate/Http/Middleware/HandleCors.php(74): Illuminate\\\\Pipeline\\\\Pipeline->Illuminate\\\\Pipeline\\\\{closure}(Object(Illuminate\\\\Http\\\\Request))\n#24 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(219): Illuminate\\\\Http\\\\Middleware\\\\HandleCors->handle(Object(Illuminate\\\\Http\\\\Request), Object(Closure))\n#25 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Http/Middleware/TransformsRequest.php(21): Illuminate\\\\Pipeline\\\\Pipeline->Illuminate\\\\Pipeline\\\\{closure}(Object(Illuminate\\\\Http\\\\Request))\n#26 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Http/Middleware/TrimStrings.php(51): Illuminate\\\\Foundation\\\\Http\\\\Middleware\\\\TransformsRequest->handle(Object(Illuminate\\\\Http\\\\Request), Object(Closure))\n#27 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(219): Illuminate\\\\Foundation\\\\Http\\\\Middleware\\\\TrimStrings->handle(Object(Illuminate\\\\Http\\\\Request), Object(Closure))\n#28 /home/jiminny/vendor/bepsvpt/secure-headers/src/SecureHeadersMiddleware.php(18): Illuminate\\\\Pipeline\\\\Pipeline->Illuminate\\\\Pipeline\\\\{closure}(Object(Illuminate\\\\Http\\\\Request))\n#29 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(219): Bepsvpt\\\\SecureHeaders\\\\SecureHeadersMiddleware->handle(Object(Illuminate\\\\Http\\\\Request), Object(Closure))\n#30 /home/jiminny/app/Http/Middleware/SentryContext.php(60): Illuminate\\\\Pipeline\\\\Pipeline->Illuminate\\\\Pipeline\\\\{closure}(Object(Illuminate\\\\Http\\\\Request))\n#31 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(219): Jiminny\\\\Http\\\\Middleware\\\\SentryContext->handle(Object(Illuminate\\\\Http\\\\Request), Object(Closure))\n#32 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Http/Middleware/PreventRequestsDuringMaintenance.php(109): Illuminate\\\\Pipeline\\\\Pipeline->Illuminate\\\\Pipeline\\\\{closure}(Object(Illuminate\\\\Http\\\\Request))\n#33 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(219): Illuminate\\\\Foundation\\\\Http\\\\Middleware\\\\PreventRequestsDuringMaintenance->handle(Object(Illuminate\\\\Http\\\\Request), Object(Closure))\n#34 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Http/Middleware/InvokeDeferredCallbacks.php(22): Illuminate\\\\Pipeline\\\\Pipeline->Illuminate\\\\Pipeline\\\\{closure}(Object(Illuminate\\\\Http\\\\Request))\n#35 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(219): Illuminate\\\\Foundation\\\\Http\\\\Middleware\\\\InvokeDeferredCallbacks->handle(Object(Illuminate\\\\Http\\\\Request), Object(Closure))\n#36 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(137): Illuminate\\\\Pipeline\\\\Pipeline->Illuminate\\\\Pipeline\\\\{closure}(Object(Illuminate\\\\Http\\\\Request))\n#37 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Http/Kernel.php(175): Illuminate\\\\Pipeline\\\\Pipeline->then(Object(Closure))\n#38 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Http/Kernel.php(144): Illuminate\\\\Foundation\\\\Http\\\\Kernel->sendRequestThroughRouter(Object(Illuminate\\\\Http\\\\Request))\n#39 /home/jiminny/public/index.php(51): Illuminate\\\\Foundation\\\\Http\\\\Kernel->handle(Object(Illuminate\\\\Http\\\\Request))\n#40 {main}\n\n[previous exception] [object] (ReflectionException(code: -1): Class \\\"Jiminny\\\\Repositories\\\\AjReportsRepository\\\" does not exist at /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php:1122)\n[stacktrace]\n#0 /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php(1122): ReflectionClass->__construct('Jiminny\\\\\\\\Reposit...')\n#1 /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php(933): Illuminate\\\\Container\\\\Container->build('Jiminny\\\\\\\\Reposit...')\n#2 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Application.php(1078): Illuminate\\\\Container\\\\Container->resolve('Jiminny\\\\\\\\Reposit...', Array, true)\n#3 /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php(864): Illuminate\\\\Foundation\\\\Application->resolve('Jiminny\\\\\\\\Reposit...', Array)\n#4 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Application.php(1058): Illuminate\\\\Container\\\\Container->make('Jiminny\\\\\\\\Reposit...', Array)\n#5 /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php(1336): Illuminate\\\\Foundation\\\\Application->make('Jiminny\\\\\\\\Reposit...')\n#6 /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php(1237): Illuminate\\\\Container\\\\Container->resolveClass(Object(ReflectionParameter))\n#7 /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php(1162): Illuminate\\\\Container\\\\Container->resolveDependencies(Array)\n#8 /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php(933): Illuminate\\\\Container\\\\Container->build('Jiminny\\\\\\\\Http\\\\\\\\Co...')\n#9 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Application.php(1078): Illuminate\\\\Container\\\\Container->resolve('Jiminny\\\\\\\\Http\\\\\\\\Co...', Array, true)\n#10 /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php(864): Illuminate\\\\Foundation\\\\Application->resolve('Jiminny\\\\\\\\Http\\\\\\\\Co...', Array)\n#11 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Application.php(1058): Illuminate\\\\Container\\\\Container->make('Jiminny\\\\\\\\Http\\\\\\\\Co...', Array)\n#12 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Route.php(286): Illuminate\\\\Foundation\\\\Application->make('Jiminny\\\\\\\\Http\\\\\\\\Co...')\n#13 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Route.php(1133): Illuminate\\\\Routing\\\\Route->getController()\n#14 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Route.php(1062): Illuminate\\\\Routing\\\\Route->controllerMiddleware()\n#15 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Router.php(834): Illuminate\\\\Routing\\\\Route->gatherMiddleware()\n#16 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Router.php(816): Illuminate\\\\Routing\\\\Router->gatherRouteMiddleware(Object(Illuminate\\\\Routing\\\\Route))\n#17 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Router.php(800): Illuminate\\\\Routing\\\\Router->runRouteWithinStack(Object(Illuminate\\\\Routing\\\\Route), Object(Illuminate\\\\Http\\\\Request))\n#18 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Router.php(764): Illuminate\\\\Routing\\\\Router->runRoute(Object(Illuminate\\\\Http\\\\Request), Object(Illuminate\\\\Routing\\\\Route))\n#19 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Router.php(753): Illuminate\\\\Routing\\\\Router->dispatchToRoute(Object(Illuminate\\\\Http\\\\Request))\n#20 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Http/Kernel.php(200): Illuminate\\\\Routing\\\\Router->dispatch(Object(Illuminate\\\\Http\\\\Request))\n#21 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(180): Illuminate\\\\Foundation\\\\Http\\\\Kernel->Illuminate\\\\Foundation\\\\Http\\\\{closure}(Object(Illuminate\\\\Http\\\\Request))\n#22 /home/jiminny/vendor/barryvdh/laravel-debugbar/src/Middleware/InjectDebugbar.php(59): Illuminate\\\\Pipeline\\\\Pipeline->Illuminate\\\\Pipeline\\\\{closure}(Object(Illuminate\\\\Http\\\\Request))\n#23 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(219): Barryvdh\\\\Debugbar\\\\Middleware\\\\InjectDebugbar->handle(Object(Illuminate\\\\Http\\\\Request), Object(Closure))\n#24 /home/jiminny/vendor/laravel/framework/src/Illuminate/Http/Middleware/HandleCors.php(74): Illuminate\\\\Pipeline\\\\Pipeline->Illuminate\\\\Pipeline\\\\{closure}(Object(Illuminate\\\\Http\\\\Request))\n#25 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(219): Illuminate\\\\Http\\\\Middleware\\\\HandleCors->handle(Object(Illuminate\\\\Http\\\\Request), Object(Closure))\n#26 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Http/Middleware/TransformsRequest.php(21): Illuminate\\\\Pipeline\\\\Pipeline->Illuminate\\\\Pipeline\\\\{closure}(Object(Illuminate\\\\Http\\\\Request))\n#27 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Http/Middleware/TrimStrings.php(51): Illuminate\\\\Foundation\\\\Http\\\\Middleware\\\\TransformsRequest->handle(Object(Illuminate\\\\Http\\\\Request), Object(Closure))\n#28 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(219): Illuminate\\\\Foundation\\\\Http\\\\Middleware\\\\TrimStrings->handle(Object(Illuminate\\\\Http\\\\Request), Object(Closure))\n#29 /home/jiminny/vendor/bepsvpt/secure-headers/src/SecureHeadersMiddleware.php(18): Illuminate\\\\Pipeline\\\\Pipeline->Illuminate\\\\Pipeline\\\\{closure}(Object(Illuminate\\\\Http\\\\Request))\n#30 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(219): Bepsvpt\\\\SecureHeaders\\\\SecureHeadersMiddleware->handle(Object(Illuminate\\\\Http\\\\Request), Object(Closure))\n#31 /home/jiminny/app/Http/Middleware/SentryContext.php(60): Illuminate\\\\Pipeline\\\\Pipeline->Illuminate\\\\Pipeline\\\\{closure}(Object(Illuminate\\\\Http\\\\Request))\n#32 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(219): Jiminny\\\\Http\\\\Middleware\\\\SentryContext->handle(Object(Illuminate\\\\Http\\\\Request), Object(Closure))\n#33 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Http/Middleware/PreventRequestsDuringMaintenance.php(109): Illuminate\\\\Pipeline\\\\Pipeline->Illuminate\\\\Pipeline\\\\{closure}(Object(Illuminate\\\\Http\\\\Request))\n#34 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(219): Illuminate\\\\Foundation\\\\Http\\\\Middleware\\\\PreventRequestsDuringMaintenance->handle(Object(Illuminate\\\\Http\\\\Request), Object(Closure))\n#35 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Http/Middleware/InvokeDeferredCallbacks.php(22): Illuminate\\\\Pipeline\\\\Pipeline->Illuminate\\\\Pipeline\\\\{closure}(Object(Illuminate\\\\Http\\\\Request))\n#36 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(219): Illuminate\\\\Foundation\\\\Http\\\\Middleware\\\\InvokeDeferredCallbacks->handle(Object(Illuminate\\\\Http\\\\Request), Object(Closure))\n#37 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(137): Illuminate\\\\Pipeline\\\\Pipeline->Illuminate\\\\Pipeline\\\\{closure}(Object(Illuminate\\\\Http\\\\Request))\n#38 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Http/Kernel.php(175): Illuminate\\\\Pipeline\\\\Pipeline->then(Object(Closure))\n#39 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Http/Kernel.php(144): Illuminate\\\\Foundation\\\\Http\\\\Kernel->sendRequestThroughRouter(Object(Illuminate\\\\Http\\\\Request))\n#40 /home/jiminny/public/index.php(51): Illuminate\\\\Foundation\\\\Http\\\\Kernel->handle(Object(Illuminate\\\\Http\\\\Request))\n#41 {main}\n\"} {\"correlation_id\":\"46b082fc-5a28-4cc4-8bd1-c670f8662e7f\",\"trace_id\":\"8ae7dc26-61c7-42e4-8617-0b85f53e54c1\"}\n[2026-04-27 10:28:19] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage before starting command {\"command\":\"calendar:sync\",\"memoryBeforeCommandInMb\":60.0,\"memoryPeakBeforeCommandInMb\":99.727} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:19] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage before starting command {\"command\":\"mailbox:batch:retry-failed\",\"memoryBeforeCommandInMb\":60.0,\"memoryPeakBeforeCommandInMb\":99.727} {\"correlation_id\":\"e155562a-84a1-45f5-8897-1779f96886f6\",\"trace_id\":\"12141267-1032-479b-aab5-e17e133007d9\"}\n[2026-04-27 10:28:19] local.NOTICE: Calendar sync start {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:19] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage for command {\"command\":\"mailbox:batch:retry-failed\",\"memoryBeforeCommandInMb\":60.0,\"memoryAfterCommandInMB\":62.0,\"memoryPeakBeforeCommandInMb\":99.727,\"memoryPeakAfterCommandInMB\":99.727} {\"correlation_id\":\"e155562a-84a1-45f5-8897-1779f96886f6\",\"trace_id\":\"12141267-1032-479b-aab5-e17e133007d9\"}\n[2026-04-27 10:28:19] local.INFO: [SocialAccountService] Fetching token {\"socialAccountId\":1393,\"provider\":\"google\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:19] local.INFO: [SocialAccountService] Token needs refreshing {\"socialAccountId\":1393,\"provider\":\"google\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:19] local.INFO: [EncryptedTokenManager] Generating access token. {\"mode\":\"legacy\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:19] local.INFO: [SocialAccountService] Refreshing token from provider {\"socialAccountId\":1393,\"provider\":\"google\",\"refreshToken\":\"5aa7e2d96b53201cd16fca5d2e4ef3ad03320971fc064781d18aee3ae7b99fbf\",\"state\":\"full-refresh\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:20] local.ERROR: [SocialAccountService] Failed to refresh token {\"socialAccountId\":1393,\"provider\":\"google\",\"responseBody\":{\"error\":\"invalid_grant\",\"error_description\":\"Account has been deleted\"}} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:20] local.INFO: [SocialAccountObserver] Saving model {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:20] local.ERROR: [SocialAccountService] Failed to refresh token {\"socialAccountId\":1393,\"provider\":\"google\",\"reason\":\"Flow refresh required.\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:20] local.INFO: [SocialAccountService] Fetching token {\"socialAccountId\":1387,\"provider\":\"google\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:20] local.INFO: [SocialAccountService] Token needs refreshing {\"socialAccountId\":1387,\"provider\":\"google\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:20] local.INFO: [EncryptedTokenManager] Generating access token. {\"mode\":\"legacy\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:20] local.INFO: [SocialAccountService] Refreshing token from provider {\"socialAccountId\":1387,\"provider\":\"google\",\"refreshToken\":\"8157ac6de94842937194009e9c50e459253600f799dacf6a40755ffdbeb5bba6\",\"state\":\"full-refresh\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:21] local.ERROR: [SocialAccountService] Failed to refresh token {\"socialAccountId\":1387,\"provider\":\"google\",\"responseBody\":{\"error\":\"invalid_grant\",\"error_description\":\"Account has been deleted\"}} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:21] local.INFO: [SocialAccountObserver] Saving model {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:21] local.ERROR: [SocialAccountService] Failed to refresh token {\"socialAccountId\":1387,\"provider\":\"google\",\"reason\":\"Flow refresh required.\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:21] local.INFO: [SocialAccountService] Fetching token {\"socialAccountId\":1348,\"provider\":\"google\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:21] local.INFO: [SocialAccountService] Token needs refreshing {\"socialAccountId\":1348,\"provider\":\"google\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:21] local.INFO: [EncryptedTokenManager] Generating access token. {\"mode\":\"legacy\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:21] local.INFO: [SocialAccountService] Refreshing token from provider {\"socialAccountId\":1348,\"provider\":\"google\",\"refreshToken\":\"9e7d13d3032d0cb1b79d8e95aef01383e8e91eb52ff8ee960c8a0b6b95cd8c73\",\"state\":\"full-refresh\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:21] local.ERROR: [SocialAccountService] Failed to refresh token {\"socialAccountId\":1348,\"provider\":\"google\",\"responseBody\":{\"error\":\"invalid_grant\",\"error_description\":\"Bad Request\"}} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:21] local.INFO: [SocialAccountObserver] Saving model {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:21] local.ERROR: [SocialAccountService] Failed to refresh token {\"socialAccountId\":1348,\"provider\":\"google\",\"reason\":\"Flow refresh required.\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:21] local.INFO: [SocialAccountService] Fetching token {\"socialAccountId\":1361,\"provider\":\"google\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:21] local.INFO: [SocialAccountService] Token needs refreshing {\"socialAccountId\":1361,\"provider\":\"google\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:21] local.INFO: [EncryptedTokenManager] Generating access token. {\"mode\":\"legacy\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:21] local.INFO: [SocialAccountService] Refreshing token from provider {\"socialAccountId\":1361,\"provider\":\"google\",\"refreshToken\":\"6c843da199c2b9907445329304fcc4ec5057a4ee748d8299641764395c08e1fd\",\"state\":\"full-refresh\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:22] local.ERROR: [SocialAccountService] Failed to refresh token {\"socialAccountId\":1361,\"provider\":\"google\",\"responseBody\":{\"error\":\"invalid_grant\",\"error_description\":\"Account has been deleted\"}} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:22] local.INFO: [SocialAccountObserver] Saving model {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:22] local.ERROR: [SocialAccountService] Failed to refresh token {\"socialAccountId\":1361,\"provider\":\"google\",\"reason\":\"Flow refresh required.\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:22] local.INFO: [SocialAccountService] Fetching token {\"socialAccountId\":1310,\"provider\":\"google\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:22] local.INFO: [SocialAccountService] Token needs refreshing {\"socialAccountId\":1310,\"provider\":\"google\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:22] local.INFO: [EncryptedTokenManager] Generating access token. {\"mode\":\"legacy\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:22] local.INFO: [SocialAccountService] Refreshing token from provider {\"socialAccountId\":1310,\"provider\":\"google\",\"refreshToken\":\"e34818922c2830a660813a63f6169a4a9a992ae2cccd7dc8dd7796cfdb470ef1\",\"state\":\"full-refresh\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:23] local.ERROR: [SocialAccountService] Failed to refresh token {\"socialAccountId\":1310,\"provider\":\"google\",\"responseBody\":{\"error\":\"invalid_grant\",\"error_description\":\"Bad Request\"}} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:23] local.INFO: [SocialAccountObserver] Saving model {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:23] local.ERROR: [SocialAccountService] Failed to refresh token {\"socialAccountId\":1310,\"provider\":\"google\",\"reason\":\"Flow refresh required.\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:23] local.INFO: [SocialAccountService] Fetching token {\"socialAccountId\":1333,\"provider\":\"google\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:23] local.INFO: [SocialAccountService] Token needs refreshing {\"socialAccountId\":1333,\"provider\":\"google\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:23] local.INFO: [EncryptedTokenManager] Generating access token. {\"mode\":\"legacy\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:23] local.INFO: [SocialAccountService] Refreshing token from provider {\"socialAccountId\":1333,\"provider\":\"google\",\"refreshToken\":\"6c902986546d8e8da1dc539b046cdc1d458f519acc972e5b5f1d6a1a295165e0\",\"state\":\"full-refresh\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:23] local.ERROR: [SocialAccountService] Failed to refresh token {\"socialAccountId\":1333,\"provider\":\"google\",\"responseBody\":{\"error\":\"unauthorized_client\",\"error_description\":\"Unauthorized\"}} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:23] local.INFO: [SocialAccountObserver] Saving model {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:23] local.ERROR: [SocialAccountService] Failed to refresh token {\"socialAccountId\":1333,\"provider\":\"google\",\"reason\":\"Flow refresh required.\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:24] local.INFO: [SocialAccountService] Fetching token {\"socialAccountId\":1368,\"provider\":\"google\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:24] local.INFO: [SocialAccountService] Token needs refreshing {\"socialAccountId\":1368,\"provider\":\"google\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:24] local.INFO: [EncryptedTokenManager] Generating access token. {\"mode\":\"legacy\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:24] local.INFO: [SocialAccountService] Refreshing token from provider {\"socialAccountId\":1368,\"provider\":\"google\",\"refreshToken\":\"d2f128898ff8543bd16b69cfae37896ab85119b0f5ed2b431d739593bb600333\",\"state\":\"full-refresh\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:24] local.ERROR: [SocialAccountService] Failed to refresh token {\"socialAccountId\":1368,\"provider\":\"google\",\"responseBody\":{\"error\":\"invalid_grant\",\"error_description\":\"Bad Request\"}} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:24] local.INFO: [SocialAccountObserver] Saving model {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:24] local.ERROR: [SocialAccountService] Failed to refresh token {\"socialAccountId\":1368,\"provider\":\"google\",\"reason\":\"Flow refresh required.\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:24] local.INFO: [SocialAccountService] Fetching token {\"socialAccountId\":1365,\"provider\":\"google\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:24] local.INFO: [SocialAccountService] Token needs refreshing {\"socialAccountId\":1365,\"provider\":\"google\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:24] local.INFO: [EncryptedTokenManager] Generating access token. {\"mode\":\"legacy\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:24] local.INFO: [SocialAccountService] Refreshing token from provider {\"socialAccountId\":1365,\"provider\":\"google\",\"refreshToken\":\"7676e4a9afcd082b413248ab5ec6e487021fec6a9bdf315860a59cefad9caad8\",\"state\":\"full-refresh\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:25] local.ERROR: [SocialAccountService] Failed to refresh token {\"socialAccountId\":1365,\"provider\":\"google\",\"responseBody\":{\"error\":\"unauthorized_client\",\"error_description\":\"Unauthorized\"}} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:25] local.INFO: [SocialAccountObserver] Saving model {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:25] local.ERROR: [SocialAccountService] Failed to refresh token {\"socialAccountId\":1365,\"provider\":\"google\",\"reason\":\"Flow refresh required.\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:25] local.INFO: [SocialAccountService] Fetching token {\"socialAccountId\":1364,\"provider\":\"google\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:25] local.INFO: [SocialAccountService] Token needs refreshing {\"socialAccountId\":1364,\"provider\":\"google\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:25] local.INFO: [EncryptedTokenManager] Generating access token. {\"mode\":\"legacy\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:25] local.INFO: [SocialAccountService] Refreshing token from provider {\"socialAccountId\":1364,\"provider\":\"google\",\"refreshToken\":\"dd5882ebce76e645292ce33ae74238abbb77c0a4ecc6a2bfe723cad82e72ba8e\",\"state\":\"full-refresh\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:25] local.ERROR: [SocialAccountService] Failed to refresh token {\"socialAccountId\":1364,\"provider\":\"google\",\"responseBody\":{\"error\":\"unauthorized_client\",\"error_description\":\"Unauthorized\"}} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:25] local.INFO: [SocialAccountObserver] Saving model {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:25] local.ERROR: [SocialAccountService] Failed to refresh token {\"socialAccountId\":1364,\"provider\":\"google\",\"reason\":\"Flow refresh required.\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:25] local.INFO: [SocialAccountService] Fetching token {\"socialAccountId\":1370,\"provider\":\"office\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:25] local.INFO: [SocialAccountService] Token needs refreshing {\"socialAccountId\":1370,\"provider\":\"office\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:25] local.INFO: [EncryptedTokenManager] Generating access token. {\"mode\":\"legacy\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:25] local.INFO: [SocialAccountService] Refreshing token from provider {\"socialAccountId\":1370,\"provider\":\"office\",\"refreshToken\":\"b7ee8035306d0043cea6e00e7c4fe14f745e44074a1194db62a31cdf8b70af3e\",\"state\":\"full-refresh\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:26] local.ERROR: [SocialAccountService] Failed to refresh token {\"socialAccountId\":1370,\"provider\":\"office\",\"responseBody\":\"{\\\"error\\\":\\\"invalid_client\\\",\\\"error_description\\\":\\\"AADSTS7000215: Invalid client secret provided. Ensure the secret being sent in the request is the client secret value, not the client secret ID, for a secret added to app 'bbcbb2ef-6200-4fae-82bd-d81f5dd738da'. Trace ID: 2be54cfa-1691-4388-a284-754fdf414400 Correlation ID: 9a934049-899b-45c5-99a5-15c9c470d8a2 Timestamp: 2026-04-27 10:28:26Z\\\",\\\"error_codes\\\":[7000215],\\\"timestamp\\\":\\\"2026-04-27 10:28:26Z\\\",\\\"trace_id\\\":\\\"2be54cfa-1691-4388-a284-754fdf414400\\\",\\\"correlation_id\\\":\\\"9a934049-899b-45c5-99a5-15c9c470d8a2\\\",\\\"error_uri\\\":\\\"https://login.microsoftonline.com/error?code=7000215\\\"}\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:26] local.INFO: [SocialAccountObserver] Saving model {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:26] local.ERROR: [SocialAccountService] Failed to refresh token {\"socialAccountId\":1370,\"provider\":\"office\",\"reason\":\"Flow refresh required.\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:26] local.INFO: [SocialAccountService] Fetching token {\"socialAccountId\":1202,\"provider\":\"office\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:26] local.INFO: [SocialAccountService] Token needs refreshing {\"socialAccountId\":1202,\"provider\":\"office\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:26] local.INFO: [EncryptedTokenManager] Generating access token. {\"mode\":\"legacy\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:26] local.INFO: [SocialAccountService] Refreshing token from provider {\"socialAccountId\":1202,\"provider\":\"office\",\"refreshToken\":\"b458799ccc29b21a6e2eb5260fdb63e49ccba21bf942a3973fb63799bd7f0afe\",\"state\":\"full-refresh\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:26] local.ERROR: [SocialAccountService] Failed to refresh token {\"socialAccountId\":1202,\"provider\":\"office\",\"responseBody\":\"{\\\"error\\\":\\\"invalid_client\\\",\\\"error_description\\\":\\\"AADSTS7000215: Invalid client secret provided. Ensure the secret being sent in the request is the client secret value, not the client secret ID, for a secret added to app 'bbcbb2ef-6200-4fae-82bd-d81f5dd738da'. Trace ID: 27729e9a-0943-41bd-9752-4ec9a8444500 Correlation ID: 824a9761-9387-4c53-ae31-5c242b1d4298 Timestamp: 2026-04-27 10:28:26Z\\\",\\\"error_codes\\\":[7000215],\\\"timestamp\\\":\\\"2026-04-27 10:28:26Z\\\",\\\"trace_id\\\":\\\"27729e9a-0943-41bd-9752-4ec9a8444500\\\",\\\"correlation_id\\\":\\\"824a9761-9387-4c53-ae31-5c242b1d4298\\\",\\\"error_uri\\\":\\\"https://login.microsoftonline.com/error?code=7000215\\\"}\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:26] local.INFO: [SocialAccountObserver] Saving model {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:26] local.ERROR: [SocialAccountService] Failed to refresh token {\"socialAccountId\":1202,\"provider\":\"office\",\"reason\":\"Flow refresh required.\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:26] local.INFO: [SocialAccountService] Fetching token {\"socialAccountId\":1502,\"provider\":\"google\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:26] local.INFO: [SocialAccountService] Token needs refreshing {\"socialAccountId\":1502,\"provider\":\"google\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:26] local.INFO: [EncryptedTokenManager] Generating access token. {\"mode\":\"legacy\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:26] local.INFO: [SocialAccountService] Refreshing token from provider {\"socialAccountId\":1502,\"provider\":\"google\",\"refreshToken\":\"d417c92ebaa137295a04675f715d0511ae8acac9d779b102eac50d6300116d3e\",\"state\":\"connected\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:27] local.INFO: [SocialAccountObserver] Saving model {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:27] local.INFO: [SocialAccountObserver] Access token was modified, encrypting {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:27] local.INFO: [SocialAccountService] Token refreshed {\"socialAccountId\":1502,\"provider\":\"google\",\"state\":\"connected\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:27] local.INFO: Calendar sync job dispatched {\"calendar_id\":501} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:27] local.INFO: [SocialAccountService] Fetching token {\"socialAccountId\":1300,\"provider\":\"google\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:27] local.INFO: [SocialAccountService] Token needs refreshing {\"socialAccountId\":1300,\"provider\":\"google\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:27] local.INFO: [EncryptedTokenManager] Generating access token. {\"mode\":\"legacy\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:27] local.INFO: [SocialAccountService] Refreshing token from provider {\"socialAccountId\":1300,\"provider\":\"google\",\"refreshToken\":\"4b811db0725fd9602a95943519a7da935e2a5065da7d9ebfcb170752e3e1ddb8\",\"state\":\"full-refresh\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:27] local.ERROR: [SocialAccountService] Failed to refresh token {\"socialAccountId\":1300,\"provider\":\"google\",\"responseBody\":{\"error\":\"invalid_grant\",\"error_description\":\"Account has been deleted\"}} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:27] local.INFO: [SocialAccountObserver] Saving model {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:27] local.ERROR: [SocialAccountService] Failed to refresh token {\"socialAccountId\":1300,\"provider\":\"google\",\"reason\":\"Flow refresh required.\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:27] local.INFO: [SocialAccountService] Fetching token {\"socialAccountId\":1409,\"provider\":\"google\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:27] local.INFO: [SocialAccountService] Token needs refreshing {\"socialAccountId\":1409,\"provider\":\"google\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:27] local.INFO: [EncryptedTokenManager] Generating access token. {\"mode\":\"legacy\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:27] local.INFO: [SocialAccountService] Refreshing token from provider {\"socialAccountId\":1409,\"provider\":\"google\",\"refreshToken\":\"e2a3f2d06894894eed1ee87d9db1ace77d4d42ee6e1288a8940ad2c10333b0c4\",\"state\":\"full-refresh\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:28] local.ERROR: [SocialAccountService] Failed to refresh token {\"socialAccountId\":1409,\"provider\":\"google\",\"responseBody\":{\"error\":\"invalid_grant\",\"error_description\":\"Bad Request\"}} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:28] local.INFO: [SocialAccountObserver] Saving model {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:28] local.ERROR: [SocialAccountService] Failed to refresh token {\"socialAccountId\":1409,\"provider\":\"google\",\"reason\":\"Flow refresh required.\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:28] local.INFO: [SocialAccountService] Fetching token {\"socialAccountId\":1352,\"provider\":\"google\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:28] local.INFO: [SocialAccountService] Token needs refreshing {\"socialAccountId\":1352,\"provider\":\"google\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:28] local.INFO: [EncryptedTokenManager] Generating access token. {\"mode\":\"legacy\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:28] local.INFO: [SocialAccountService] Refreshing token from provider {\"socialAccountId\":1352,\"provider\":\"google\",\"refreshToken\":\"dd4b16b00fdc1216da6b717c02338c073636e29162826b2de6db3f064fc029eb\",\"state\":\"full-refresh\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:28] local.ERROR: [SocialAccountService] Failed to refresh token {\"socialAccountId\":1352,\"provider\":\"google\",\"responseBody\":{\"error\":\"unauthorized_client\",\"error_description\":\"Unauthorized\"}} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:28] local.INFO: [SocialAccountObserver] Saving model {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:28] local.ERROR: [SocialAccountService] Failed to refresh token {\"socialAccountId\":1352,\"provider\":\"google\",\"reason\":\"Flow refresh required.\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:28] local.INFO: [SocialAccountService] Fetching token {\"socialAccountId\":1296,\"provider\":\"office\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:28] local.INFO: [SocialAccountService] Token needs refreshing {\"socialAccountId\":1296,\"provider\":\"office\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:28] local.INFO: [EncryptedTokenManager] Generating access token. {\"mode\":\"legacy\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:28] local.INFO: [SocialAccountService] Refreshing token from provider {\"socialAccountId\":1296,\"provider\":\"office\",\"refreshToken\":\"011ae723c9d800c674e0b4be76f49fc046dac7d501b66c59ef0d9549cfa56ae5\",\"state\":\"full-refresh\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:28] local.ERROR: [SocialAccountService] Failed to refresh token {\"socialAccountId\":1296,\"provider\":\"office\",\"responseBody\":\"{\\\"error\\\":\\\"invalid_client\\\",\\\"error_description\\\":\\\"AADSTS7000215: Invalid client secret provided. Ensure the secret being sent in the request is the client secret value, not the client secret ID, for a secret added to app 'bbcbb2ef-6200-4fae-82bd-d81f5dd738da'. Trace ID: 73b326f0-9fcd-4ff5-ad61-edd6a0504500 Correlation ID: 6bd8fef3-b24c-46d8-8bba-a9b2ab41c108 Timestamp: 2026-04-27 10:28:28Z\\\",\\\"error_codes\\\":[7000215],\\\"timestamp\\\":\\\"2026-04-27 10:28:28Z\\\",\\\"trace_id\\\":\\\"73b326f0-9fcd-4ff5-ad61-edd6a0504500\\\",\\\"correlation_id\\\":\\\"6bd8fef3-b24c-46d8-8bba-a9b2ab41c108\\\",\\\"error_uri\\\":\\\"https://login.microsoftonline.com/error?code=7000215\\\"}\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:28] local.INFO: [SocialAccountObserver] Saving model {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:28] local.ERROR: [SocialAccountService] Failed to refresh token {\"socialAccountId\":1296,\"provider\":\"office\",\"reason\":\"Flow refresh required.\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:28] local.INFO: [SocialAccountService] Fetching token {\"socialAccountId\":391,\"provider\":\"office\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:28] local.INFO: [SocialAccountService] Token needs refreshing {\"socialAccountId\":391,\"provider\":\"office\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:28] local.INFO: [EncryptedTokenManager] Generating access token. {\"mode\":\"legacy\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:28] local.INFO: [SocialAccountService] Refreshing token from provider {\"socialAccountId\":391,\"provider\":\"office\",\"refreshToken\":\"00045eebae0f39b34887c6d53f92ae78064f7145e1f4b67754aebd03cfb2d881\",\"state\":\"full-refresh\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:28] local.INFO: [SocialAccountService] Fetching token {\"socialAccountId\":1502,\"provider\":\"google\"} {\"correlation_id\":\"8a9488be-81de-40f5-8554-2503eec42566\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:28] local.INFO: [SocialAccountService] Token retrieved {\"socialAccountId\":1502,\"provider\":\"google\"} {\"correlation_id\":\"8a9488be-81de-40f5-8554-2503eec42566\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:28] local.INFO: [EncryptedTokenManager] Generating access token. {\"mode\":\"legacy\"} {\"correlation_id\":\"8a9488be-81de-40f5-8554-2503eec42566\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:29] local.INFO: [Calendar] Processing sync {\"calendarId\":\"a33076c1-8d97-431a-99f0-85c9524e118b\",\"from\":null,\"to\":null,\"delta\":\"CIiFh8TP44kDEIiFh8TP44kDGAUgkZvkzgIokZvkzgI=\",\"last_sync\":\"2024-12-09 07:12:53\",\"dateMode\":\"daily\"} {\"correlation_id\":\"8a9488be-81de-40f5-8554-2503eec42566\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:29] local.INFO: [CrmOwnerResolver] Integration owner matched as CRM Owner {\"crm_provider\":\"integration-app\",\"crm_owner\":1695,\"team_id\":3143} {\"correlation_id\":\"8a9488be-81de-40f5-8554-2503eec42566\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:29] local.INFO: [SocialAccountService] Fetching token {\"socialAccountId\":1502,\"provider\":\"google\"} {\"correlation_id\":\"8a9488be-81de-40f5-8554-2503eec42566\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:29] local.INFO: [SocialAccountService] Token retrieved {\"socialAccountId\":1502,\"provider\":\"google\"} {\"correlation_id\":\"8a9488be-81de-40f5-8554-2503eec42566\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:29] local.INFO: [EncryptedTokenManager] Generating access token. {\"mode\":\"legacy\"} {\"correlation_id\":\"8a9488be-81de-40f5-8554-2503eec42566\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:29] local.ERROR: [SocialAccountService] Failed to refresh token {\"socialAccountId\":391,\"provider\":\"office\",\"responseBody\":\"{\\\"error\\\":\\\"invalid_client\\\",\\\"error_description\\\":\\\"AADSTS7000215: Invalid client secret provided. Ensure the secret being sent in the request is the client secret value, not the client secret ID, for a secret added to app 'bbcbb2ef-6200-4fae-82bd-d81f5dd738da'. Trace ID: c5f1db6f-22fb-41cc-bd89-c46497634200 Correlation ID: 7b4df0cc-bf50-4017-9b1a-e25ffb6fe9bf Timestamp: 2026-04-27 10:28:29Z\\\",\\\"error_codes\\\":[7000215],\\\"timestamp\\\":\\\"2026-04-27 10:28:29Z\\\",\\\"trace_id\\\":\\\"c5f1db6f-22fb-41cc-bd89-c46497634200\\\",\\\"correlation_id\\\":\\\"7b4df0cc-bf50-4017-9b1a-e25ffb6fe9bf\\\",\\\"error_uri\\\":\\\"https://login.microsoftonline.com/error?code=7000215\\\"}\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:29] local.INFO: [SocialAccountObserver] Saving model {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:29] local.ERROR: [SocialAccountService] Failed to refresh token {\"socialAccountId\":391,\"provider\":\"office\",\"reason\":\"Flow refresh required.\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:29] local.INFO: [SocialAccountService] Fetching token {\"socialAccountId\":1271,\"provider\":\"office\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:29] local.INFO: [SocialAccountService] Token needs refreshing {\"socialAccountId\":1271,\"provider\":\"office\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:29] local.INFO: [EncryptedTokenManager] Generating access token. {\"mode\":\"legacy\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:29] local.INFO: [SocialAccountService] Refreshing token from provider {\"socialAccountId\":1271,\"provider\":\"office\",\"refreshToken\":\"118cde2c06993147b07ccaec4cbcd5026a819dea6c71081166a492933e392afb\",\"state\":\"full-refresh\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:29] local.INFO: [Google Calendar] Failed to watch channel for calendar {\"calendarId\":\"a33076c1-8d97-431a-99f0-85c9524e118b\",\"code\":400,\"reason\":\"{\n \\\"error\\\": {\n \\\"errors\\\": [\n {\n \\\"domain\\\": \\\"global\\\",\n \\\"reason\\\": \\\"push.webhookUrlNotHttps\\\",\n \\\"message\\\": \\\"WebHook callback must be HTTPS: /webhook/calendar/google?resourceType=event\\\"\n }\n ],\n \\\"code\\\": 400,\n \\\"message\\\": \\\"WebHook callback must be HTTPS: /webhook/calendar/google?resourceType=event\\\"\n }\n}\"} {\"correlation_id\":\"8a9488be-81de-40f5-8554-2503eec42566\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:29] local.WARNING: [Calendar] Sync failed {\"calendarId\":\"a33076c1-8d97-431a-99f0-85c9524e118b\",\"code\":400,\"reason\":\"{\n \\\"error\\\": {\n \\\"errors\\\": [\n {\n \\\"domain\\\": \\\"global\\\",\n \\\"reason\\\": \\\"push.webhookUrlNotHttps\\\",\n \\\"message\\\": \\\"WebHook callback must be HTTPS: /webhook/calendar/google?resourceType=event\\\"\n }\n ],\n \\\"code\\\": 400,\n \\\"message\\\": \\\"WebHook callback must be HTTPS: /webhook/calendar/google?resourceType=event\\\"\n }\n}\"} {\"correlation_id\":\"8a9488be-81de-40f5-8554-2503eec42566\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:29] local.ERROR: [SocialAccountService] Failed to refresh token {\"socialAccountId\":1271,\"provider\":\"office\",\"responseBody\":\"{\\\"error\\\":\\\"invalid_client\\\",\\\"error_description\\\":\\\"AADSTS7000215: Invalid client secret provided. Ensure the secret being sent in the request is the client secret value, not the client secret ID, for a secret added to app 'bbcbb2ef-6200-4fae-82bd-d81f5dd738da'. Trace ID: 8d6e82ba-88c3-4ec9-95ee-a4a905803900 Correlation ID: 6ede51d5-704d-4e24-9fb7-88f69f3da5db Timestamp: 2026-04-27 10:28:29Z\\\",\\\"error_codes\\\":[7000215],\\\"timestamp\\\":\\\"2026-04-27 10:28:29Z\\\",\\\"trace_id\\\":\\\"8d6e82ba-88c3-4ec9-95ee-a4a905803900\\\",\\\"correlation_id\\\":\\\"6ede51d5-704d-4e24-9fb7-88f69f3da5db\\\",\\\"error_uri\\\":\\\"https://login.microsoftonline.com/error?code=7000215\\\"}\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:30] local.INFO: [SocialAccountObserver] Saving model {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:30] local.ERROR: [SocialAccountService] Failed to refresh token {\"socialAccountId\":1271,\"provider\":\"office\",\"reason\":\"Flow refresh required.\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:30] local.INFO: [SocialAccountService] Fetching token {\"socialAccountId\":1351,\"provider\":\"google\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:30] local.INFO: [SocialAccountService] Token needs refreshing {\"socialAccountId\":1351,\"provider\":\"google\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:30] local.INFO: [EncryptedTokenManager] Generating access token. {\"mode\":\"legacy\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:30] local.INFO: [SocialAccountService] Refreshing token from provider {\"socialAccountId\":1351,\"provider\":\"google\",\"refreshToken\":\"4271d15b9e60a606439caddc68337f783e472c85b03dacff14d1b6dfded9051c\",\"state\":\"full-refresh\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:30] local.ERROR: [SocialAccountService] Failed to refresh token {\"socialAccountId\":1351,\"provider\":\"google\",\"responseBody\":{\"error\":\"invalid_grant\",\"error_description\":\"Token has been expired or revoked.\"}} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:30] local.INFO: [SocialAccountObserver] Saving model {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:30] local.ERROR: [SocialAccountService] Failed to refresh token {\"socialAccountId\":1351,\"provider\":\"google\",\"reason\":\"Flow refresh required.\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:30] local.INFO: [SocialAccountService] Fetching token {\"socialAccountId\":1366,\"provider\":\"google\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:30] local.INFO: [SocialAccountService] Token needs refreshing {\"socialAccountId\":1366,\"provider\":\"google\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:30] local.INFO: [EncryptedTokenManager] Generating access token. {\"mode\":\"legacy\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:30] local.INFO: [SocialAccountService] Refreshing token from provider {\"socialAccountId\":1366,\"provider\":\"google\",\"refreshToken\":\"ae21385059b2eebfd43f68aecd56eccd702a1aabb6598f1f7ab594ed8af491b4\",\"state\":\"full-refresh\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:30] local.ERROR: [SocialAccountService] Failed to refresh token {\"socialAccountId\":1366,\"provider\":\"google\",\"responseBody\":{\"error\":\"invalid_grant\",\"error_description\":\"Bad Request\"}} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:30] local.INFO: [SocialAccountObserver] Saving model {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:30] local.ERROR: [SocialAccountService] Failed to refresh token {\"socialAccountId\":1366,\"provider\":\"google\",\"reason\":\"Flow refresh required.\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:30] local.INFO: [SocialAccountService] Fetching token {\"socialAccountId\":1115,\"provider\":\"google\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:30] local.INFO: [SocialAccountService] Token needs refreshing {\"socialAccountId\":1115,\"provider\":\"google\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:30] local.INFO: [EncryptedTokenManager] Generating access token. {\"mode\":\"legacy\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:30] local.INFO: [SocialAccountService] Refreshing token from provider {\"socialAccountId\":1115,\"provider\":\"google\",\"refreshToken\":\"356b60f12e262a5e24d3042386ef47d6a6cfe3074c242f4426edcec8646192b1\",\"state\":\"connected\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:30] local.INFO: [SocialAccountObserver] Saving model {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:30] local.INFO: [SocialAccountObserver] Access token was modified, encrypting {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:31] local.INFO: [SocialAccountService] Token refreshed {\"socialAccountId\":1115,\"provider\":\"google\",\"state\":\"connected\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:31] local.INFO: Calendar sync job dispatched {\"calendar_id\":378} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:31] local.INFO: [SocialAccountService] Fetching token {\"socialAccountId\":1421,\"provider\":\"office\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:31] local.INFO: [SocialAccountService] Token needs refreshing {\"socialAccountId\":1421,\"provider\":\"office\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:31] local.INFO: [EncryptedTokenManager] Generating access token. {\"mode\":\"legacy\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:31] local.INFO: [SocialAccountService] Refreshing token from provider {\"socialAccountId\":1421,\"provider\":\"office\",\"refreshToken\":\"9e3e287aa0cc900726e3d4eead2600fb64aa34b437790f6a1fd8048306cb2db9\",\"state\":\"connected\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:31] local.INFO: [SocialAccountService] Fetching token {\"socialAccountId\":1115,\"provider\":\"google\"} {\"correlation_id\":\"8fdf411d-8a4f-46c1-a46a-cc84f3c2f2b1\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:31] local.INFO: [SocialAccountService] Token retrieved {\"socialAccountId\":1115,\"provider\":\"google\"} {\"correlation_id\":\"8fdf411d-8a4f-46c1-a46a-cc84f3c2f2b1\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:31] local.INFO: [EncryptedTokenManager] Generating access token. {\"mode\":\"legacy\"} {\"correlation_id\":\"8fdf411d-8a4f-46c1-a46a-cc84f3c2f2b1\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:31] local.INFO: [Calendar] Processing sync {\"calendarId\":\"2676cb6d-f86c-427e-bf78-591e388e3c1e\",\"from\":null,\"to\":null,\"delta\":\"CJ_x49O3jpIDEJ_x49O3jpIDGAUgw67KlwMow67KlwM=\",\"last_sync\":\"2026-01-19 07:48:40\",\"dateMode\":\"daily\"} {\"correlation_id\":\"8fdf411d-8a4f-46c1-a46a-cc84f3c2f2b1\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:31] local.WARNING: [Pipedrive] Account not connected for user {\"userId\":\"e6538737-e7b4-455f-a37a-3e79b665a220\",\"account\":{\"Jiminny\\\\Models\\\\SocialAccount\":{\"id\":1116,\"sociable_id\":241,\"provider_user_id\":\"19555731\",\"expires\":1775683749,\"refresh_token_expires\":null,\"provider\":\"pipedrive\",\"state\":\"full-refresh\",\"auth_scope\":\"base,deals:full,activities:full,contacts:full,search:read\",\"retry_after\":null,\"created_at\":\"2023-09-08 09:44:29\",\"updated_at\":\"2026-04-08 22:58:34\"}}} {\"correlation_id\":\"8fdf411d-8a4f-46c1-a46a-cc84f3c2f2b1\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:31] local.INFO: [CrmOwnerResolver] Integration owner is not connected, attempting team members {\"crm_provider\":\"pipedrive\",\"crm_owner\":241,\"team_id\":19} {\"correlation_id\":\"8fdf411d-8a4f-46c1-a46a-cc84f3c2f2b1\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:31] local.INFO: [CrmOwnerResolver] No team members found with active crm connection {\"crm_provider\":\"pipedrive\",\"team_id\":19} {\"correlation_id\":\"8fdf411d-8a4f-46c1-a46a-cc84f3c2f2b1\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:31] local.INFO: [CrmOwnerResolver] No team member found with active crm connection {\"crm_provider\":\"pipedrive\",\"team_id\":19} {\"correlation_id\":\"8fdf411d-8a4f-46c1-a46a-cc84f3c2f2b1\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:31] local.WARNING: [Calendar] CRM disconnected for user so events will not be matched {\"provider\":\"pipedrive\",\"user_id\":241,\"message\":\"Your Pipedrive account has become disconnected. Please login to Jiminny to reconnect.\"} {\"correlation_id\":\"8fdf411d-8a4f-46c1-a46a-cc84f3c2f2b1\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:31] local.INFO: [SocialAccountService] Fetching token {\"socialAccountId\":1115,\"provider\":\"google\"} {\"correlation_id\":\"8fdf411d-8a4f-46c1-a46a-cc84f3c2f2b1\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:31] local.INFO: [SocialAccountService] Token retrieved {\"socialAccountId\":1115,\"provider\":\"google\"} {\"correlation_id\":\"8fdf411d-8a4f-46c1-a46a-cc84f3c2f2b1\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:31] local.INFO: [EncryptedTokenManager] Generating access token. {\"mode\":\"legacy\"} {\"correlation_id\":\"8fdf411d-8a4f-46c1-a46a-cc84f3c2f2b1\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:31] local.INFO: [Google Calendar] Failed to watch channel for calendar {\"calendarId\":\"2676cb6d-f86c-427e-bf78-591e388e3c1e\",\"code\":400,\"reason\":\"{\n \\\"error\\\": {\n \\\"errors\\\": [\n {\n \\\"domain\\\": \\\"global\\\",\n \\\"reason\\\": \\\"push.webhookUrlNotHttps\\\",\n \\\"message\\\": \\\"WebHook callback must be HTTPS: /webhook/calendar/google?resourceType=event\\\"\n }\n ],\n \\\"code\\\": 400,\n \\\"message\\\": \\\"WebHook callback must be HTTPS: /webhook/calendar/google?resourceType=event\\\"\n }\n}\"} {\"correlation_id\":\"8fdf411d-8a4f-46c1-a46a-cc84f3c2f2b1\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:31] local.WARNING: [Calendar] Sync failed {\"calendarId\":\"2676cb6d-f86c-427e-bf78-591e388e3c1e\",\"code\":400,\"reason\":\"{\n \\\"error\\\": {\n \\\"errors\\\": [\n {\n \\\"domain\\\": \\\"global\\\",\n \\\"reason\\\": \\\"push.webhookUrlNotHttps\\\",\n \\\"message\\\": \\\"WebHook callback must be HTTPS: /webhook/calendar/google?resourceType=event\\\"\n }\n ],\n \\\"code\\\": 400,\n \\\"message\\\": \\\"WebHook callback must be HTTPS: /webhook/calendar/google?resourceType=event\\\"\n }\n}\"} {\"correlation_id\":\"8fdf411d-8a4f-46c1-a46a-cc84f3c2f2b1\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:32] local.INFO: [SocialAccountObserver] Saving model {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:32] local.INFO: [SocialAccountObserver] Access token was modified, encrypting {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:32] local.INFO: [SocialAccountObserver] Refresh token was modified, encrypting {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:32] local.INFO: [SocialAccountService] Token refreshed {\"socialAccountId\":1421,\"provider\":\"office\",\"state\":\"connected\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:32] local.INFO: Calendar sync job dispatched {\"calendar_id\":504} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:32] local.NOTICE: Calendar sync end {\"retrieved_calendars\":31,\"processed_calendars\":3} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:32] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage for command {\"command\":\"calendar:sync\",\"memoryBeforeCommandInMb\":60.0,\"memoryAfterCommandInMB\":62.0,\"memoryPeakBeforeCommandInMb\":99.727,\"memoryPeakAfterCommandInMB\":99.727} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:33] local.INFO: [SocialAccountService] Fetching token {\"socialAccountId\":1421,\"provider\":\"office\"} {\"correlation_id\":\"52b2ff86-82c7-4ed0-b528-35d7d00d69f0\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:33] local.INFO: [SocialAccountService] Token retrieved {\"socialAccountId\":1421,\"provider\":\"office\"} {\"correlation_id\":\"52b2ff86-82c7-4ed0-b528-35d7d00d69f0\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:33] local.INFO: [EncryptedTokenManager] Generating access token. {\"mode\":\"legacy\"} {\"correlation_id\":\"52b2ff86-82c7-4ed0-b528-35d7d00d69f0\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:34] local.INFO: [Calendar] Processing sync {\"calendarId\":\"9e8b1a2c-1a8f-42bd-b161-810fc0baf540\",\"from\":null,\"to\":null,\"delta\":\"R0usmcdvmMuZCBYV0hguCMx0nGVsTQEodM41jSlu-5eY2JUTYHZXE-bDro2tnepp8q_Vq3ITNS8CkncjGjcBfaHkza05dCTIlaXcSIoPNGOl9vNckYy_ZhVgE1SWvgRcCeVT-SyogdB9hP-oaWbH9r51OQrrzAwmalK5aCksyUVh9jG_A-mGVzRPnUB4SKa77o5P659lpx1egON48YU2tQ.D2oErKma-mQrBdXxAKdH7beUkp6HS2PXUWYLCgvbP2c\",\"last_sync\":\"2026-04-27 06:28:41\",\"dateMode\":\"daily\"} {\"correlation_id\":\"52b2ff86-82c7-4ed0-b528-35d7d00d69f0\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:34] local.INFO: [SocialAccountService] Fetching token {\"socialAccountId\":1499,\"provider\":\"hubspot\"} {\"correlation_id\":\"52b2ff86-82c7-4ed0-b528-35d7d00d69f0\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:34] local.INFO: [SocialAccountService] Token retrieved {\"socialAccountId\":1499,\"provider\":\"hubspot\"} {\"correlation_id\":\"52b2ff86-82c7-4ed0-b528-35d7d00d69f0\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:34] local.INFO: [EncryptedTokenManager] Generating access token. {\"mode\":\"legacy\"} {\"correlation_id\":\"52b2ff86-82c7-4ed0-b528-35d7d00d69f0\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:34] local.INFO: [CrmOwnerResolver] Integration owner matched as CRM Owner {\"crm_provider\":\"hubspot\",\"crm_owner\":89,\"team_id\":2} {\"correlation_id\":\"52b2ff86-82c7-4ed0-b528-35d7d00d69f0\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:34] local.INFO: [MS Office Calendar] Skipping delta sync for daily mode {\"calendarId\":\"9e8b1a2c-1a8f-42bd-b161-810fc0baf540\"} {\"correlation_id\":\"52b2ff86-82c7-4ed0-b528-35d7d00d69f0\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:29:06] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage before starting command {\"command\":\"meeting-bot:schedule-bot\",\"memoryBeforeCommandInMb\":60.0,\"memoryPeakBeforeCommandInMb\":99.727} {\"correlation_id\":\"7e95c05b-50c8-4e0b-a01c-efeed6af544c\",\"trace_id\":\"07db4d4f-d6e7-44d9-8e54-4f038ea69afb\"}\n[2026-04-27 10:29:06] local.INFO: [ScheduleBotCommand] Number of activities to be captured: 0 {\"correlation_id\":\"7e95c05b-50c8-4e0b-a01c-efeed6af544c\",\"trace_id\":\"07db4d4f-d6e7-44d9-8e54-4f038ea69afb\"}\n[2026-04-27 10:29:06] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage for command {\"command\":\"meeting-bot:schedule-bot\",\"memoryBeforeCommandInMb\":60.0,\"memoryAfterCommandInMB\":62.0,\"memoryPeakBeforeCommandInMb\":99.727,\"memoryPeakAfterCommandInMB\":99.727} {\"correlation_id\":\"7e95c05b-50c8-4e0b-a01c-efeed6af544c\",\"trace_id\":\"07db4d4f-d6e7-44d9-8e54-4f038ea69afb\"}\n[2026-04-27 10:29:09] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage before starting command {\"command\":\"dialers:monitor-activities\",\"memoryBeforeCommandInMb\":60.0,\"memoryPeakBeforeCommandInMb\":99.727} {\"correlation_id\":\"e6e7fbf8-6e80-4529-858e-a4e95d0d3805\",\"trace_id\":\"f91a115a-9fdc-46ce-b9f2-13a1e6d0e325\"}\n[2026-04-27 10:29:09] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage for command {\"command\":\"dialers:monitor-activities\",\"memoryBeforeCommandInMb\":60.0,\"memoryAfterCommandInMB\":62.0,\"memoryPeakBeforeCommandInMb\":99.727,\"memoryPeakAfterCommandInMB\":99.727} {\"correlation_id\":\"e6e7fbf8-6e80-4529-858e-a4e95d0d3805\",\"trace_id\":\"f91a115a-9fdc-46ce-b9f2-13a1e6d0e325\"}\n[2026-04-27 10:29:10] local.NOTICE: Monitoring start {\"correlation_id\":\"211e5e36-1c6c-4198-b928-d2f1525ab0ea\",\"trace_id\":\"c26e980d-6e32-426d-9667-f1d902432b8d\"}\n[2026-04-27 10:29:10] local.NOTICE: Monitoring end {\"correlation_id\":\"211e5e36-1c6c-4198-b928-d2f1525ab0ea\",\"trace_id\":\"c26e980d-6e32-426d-9667-f1d902432b8d\"}\n[2026-04-27 10:29:12] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage before starting command {\"command\":\"mailbox:skip-lists:refresh\",\"memoryBeforeCommandInMb\":60.0,\"memoryPeakBeforeCommandInMb\":99.727} {\"correlation_id\":\"684bdf68-76ee-42dd-9d97-62f414a33b1b\",\"trace_id\":\"284c98ef-5b7f-4572-996c-72c5bc2995f9\"}\n[2026-04-27 10:29:13] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage for command {\"command\":\"mailbox:skip-lists:refresh\",\"memoryBeforeCommandInMb\":60.0,\"memoryAfterCommandInMB\":62.0,\"memoryPeakBeforeCommandInMb\":99.727,\"memoryPeakAfterCommandInMB\":99.727} {\"correlation_id\":\"684bdf68-76ee-42dd-9d97-62f414a33b1b\",\"trace_id\":\"284c98ef-5b7f-4572-996c-72c5bc2995f9\"}\n[2026-04-27 10:29:18] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage before starting command {\"command\":\"mailbox:batch:process\",\"memoryBeforeCommandInMb\":60.0,\"memoryPeakBeforeCommandInMb\":99.727} {\"correlation_id\":\"6e74b148-ae54-42ff-ab72-2ded18d10ff4\",\"trace_id\":\"d05bc9a0-450c-45b2-be3b-225a551ed909\"}\n[2026-04-27 10:29:18] local.INFO: [EmailSchedule] STARTING batch process {\"host\":\"docker_lamp_1\"} {\"correlation_id\":\"6e74b148-ae54-42ff-ab72-2ded18d10ff4\",\"trace_id\":\"d05bc9a0-450c-45b2-be3b-225a551ed909\"}\n[2026-04-27 10:29:18] local.INFO: [EmailSchedule] FINISHED batch process {\"host\":\"docker_lamp_1\",\"processed\":0} {\"correlation_id\":\"6e74b148-ae54-42ff-ab72-2ded18d10ff4\",\"trace_id\":\"d05bc9a0-450c-45b2-be3b-225a551ed909\"}\n[2026-04-27 10:29:18] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage for command {\"command\":\"mailbox:batch:process\",\"memoryBeforeCommandInMb\":60.0,\"memoryAfterCommandInMB\":62.0,\"memoryPeakBeforeCommandInMb\":99.727,\"memoryPeakAfterCommandInMB\":99.727} {\"correlation_id\":\"6e74b148-ae54-42ff-ab72-2ded18d10ff4\",\"trace_id\":\"d05bc9a0-450c-45b2-be3b-225a551ed909\"}\n[2026-04-27 10:30:08] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage before starting command {\"command\":\"meeting-bot:schedule-bot\",\"memoryBeforeCommandInMb\":60.0,\"memoryPeakBeforeCommandInMb\":99.727} {\"correlation_id\":\"24b807e0-b413-4ecc-a89b-365280be88ac\",\"trace_id\":\"542ac70c-6756-4409-8224-0844a7576d9c\"}\n[2026-04-27 10:30:08] local.INFO: [ScheduleBotCommand] Number of activities to be captured: 0 {\"correlation_id\":\"24b807e0-b413-4ecc-a89b-365280be88ac\",\"trace_id\":\"542ac70c-6756-4409-8224-0844a7576d9c\"}\n[2026-04-27 10:30:08] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage for command {\"command\":\"meeting-bot:schedule-bot\",\"memoryBeforeCommandInMb\":60.0,\"memoryAfterCommandInMB\":62.0,\"memoryPeakBeforeCommandInMb\":99.727,\"memoryPeakAfterCommandInMB\":99.727} {\"correlation_id\":\"24b807e0-b413-4ecc-a89b-365280be88ac\",\"trace_id\":\"542ac70c-6756-4409-8224-0844a7576d9c\"}\n[2026-04-27 10:30:10] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage before starting command {\"command\":\"dialers:monitor-activities\",\"memoryBeforeCommandInMb\":60.0,\"memoryPeakBeforeCommandInMb\":99.727} {\"correlation_id\":\"2711ca04-b4a7-4f3a-b0b2-5826fea4da3b\",\"trace_id\":\"2613ffca-f181-4d91-ad55-ec1bdd97ee7e\"}\n[2026-04-27 10:30:10] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage for command {\"command\":\"dialers:monitor-activities\",\"memoryBeforeCommandInMb\":60.0,\"memoryAfterCommandInMB\":62.0,\"memoryPeakBeforeCommandInMb\":99.727,\"memoryPeakAfterCommandInMB\":99.727} {\"correlation_id\":\"2711ca04-b4a7-4f3a-b0b2-5826fea4da3b\",\"trace_id\":\"2613ffca-f181-4d91-ad55-ec1bdd97ee7e\"}\n[2026-04-27 10:30:17] local.NOTICE: Monitoring start {\"correlation_id\":\"e1537475-8481-4144-b9f5-596ea8034b8a\",\"trace_id\":\"f5561d0a-0cb7-4c72-8bde-cce617183529\"}\n[2026-04-27 10:30:17] local.NOTICE: Monitoring end {\"correlation_id\":\"e1537475-8481-4144-b9f5-596ea8034b8a\",\"trace_id\":\"f5561d0a-0cb7-4c72-8bde-cce617183529\"}\n[2026-04-27 10:30:21] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage before starting command {\"command\":\"mailbox:skip-lists:refresh\",\"memoryBeforeCommandInMb\":60.0,\"memoryPeakBeforeCommandInMb\":99.727} {\"correlation_id\":\"8a911b60-16a0-416e-934e-3b7e0280e452\",\"trace_id\":\"3c2bbdd6-985d-4624-b42b-fd9cc2b4756f\"}\n[2026-04-27 10:30:21] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage for command {\"command\":\"mailbox:skip-lists:refresh\",\"memoryBeforeCommandInMb\":60.0,\"memoryAfterCommandInMB\":62.0,\"memoryPeakBeforeCommandInMb\":99.727,\"memoryPeakAfterCommandInMB\":99.727} {\"correlation_id\":\"8a911b60-16a0-416e-934e-3b7e0280e452\",\"trace_id\":\"3c2bbdd6-985d-4624-b42b-fd9cc2b4756f\"}\n[2026-04-27 10:30:25] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage before starting command {\"command\":\"mailbox:batch:process\",\"memoryBeforeCommandInMb\":60.0,\"memoryPeakBeforeCommandInMb\":99.727} {\"correlation_id\":\"f62efaa6-201f-43b2-bf45-9619982dffb1\",\"trace_id\":\"f1ad3d60-af4e-4427-9d77-6aa71cfed172\"}\n[2026-04-27 10:30:25] local.INFO: [EmailSchedule] STARTING batch process {\"host\":\"docker_lamp_1\"} {\"correlation_id\":\"f62efaa6-201f-43b2-bf45-9619982dffb1\",\"trace_id\":\"f1ad3d60-af4e-4427-9d77-6aa71cfed172\"}\n[2026-04-27 10:30:25] local.INFO: [EmailSchedule] FINISHED batch process {\"host\":\"docker_lamp_1\",\"processed\":0} {\"correlation_id\":\"f62efaa6-201f-43b2-bf45-9619982dffb1\",\"trace_id\":\"f1ad3d60-af4e-4427-9d77-6aa71cfed172\"}\n[2026-04-27 10:30:25] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage for command {\"command\":\"mailbox:batch:process\",\"memoryBeforeCommandInMb\":60.0,\"memoryAfterCommandInMB\":62.0,\"memoryPeakBeforeCommandInMb\":99.727,\"memoryPeakAfterCommandInMB\":99.727} {\"correlation_id\":\"f62efaa6-201f-43b2-bf45-9619982dffb1\",\"trace_id\":\"f1ad3d60-af4e-4427-9d77-6aa71cfed172\"}\n[2026-04-27 10:30:30] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage before starting command {\"command\":\"conference:monitor:count\",\"memoryBeforeCommandInMb\":60.0,\"memoryPeakBeforeCommandInMb\":99.727} {\"correlation_id\":\"cf6dc132-ecb8-4efc-bead-06bf1c96cae6\",\"trace_id\":\"9e2b073b-940d-498a-babe-115f633dd954\"}\n[2026-04-27 10:30:30] local.INFO: Running conference:monitor:count command for activities in (2026-04-27 10:28:00, 2026-04-27 10:30:00] {\"correlation_id\":\"cf6dc132-ecb8-4efc-bead-06bf1c96cae6\",\"trace_id\":\"9e2b073b-940d-498a-babe-115f633dd954\"}\n[2026-04-27 10:30:30] local.INFO: [conference:monitor:count] No activities found in (2026-04-27 10:28:00, 2026-04-27 10:30:00] {\"correlation_id\":\"cf6dc132-ecb8-4efc-bead-06bf1c96cae6\",\"trace_id\":\"9e2b073b-940d-498a-babe-115f633dd954\"}\n[2026-04-27 10:30:30] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage for command {\"command\":\"conference:monitor:count\",\"memoryBeforeCommandInMb\":60.0,\"memoryAfterCommandInMB\":62.0,\"memoryPeakBeforeCommandInMb\":99.727,\"memoryPeakAfterCommandInMB\":99.727} {\"correlation_id\":\"cf6dc132-ecb8-4efc-bead-06bf1c96cae6\",\"trace_id\":\"9e2b073b-940d-498a-babe-115f633dd954\"}\n[2026-04-27 10:30:32] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage before starting command {\"command\":\"activity:purge-stale\",\"memoryBeforeCommandInMb\":60.0,\"memoryPeakBeforeCommandInMb\":99.727} {\"correlation_id\":\"f455260d-d1c1-456f-8937-d4bb026d9078\",\"trace_id\":\"f8c2e585-0137-4103-9e16-1b4ce05df2ef\"}\n[2026-04-27 10:30:32] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage for command {\"command\":\"activity:purge-stale\",\"memoryBeforeCommandInMb\":60.0,\"memoryAfterCommandInMB\":62.0,\"memoryPeakBeforeCommandInMb\":99.727,\"memoryPeakAfterCommandInMB\":99.727} {\"correlation_id\":\"f455260d-d1c1-456f-8937-d4bb026d9078\",\"trace_id\":\"f8c2e585-0137-4103-9e16-1b4ce05df2ef\"}\n[2026-04-27 10:30:35] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage before starting command {\"command\":\"mailbox:text-relay:sync\",\"memoryBeforeCommandInMb\":60.0,\"memoryPeakBeforeCommandInMb\":99.727} {\"correlation_id\":\"be7d3c6a-664a-42e8-a4b3-cbd021296404\",\"trace_id\":\"0b250827-4c02-4237-8b34-e5ddd004eba1\"}\n[2026-04-27 10:30:35] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage for command {\"command\":\"mailbox:text-relay:sync\",\"memoryBeforeCommandInMb\":60.0,\"memoryAfterCommandInMB\":62.0,\"memoryPeakBeforeCommandInMb\":99.727,\"memoryPeakAfterCommandInMB\":99.727} {\"correlation_id\":\"be7d3c6a-664a-42e8-a4b3-cbd021296404\",\"trace_id\":\"0b250827-4c02-4237-8b34-e5ddd004eba1\"}\n[2026-04-27 10:30:37] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage before starting command {\"command\":\"conference:pre-meeting-notification\",\"memoryBeforeCommandInMb\":60.0,\"memoryPeakBeforeCommandInMb\":99.727} {\"correlation_id\":\"5d8fbdf6-2737-4d73-babe-699c88279e1f\",\"trace_id\":\"0882a963-b02f-4049-b670-12b92f80829a\"}\n[2026-04-27 10:30:37] local.INFO: Running pre-meeting notification command {\"correlation_id\":\"5d8fbdf6-2737-4d73-babe-699c88279e1f\",\"trace_id\":\"0882a963-b02f-4049-b670-12b92f80829a\"}\n[2026-04-27 10:30:37] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage for command {\"command\":\"conference:pre-meeting-notification\",\"memoryBeforeCommandInMb\":60.0,\"memoryAfterCommandInMB\":62.0,\"memoryPeakBeforeCommandInMb\":99.727,\"memoryPeakAfterCommandInMB\":99.727} {\"correlation_id\":\"5d8fbdf6-2737-4d73-babe-699c88279e1f\",\"trace_id\":\"0882a963-b02f-4049-b670-12b92f80829a\"}\n[2026-04-27 10:30:39] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage before starting command {\"command\":\"conference:monitor:start\",\"memoryBeforeCommandInMb\":60.0,\"memoryPeakBeforeCommandInMb\":99.727} {\"correlation_id\":\"e8968e47-9acd-4bd4-9e7d-3c415f46df05\",\"trace_id\":\"287fba85-f395-41ab-813d-49cb5d0ea06d\"}\n[2026-04-27 10:30:39] local.INFO: Running conference:monitor:start command for activities in (2026-04-27 10:20:00, 2026-04-27 10:25:00] {\"correlation_id\":\"e8968e47-9acd-4bd4-9e7d-3c415f46df05\",\"trace_id\":\"287fba85-f395-41ab-813d-49cb5d0ea06d\"}\n[2026-04-27 10:30:39] local.INFO: [conference:monitor:start] No activities found in (2026-04-27 10:20:00, 2026-04-27 10:25:00] {\"correlation_id\":\"e8968e47-9acd-4bd4-9e7d-3c415f46df05\",\"trace_id\":\"287fba85-f395-41ab-813d-49cb5d0ea06d\"}\n[2026-04-27 10:30:39] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage for command {\"command\":\"conference:monitor:start\",\"memoryBeforeCommandInMb\":60.0,\"memoryAfterCommandInMB\":62.0,\"memoryPeakBeforeCommandInMb\":99.727,\"memoryPeakAfterCommandInMB\":99.727} {\"correlation_id\":\"e8968e47-9acd-4bd4-9e7d-3c415f46df05\",\"trace_id\":\"287fba85-f395-41ab-813d-49cb5d0ea06d\"}\n[2026-04-27 10:30:43] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage before starting command {\"command\":\"conference:monitor:end\",\"memoryBeforeCommandInMb\":60.0,\"memoryPeakBeforeCommandInMb\":99.727} {\"correlation_id\":\"b87eb011-7108-46bd-aa90-975ebe41f483\",\"trace_id\":\"1f54c2a6-fad1-4804-87d8-4972a378e91b\"}\n[2026-04-27 10:30:43] local.INFO: conference:monitor:end:Jiminny\\Console\\Commands\\Activities\\MonitorMeetingEndCommand::logActivitiesEnded {\"from\":\"10:25\",\"to\":\"10:30\"} {\"correlation_id\":\"b87eb011-7108-46bd-aa90-975ebe41f483\",\"trace_id\":\"1f54c2a6-fad1-4804-87d8-4972a378e91b\"}\n[2026-04-27 10:30:44] local.INFO: conference:monitor:end:Jiminny\\Console\\Commands\\Activities\\MonitorMeetingEndCommand::logActivitiesWithUnfinishedSession {\"from\":\"00:20\",\"to\":\"00:25\"} {\"correlation_id\":\"b87eb011-7108-46bd-aa90-975ebe41f483\",\"trace_id\":\"1f54c2a6-fad1-4804-87d8-4972a378e91b\"}\n[2026-04-27 10:30:44] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage for command {\"command\":\"conference:monitor:end\",\"memoryBeforeCommandInMb\":60.0,\"memoryAfterCommandInMB\":62.0,\"memoryPeakBeforeCommandInMb\":99.727,\"memoryPeakAfterCommandInMB\":99.727} {\"correlation_id\":\"b87eb011-7108-46bd-aa90-975ebe41f483\",\"trace_id\":\"1f54c2a6-fad1-4804-87d8-4972a378e91b\"}\n[2026-04-27 10:30:51] local.NOTICE: Repairing HubSpot tokens start {\"correlation_id\":\"2e6dfa3d-f07d-4eee-8232-6138133e2fbb\",\"trace_id\":\"41dea86e-0ecc-414e-8303-e60d8ae09976\"}\n[2026-04-27 10:30:51] local.INFO: Trying to refresh HubSpot token {\"account_id\":59,\"updated_at\":\"2025-10-03 09:32:05\"} {\"correlation_id\":\"2e6dfa3d-f07d-4eee-8232-6138133e2fbb\",\"trace_id\":\"41dea86e-0ecc-414e-8303-e60d8ae09976\"}\n[2026-04-27 10:30:51] local.INFO: [EncryptedTokenManager] Generating access token. {\"mode\":\"legacy\"} {\"correlation_id\":\"2e6dfa3d-f07d-4eee-8232-6138133e2fbb\",\"trace_id\":\"41dea86e-0ecc-414e-8303-e60d8ae09976\"}\n[2026-04-27 10:30:51] local.INFO: [SocialAccountService] Refreshing token from provider {\"socialAccountId\":59,\"provider\":\"hubspot\",\"refreshToken\":\"97b78f6e2cc49965c00c2492b602b02708b1392551e6b3f113fbaa48992af90b\",\"state\":\"full-refresh\"} {\"correlation_id\":\"2e6dfa3d-f07d-4eee-8232-6138133e2fbb\",\"trace_id\":\"41dea86e-0ecc-414e-8303-e60d8ae09976\"}\n[2026-04-27 10:30:52] local.ERROR: Failed to refresh HubSpot token {\"account_id\":59,\"updated_at\":\"2025-10-03 09:32:05\",\"reason\":\"missing or invalid refresh token\",\"previous\":\"\"} {\"correlation_id\":\"2e6dfa3d-f07d-4eee-8232-6138133e2fbb\",\"trace_id\":\"41dea86e-0ecc-414e-8303-e60d8ae09976\"}\n[2026-04-27 10:30:52] local.INFO: Trying to refresh HubSpot token {\"account_id\":306,\"updated_at\":\"2023-11-27 09:30:03\"} {\"correlation_id\":\"2e6dfa3d-f07d-4eee-8232-6138133e2fbb\",\"trace_id\":\"41dea86e-0ecc-414e-8303-e60d8ae09976\"}\n[2026-04-27 10:30:52] local.INFO: [EncryptedTokenManager] Generating access token. {\"mode\":\"legacy\"} {\"correlation_id\":\"2e6dfa3d-f07d-4eee-8232-6138133e2fbb\",\"trace_id\":\"41dea86e-0ecc-414e-8303-e60d8ae09976\"}\n[2026-04-27 10:30:52] local.INFO: [SocialAccountService] Refreshing token from provider {\"socialAccountId\":306,\"provider\":\"hubspot\",\"refreshToken\":\"6fa6aa8cc641d131231acc3470f5c03cb3b07b2e580fb18f8acb3b1dbb72549b\",\"state\":\"full-refresh\"} {\"correlation_id\":\"2e6dfa3d-f07d-4eee-8232-6138133e2fbb\",\"trace_id\":\"41dea86e-0ecc-414e-8303-e60d8ae09976\"}\n[2026-04-27 10:30:52] local.ERROR: Failed to refresh HubSpot token {\"account_id\":306,\"updated_at\":\"2023-11-27 09:30:03\",\"reason\":\"missing or invalid refresh token\",\"previous\":\"\"} {\"correlation_id\":\"2e6dfa3d-f07d-4eee-8232-6138133e2fbb\",\"trace_id\":\"41dea86e-0ecc-414e-8303-e60d8ae09976\"}\n[2026-04-27 10:30:52] local.INFO: Trying to refresh HubSpot token {\"account_id\":1372,\"updated_at\":\"2025-10-02 14:47:06\"} {\"correlation_id\":\"2e6dfa3d-f07d-4eee-8232-6138133e2fbb\",\"trace_id\":\"41dea86e-0ecc-414e-8303-e60d8ae09976\"}\n[2026-04-27 10:30:52] local.INFO: [EncryptedTokenManager] Generating access token. {\"mode\":\"legacy\"} {\"correlation_id\":\"2e6dfa3d-f07d-4eee-8232-6138133e2fbb\",\"trace_id\":\"41dea86e-0ecc-414e-8303-e60d8ae09976\"}\n[2026-04-27 10:30:52] local.INFO: [SocialAccountService] Refreshing token from provider {\"socialAccountId\":1372,\"provider\":\"hubspot\",\"refreshToken\":\"9aa73948c761da29dce46c177cf9aee1fde483a44169ca38723f9f0597d7a8c4\",\"state\":\"full-refresh\"} {\"correlation_id\":\"2e6dfa3d-f07d-4eee-8232-6138133e2fbb\",\"trace_id\":\"41dea86e-0ecc-414e-8303-e60d8ae09976\"}\n[2026-04-27 10:30:52] local.ERROR: Failed to refresh HubSpot token {\"account_id\":1372,\"updated_at\":\"2025-10-02 14:47:06\",\"reason\":\"missing or invalid refresh token\",\"previous\":\"\"} {\"correlation_id\":\"2e6dfa3d-f07d-4eee-8232-6138133e2fbb\",\"trace_id\":\"41dea86e-0ecc-414e-8303-e60d8ae09976\"}\n[2026-04-27 10:30:52] local.NOTICE: Repairing HubSpot tokens end {\"total\":3,\"fixed\":0,\"failed\":3} {\"correlation_id\":\"2e6dfa3d-f07d-4eee-8232-6138133e2fbb\",\"trace_id\":\"41dea86e-0ecc-414e-8303-e60d8ae09976\"}\n[2026-04-27 10:30:56] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage before starting command {\"command\":\"jiminny:transcription:retry-failed\",\"memoryBeforeCommandInMb\":60.0,\"memoryPeakBeforeCommandInMb\":99.727} {\"correlation_id\":\"a6655a64-38ad-4c65-96f1-f5a294dbb343\",\"trace_id\":\"52cdca32-c0c8-4215-81a2-4263bbde2158\"}\n[2026-04-27 10:30:56] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage before starting command {\"command\":\"conference:pre-meeting-reminder\",\"memoryBeforeCommandInMb\":60.0,\"memoryPeakBeforeCommandInMb\":99.727} {\"correlation_id\":\"ec2d49dd-ab36-4f4e-8a60-7bf5ea384cac\",\"trace_id\":\"b3e89960-9a27-4bf7-8b55-95f5704f9a10\"}\n[2026-04-27 10:30:57] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage for command {\"command\":\"jiminny:transcription:retry-failed\",\"memoryBeforeCommandInMb\":60.0,\"memoryAfterCommandInMB\":62.0,\"memoryPeakBeforeCommandInMb\":99.727,\"memoryPeakAfterCommandInMB\":99.727} {\"correlation_id\":\"a6655a64-38ad-4c65-96f1-f5a294dbb343\",\"trace_id\":\"52cdca32-c0c8-4215-81a2-4263bbde2158\"}\n[2026-04-27 10:30:57] local.INFO: [HubSpot Journal Polling] Getting offset from database {\"offset\":\"\",\"jiminny_team_id\":1} {\"correlation_id\":\"1f928e93-b17e-4518-a1b9-a8280d1d95e8\",\"trace_id\":\"ba203ed6-fe98-4fd7-aa62-8de9fb8ffeca\"}\n[2026-04-27 10:30:57] local.INFO: [HubSpot Journal Command] Starting polling service {\"correlation_id\":\"1f928e93-b17e-4518-a1b9-a8280d1d95e8\",\"trace_id\":\"ba203ed6-fe98-4fd7-aa62-8de9fb8ffeca\"}\n[2026-04-27 10:30:57] local.INFO: [HubSpot Journal Polling] Service starting {\"memory_limit\":\"256M\",\"max_execution_time\":\"0\",\"initial_memory_mb\":62.0} {\"correlation_id\":\"1f928e93-b17e-4518-a1b9-a8280d1d95e8\",\"trace_id\":\"ba203ed6-fe98-4fd7-aa62-8de9fb8ffeca\"}\n[2026-04-27 10:30:57] local.INFO: [HubSpot Journal Polling] Acquired polling lock {\"expires_at\":\"2026-04-27T10:32:57.492763Z\"} {\"correlation_id\":\"1f928e93-b17e-4518-a1b9-a8280d1d95e8\",\"trace_id\":\"ba203ed6-fe98-4fd7-aa62-8de9fb8ffeca\"}\n[2026-04-27 10:30:57] local.INFO: [HubSpot Journal Polling] Getting offset from database {\"offset\":\"\",\"jiminny_team_id\":1} {\"correlation_id\":\"1f928e93-b17e-4518-a1b9-a8280d1d95e8\",\"trace_id\":\"ba203ed6-fe98-4fd7-aa62-8de9fb8ffeca\"}\n[2026-04-27 10:30:57] local.INFO: [HubSpot Journal API] Fetching latest journal entry {\"url\":\"https://api.hubapi.com/webhooks/v4/journal/latest\"} {\"correlation_id\":\"1f928e93-b17e-4518-a1b9-a8280d1d95e8\",\"trace_id\":\"ba203ed6-fe98-4fd7-aa62-8de9fb8ffeca\"}\n[2026-04-27 10:30:57] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage for command {\"command\":\"conference:pre-meeting-reminder\",\"memoryBeforeCommandInMb\":60.0,\"memoryAfterCommandInMB\":62.0,\"memoryPeakBeforeCommandInMb\":99.727,\"memoryPeakAfterCommandInMB\":99.727} {\"correlation_id\":\"ec2d49dd-ab36-4f4e-8a60-7bf5ea384cac\",\"trace_id\":\"b3e89960-9a27-4bf7-8b55-95f5704f9a10\"}\n[2026-04-27 10:30:58] local.INFO: [HubSpot Journal Polling] No data {\"correlation_id\":\"1f928e93-b17e-4518-a1b9-a8280d1d95e8\",\"trace_id\":\"ba203ed6-fe98-4fd7-aa62-8de9fb8ffeca\"}\n[2026-04-27 10:31:02] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage before starting command {\"command\":\"crm:reset-governor\",\"memoryBeforeCommandInMb\":60.0,\"memoryPeakBeforeCommandInMb\":99.727} {\"correlation_id\":\"f261cd75-c916-4f21-b4f4-856fe0ed315c\",\"trace_id\":\"6db8819d-ffaa-4fe8-8ccd-0dcc326b8cdc\"}\n[2026-04-27 10:31:02] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage for command {\"command\":\"crm:reset-governor\",\"memoryBeforeCommandInMb\":60.0,\"memoryAfterCommandInMB\":62.0,\"memoryPeakBeforeCommandInMb\":99.727,\"memoryPeakAfterCommandInMB\":99.727} {\"correlation_id\":\"f261cd75-c916-4f21-b4f4-856fe0ed315c\",\"trace_id\":\"6db8819d-ffaa-4fe8-8ccd-0dcc326b8cdc\"}\n[2026-04-27 10:31:03] local.INFO: [HubSpot Journal Polling] Getting offset from database {\"offset\":\"\",\"jiminny_team_id\":1} {\"correlation_id\":\"1f928e93-b17e-4518-a1b9-a8280d1d95e8\",\"trace_id\":\"ba203ed6-fe98-4fd7-aa62-8de9fb8ffeca\"}\n[2026-04-27 10:31:03] local.INFO: [HubSpot Journal API] Fetching latest journal entry {\"url\":\"https://api.hubapi.com/webhooks/v4/journal/latest\"} {\"correlation_id\":\"1f928e93-b17e-4518-a1b9-a8280d1d95e8\",\"trace_id\":\"ba203ed6-fe98-4fd7-aa62-8de9fb8ffeca\"}\n[2026-04-27 10:31:03] local.INFO: [HubSpot Journal Polling] No data {\"correlation_id\":\"1f928e93-b17e-4518-a1b9-a8280d1d95e8\",\"trace_id\":\"ba203ed6-fe98-4fd7-aa62-8de9fb8ffeca\"}\n[2026-04-27 10:31:06] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage before starting command {\"command\":\"activity:sync\",\"memoryBeforeCommandInMb\":60.0,\"memoryPeakBeforeCommandInMb\":99.727} {\"correlation_id\":\"f9151ed9-60aa-4b2b-b962-9eab5f75405b\",\"trace_id\":\"dba432b8-cb4b-4e76-8684-01d96f91e453\"}\n[2026-04-27 10:31:07] local.INFO: Dispatching activity sync job {\"import_id\":811739,\"provider\":\"twilio-flex\",\"team\":\"jiminny\"} {\"correlation_id\":\"f9151ed9-60aa-4b2b-b962-9eab5f75405b\",\"trace_id\":\"dba432b8-cb4b-4e76-8684-01d96f91e453\"}\n[2026-04-27 10:31:07] local.INFO: Dispatching activity sync job {\"import_id\":811740,\"provider\":\"xant\",\"team\":\"jiminny\"} {\"correlation_id\":\"f9151ed9-60aa-4b2b-b962-9eab5f75405b\",\"trace_id\":\"dba432b8-cb4b-4e76-8684-01d96f91e453\"}\n[2026-04-27 10:31:07] local.INFO: Dispatching activity sync job {\"import_id\":811741,\"provider\":\"apollo\",\"team\":\"jiminny\"} {\"correlation_id\":\"f9151ed9-60aa-4b2b-b962-9eab5f75405b\",\"trace_id\":\"dba432b8-cb4b-4e76-8684-01d96f91e453\"}\n[2026-04-27 10:31:07] local.INFO: Dispatching activity sync job {\"import_id\":811742,\"provider\":\"groove\",\"team\":\"jiminny\"} {\"correlation_id\":\"f9151ed9-60aa-4b2b-b962-9eab5f75405b\",\"trace_id\":\"dba432b8-cb4b-4e76-8684-01d96f91e453\"}\n[2026-04-27 10:31:07] local.INFO: Dispatching activity sync job {\"import_id\":811743,\"provider\":\"twilio-video\",\"team\":\"jiminny\"} {\"correlation_id\":\"f9151ed9-60aa-4b2b-b962-9eab5f75405b\",\"trace_id\":\"dba432b8-cb4b-4e76-8684-01d96f91e453\"}\n[2026-04-27 10:31:07] local.INFO: Dispatching activity sync job {\"import_id\":811744,\"provider\":\"hubspot\",\"team\":\"hubspot\"} {\"correlation_id\":\"f9151ed9-60aa-4b2b-b962-9eab5f75405b\",\"trace_id\":\"dba432b8-cb4b-4e76-8684-01d96f91e453\"}\n[2026-04-27 10:31:07] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage for command {\"command\":\"activity:sync\",\"memoryBeforeCommandInMb\":60.0,\"memoryAfterCommandInMB\":62.0,\"memoryPeakBeforeCommandInMb\":99.727,\"memoryPeakAfterCommandInMB\":99.727} {\"correlation_id\":\"f9151ed9-60aa-4b2b-b962-9eab5f75405b\",\"trace_id\":\"dba432b8-cb4b-4e76-8684-01d96f91e453\"}\n[2026-04-27 10:31:07] local.WARNING: [Salesforce] Account not connected for user {\"userId\":\"cdf8b554-d951-4758-bc2b-c1b85d1cd0b9\",\"account\":null} {\"correlation_id\":\"e7a521c5-fad4-48c2-b880-d572b5d237f4\",\"trace_id\":\"dba432b8-cb4b-4e76-8684-01d96f91e453\"}\n[2026-04-27 10:31:07] local.INFO: [CrmOwnerResolver] Integration owner is not connected, attempting team members {\"crm_provider\":\"salesforce\",\"crm_owner\":3,\"team_id\":1} {\"correlation_id\":\"e7a521c5-fad4-48c2-b880-d572b5d237f4\",\"trace_id\":\"dba432b8-cb4b-4e76-8684-01d96f91e453\"}\n[2026-04-27 10:31:07] local.INFO: [SocialAccountService] Fetching token {\"socialAccountId\":1500,\"provider\":\"salesforce\"} {\"correlation_id\":\"e7a521c5-fad4-48c2-b880-d572b5d237f4\",\"trace_id\":\"dba432b8-cb4b-4e76-8684-01d96f91e453\"}\n[2026-04-27 10:31:07] local.INFO: [SocialAccountService] Token retrieved {\"socialAccountId\":1500,\"provider\":\"salesforce\"} {\"correlation_id\":\"e7a521c5-fad4-48c2-b880-d572b5d237f4\",\"trace_id\":\"dba432b8-cb4b-4e76-8684-01d96f91e453\"}\n[2026-04-27 10:31:07] local.INFO: [EncryptedTokenManager] Generating access token. {\"mode\":\"legacy\"} {\"correlation_id\":\"e7a521c5-fad4-48c2-b880-d572b5d237f4\",\"trace_id\":\"dba432b8-cb4b-4e76-8684-01d96f91e453\"}\n[2026-04-27 10:31:07] local.INFO: [CrmOwnerResolver] TeamMember found with active crm connection {\"crm_provider\":\"salesforce\",\"crm_owner\":143,\"team_id\":1} {\"correlation_id\":\"e7a521c5-fad4-48c2-b880-d572b5d237f4\",\"trace_id\":\"dba432b8-cb4b-4e76-8684-01d96f91e453\"}\n[2026-04-27 10:31:07] local.INFO: [SocialAccountService] Fetching token {\"socialAccountId\":1194,\"provider\":\"twilio-flex\"} {\"correlation_id\":\"e7a521c5-fad4-48c2-b880-d572b5d237f4\",\"trace_id\":\"dba432b8-cb4b-4e76-8684-01d96f91e453\"}\n[2026-04-27 10:31:07] local.INFO: [SocialAccountService] Token retrieved {\"socialAccountId\":1194,\"provider\":\"twilio-flex\"} {\"correlation_id\":\"e7a521c5-fad4-48c2-b880-d572b5d237f4\",\"trace_id\":\"dba432b8-cb4b-4e76-8684-01d96f91e453\"}\n[2026-04-27 10:31:07] local.INFO: [EncryptedTokenManager] Generating access token. {\"mode\":\"legacy\"} {\"correlation_id\":\"e7a521c5-fad4-48c2-b880-d572b5d237f4\",\"trace_id\":\"dba432b8-cb4b-4e76-8684-01d96f91e453\"}\n[2026-04-27 10:31:07] local.INFO: [SyncActivity] Start {\"import_id\":811739,\"provider\":\"twilio-flex\",\"provider_id\":317,\"team\":\"jiminny\",\"team_id\":1} {\"correlation_id\":\"e7a521c5-fad4-48c2-b880-d572b5d237f4\",\"trace_id\":\"dba432b8-cb4b-4e76-8684-01d96f91e453\"}\n[2026-04-27 10:31:07] local.NOTICE: [TwilioFlex] Calls import start {\"correlation_id\":\"e7a521c5-fad4-48c2-b880-d572b5d237f4\",\"trace_id\":\"dba432b8-cb4b-4e76-8684-01d96f91e453\"}\n[2026-04-27 10:31:08] local.ALERT: [SyncActivity] Failed {\"import_id\":811739,\"provider\":\"twilio-flex\",\"provider_id\":317,\"team\":\"jiminny\",\"team_id\":1,\"reason\":\"[HTTP 401] Unable to fetch page: Authenticate\",\"file\":\"/home/jiminny/vendor/twilio/sdk/src/Twilio/Page.php\",\"line\":60} {\"correlation_id\":\"e7a521c5-fad4-48c2-b880-d572b5d237f4\",\"trace_id\":\"dba432b8-cb4b-4e76-8684-01d96f91e453\"}\n[2026-04-27 10:31:08] local.INFO: [HubSpot Journal Polling] Getting offset from database {\"offset\":\"\",\"jiminny_team_id\":1} {\"correlation_id\":\"1f928e93-b17e-4518-a1b9-a8280d1d95e8\",\"trace_id\":\"ba203ed6-fe98-4fd7-aa62-8de9fb8ffeca\"}\n[2026-04-27 10:31:08] local.INFO: [HubSpot Journal API] Fetching latest journal entry {\"url\":\"https://api.hubapi.com/webhooks/v4/journal/latest\"} {\"correlation_id\":\"1f928e93-b17e-4518-a1b9-a8280d1d95e8\",\"trace_id\":\"ba203ed6-fe98-4fd7-aa62-8de9fb8ffeca\"}\n[2026-04-27 10:31:09] local.INFO: [SocialAccountService] Fetching token {\"socialAccountId\":1500,\"provider\":\"salesforce\"} {\"correlation_id\":\"73848601-79bb-4161-91ae-37acf4385d24\",\"trace_id\":\"dba432b8-cb4b-4e76-8684-01d96f91e453\"}\n[2026-04-27 10:31:09] local.INFO: [SocialAccountService] Token retrieved {\"socialAccountId\":1500,\"provider\":\"salesforce\"} {\"correlation_id\":\"73848601-79bb-4161-91ae-37acf4385d24\",\"trace_id\":\"dba432b8-cb4b-4e76-8684-01d96f91e453\"}\n[2026-04-27 10:31:09] local.INFO: [EncryptedTokenManager] Generating access token. {\"mode\":\"legacy\"} {\"correlation_id\":\"73848601-79bb-4161-91ae-37acf4385d24\",\"trace_id\":\"dba432b8-cb4b-4e76-8684-01d96f91e453\"}\n[2026-04-27 10:31:09] local.INFO: [CrmOwnerResolver] Integration owner matched as CRM Owner {\"crm_provider\":\"salesforce\",\"crm_owner\":143,\"team_id\":1} {\"correlation_id\":\"73848601-79bb-4161-91ae-37acf4385d24\",\"trace_id\":\"dba432b8-cb4b-4e76-8684-01d96f91e453\"}\n[2026-04-27 10:31:09] local.INFO: [SocialAccountService] Fetching token {\"socialAccountId\":1500,\"provider\":\"salesforce\"} {\"correlation_id\":\"73848601-79bb-4161-91ae-37acf4385d24\",\"trace_id\":\"dba432b8-cb4b-4e76-8684-01d96f91e453\"}\n[2026-04-27 10:31:09] local.INFO: [HubSpot Journal Polling] No data {\"correlation_id\":\"1f928e93-b17e-4518-a1b9-a8280d1d95e8\",\"trace_id\":\"ba203ed6-fe98-4fd7-aa62-8de9fb8ffeca\"}\n[2026-04-27 10:31:09] local.INFO: [SocialAccountService] Token retrieved {\"socialAccountId\":1500,\"provider\":\"salesforce\"} {\"correlation_id\":\"73848601-79bb-4161-91ae-37acf4385d24\",\"trace_id\":\"dba432b8-cb4b-4e76-8684-01d96f91e453\"}\n[2026-04-27 10:31:09] local.INFO: [EncryptedTokenManager] Generating access token. {\"mode\":\"legacy\"} {\"correlation_id\":\"73848601-79bb-4161-91ae-37acf4385d24\",\"trace_id\":\"dba432b8-cb4b-4e76-8684-01d96f91e453\"}\n[2026-04-27 10:31:09] local.INFO: [SyncActivity] Start {\"import_id\":811740,\"provider\":\"xant\",\"provider_id\":161,\"team\":\"jiminny\",\"team_id\":1} {\"correlation_id\":\"73848601-79bb-4161-91ae-37acf4385d24\",\"trace_id\":\"dba432b8-cb4b-4e76-8684-01d96f91e453\"}\n[2026-04-27 10:31:09] local.INFO: [Salesforce] Performing query {\"query\":\"\n SELECT Playbooks_Call_Date__c,Playbooks_Call_Recording__c,CreatedDate,TaskSubtype,CallType,CallDurationInSeconds,Id,OwnerId,WhoId,WhatId,Priority,ActivityDate,Subject,Description,Status,Type\n FROM Task\n WHERE IsDeleted = false\n AND LastModifiedDate >= :from\n AND LastModifiedDate <= :to\n ORDER BY LastModifiedDate ASC\n LIMIT :limit\",\"params\":{\"from\":\"2026-04-27T10:14:00Z\",\"to\":\"2026-04-27T10:30:00Z\",\"ownerId\":null,\"subType\":null,\"limit\":5000}} {\"correlation_id\":\"73848601-79bb-4161-91ae-37acf4385d24\",\"trace_id\":\"dba432b8-cb4b-4e76-8684-01d96f91e453\"}\n[2026-04-27 10:31:09] local.INFO: [Salesforce] Sending request {\"endpoint\":\"https://jiminny--stagingenv.sandbox.my.salesforce.com/services/data/v50.0/query/?q=%0A++++++++++++SELECT+Playbooks_Call_Date__c%2CPlaybooks_Call_Recording__c%2CCreatedDate%2CTaskSubtype%2CCallType%2CCallDurationInSeconds%2CId%2COwnerId%2CWhoId%2CWhatId%2CPriority%2CActivityDate%2CSubject%2CDescription%2CStatus%2CType%0A++++++++++++++FROM+Task%0A+++++++++++++WHERE+IsDeleted+%3D+false%0A+++++++++++++++AND+LastModifiedDate+%3E%3D+2026-04-27T10%3A14%3A00Z%0A+++++++++++++++AND+LastModifiedDate+%3C%3D+2026-04-27T10%3A30%3A00Z%0A++++++++++ORDER+BY+LastModifiedDate+ASC%0A+++++++++++++LIMIT+5000 GET\",\"team_id\":1} {\"correlation_id\":\"73848601-79bb-4161-91ae-37acf4385d24\",\"trace_id\":\"dba432b8-cb4b-4e76-8684-01d96f91e453\"}\n[2026-04-27 10:31:09] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage before starting command {\"command\":\"mailbox:batch:fail-stalled\",\"memoryBeforeCommandInMb\":60.0,\"memoryPeakBeforeCommandInMb\":99.727} {\"correlation_id\":\"0d30446d-c469-4ade-80dc-f57f997ea610\",\"trace_id\":\"61eea326-3f40-4a50-a5e6-bddd7fa25d27\"}\n[2026-04-27 10:31:09] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage for command {\"command\":\"mailbox:batch:fail-stalled\",\"memoryBeforeCommandInMb\":60.0,\"memoryAfterCommandInMB\":62.0,\"memoryPeakBeforeCommandInMb\":99.727,\"memoryPeakAfterCommandInMB\":99.727} {\"correlation_id\":\"0d30446d-c469-4ade-80dc-f57f997ea610\",\"trace_id\":\"61eea326-3f40-4a50-a5e6-bddd7fa25d27\"}\n[2026-04-27 10:31:09] local.INFO: [Xant (InsideSales)] No calls found. {\"correlation_id\":\"73848601-79bb-4161-91ae-37acf4385d24\",\"trace_id\":\"dba432b8-cb4b-4e76-8684-01d96f91e453\"}\n[2026-04-27 10:31:09] local.INFO: [SyncActivity] End {\"import_id\":811740,\"provider\":\"xant\",\"provider_id\":161,\"team\":\"jiminny\",\"team_id\":1} {\"correlation_id\":\"73848601-79bb-4161-91ae-37acf4385d24\",\"trace_id\":\"dba432b8-cb4b-4e76-8684-01d96f91e453\"}\n[2026-04-27 10:31:09] local.INFO: [SyncActivity] Memory usage {\"import_id\":811740,\"provider\":\"xant\",\"provider_id\":161,\"team\":\"jiminny\",\"team_id\":1,\"memory_usage\":25149472,\"memory_real_usage\":65011712,\"pid\":13616} {\"correlation_id\":\"73848601-79bb-4161-91ae-37acf4385d24\",\"trace_id\":\"dba432b8-cb4b-4e76-8684-01d96f91e453\"}\n[2026-04-27 10:31:09] local.INFO: [SocialAccountService] Fetching token {\"socialAccountId\":1500,\"provider\":\"salesforce\"} {\"correlation_id\":\"11c977fb-9603-4dde-a4d7-f821d47fcdc9\",\"trace_id\":\"dba432b8-cb4b-4e76-8684-01d96f91e453\"}\n[2026-04-27 10:31:09] local.INFO: [SocialAccountService] Token retrieved {\"socialAccountId\":1500,\"provider\":\"salesforce\"} {\"correlation_id\":\"11c977fb-9603-4dde-a4d7-f821d47fcdc9\",\"trace_id\":\"dba432b8-cb4b-4e76-8684-01d96f91e453\"}\n[2026-04-27 10:31:09] local.INFO: [EncryptedTokenManager] Generating access token. {\"mode\":\"legacy\"} {\"correlation_id\":\"11c977fb-9603-4dde-a4d7-f821d47fcdc9\",\"trace_id\":\"dba432b8-cb4b-4e76-8684-01d96f91e453\"}\n[2026-04-27 10:31:09] local.INFO: [CrmOwnerResolver] Integration owner matched as CRM Owner {\"crm_provider\":\"salesforce\",\"crm_owner\":143,\"team_id\":1} {\"correlation_id\":\"11c977fb-9603-4dde-a4d7-f821d47fcdc9\",\"trace_id\":\"dba432b8-cb4b-4e76-8684-01d96f91e453\"}\n[2026-04-27 10:31:09] local.INFO: [SocialAccountService] Fetching token {\"socialAccountId\":1500,\"provider\":\"salesforce\"} {\"correlation_id\":\"11c977fb-9603-4dde-a4d7-f821d47fcdc9\",\"trace_id\":\"dba432b8-cb4b-4e76-8684-01d96f91e453\"}\n[2026-04-27 10:31:09] local.INFO: [SocialAccountService] Token retrieved {\"socialAccountId\":1500,\"provider\":\"salesforce\"} {\"correlation_id\":\"11c977fb-9603-4dde-a4d7-f821d47fcdc9\",\"trace_id\":\"dba432b8-cb4b-4e76-8684-01d96f91e453\"}\n[2026-04-27 10:31:09] local.INFO: [EncryptedTokenManager] Generating access token. {\"mode\":\"legacy\"} {\"correlation_id\":\"11c977fb-9603-4dde-a4d7-f821d47fcdc9\",\"trace_id\":\"dba432b8-cb4b-4e76-8684-01d96f91e453\"}\n[2026-04-27 10:31:09] local.INFO: [SyncActivity] Start {\"import_id\":811741,\"provider\":\"apollo\",\"provider_id\":441,\"team\":\"jiminny\",\"team_id\":1} {\"correlation_id\":\"11c977fb-9603-4dde-a4d7-f821d47fcdc9\",\"trace_id\":\"dba432b8-cb4b-4e76-8684-01d96f91e453\"}\n[2026-04-27 10:31:09] local.INFO: [Salesforce] Performing query {\"query\":\"\n SELECT AccountId,CreatedDate,TaskSubtype,CallType,Id,OwnerId,WhoId,WhatId,Priority,ActivityDate,Subject,Description,Status,Type\n FROM Task\n WHERE IsDeleted = false\n AND LastModifiedDate >= :from\n AND LastModifiedDate <= :to\n ORDER BY LastModifiedDate ASC\n LIMIT :limit\",\"params\":{\"from\":\"2026-04-27T10:14:00Z\",\"to\":\"2026-04-27T10:30:00Z\",\"ownerId\":null,\"subType\":null,\"limit\":5000}} {\"correlation_id\":\"11c977fb-9603-4dde-a4d7-f821d47fcdc9\",\"trace_id\":\"dba432b8-cb4b-4e76-8684-01d96f91e453\"}\n[2026-04-27 10:31:09] local.INFO: [Salesforce] Sending request {\"endpoint\":\"https://jiminny--stagingenv.sandbox.my.salesforce.com/services/data/v50.0/query/?q=%0A++++++++++++SELECT+AccountId%2CCreatedDate%2CTaskSubtype%2CCallType%2CId%2COwnerId%2CWhoId%2CWhatId%2CPriority%2CActivityDate%2CSubject%2CDescription%2CStatus%2CType%0A++++++++++++++FROM+Task%0A+++++++++++++WHERE+IsDeleted+%3D+false%0A+++++++++++++++AND+LastModifiedDate+%3E%3D+2026-04-27T10%3A14%3A00Z%0A+++++++++++++++AND+LastModifiedDate+%3C%3D+2026-04-27T10%3A30%3A00Z%0A++++++++++ORDER+BY+LastModifiedDate+ASC%0A+++++++++++++LIMIT+5000 GET\",\"team_id\":1} {\"correlation_id\":\"11c977fb-9603-4dde-a4d7-f821d47fcdc9\",\"trace_id\":\"dba432b8-cb4b-4e76-8684-01d96f91e453\"}\n[2026-04-27 10:31:10] local.INFO: [Apollo] No calls found. {\"correlation_id\":\"11c977fb-9603-4dde-a4d7-f821d47fcdc9\",\"trace_id\":\"dba432b8-cb4b-4e76-8684-01d96f91e453\"}\n[2026-04-27 10:31:10] local.INFO: [SyncActivity] End {\"import_id\":811741,\"provider\":\"apollo\",\"provider_id\":441,\"team\":\"jiminny\",\"team_id\":1} {\"correlation_id\":\"11c977fb-9603-4dde-a4d7-f821d47fcdc9\",\"trace_id\":\"dba432b8-cb4b-4e76-8684-01d96f91e453\"}\n[2026-04-27 10:31:10] local.INFO: [SyncActivity] Memory usage {\"import_id\":811741,\"provider\":\"apollo\",\"provider_id\":441,\"team\":\"jiminny\",\"team_id\":1,\"memory_usage\":25527752,\"memory_real_usage\":65011712,\"pid\":13616} {\"correlation_id\":\"11c977fb-9603-4dde-a4d7-f821d47fcdc9\",\"trace_id\":\"dba432b8-cb4b-4e76-8684-01d96f91e453\"}\n[2026-04-27 10:31:10] local.INFO: [SocialAccountService] Fetching token {\"socialAccountId\":1500,\"provider\":\"salesforce\"} {\"correlation_id\":\"856f8092-8c5e-409d-8aa6-ffb8efd8bfa1\",\"trace_id\":\"dba432b8-cb4b-4e76-8684-01d96f91e453\"}\n[2026-04-27 10:31:10] local.INFO: [SocialAccountService] Token retrieved {\"socialAccountId\":1500,\"provider\":\"salesforce\"} {\"correlation_id\":\"856f8092-8c5e-409d-8aa6-ffb8efd8bfa1\",\"trace_id\":\"dba432b8-cb4b-4e76-8684-01d96f91e453\"}\n[2026-04-27 10:31:10] local.INFO: [EncryptedTokenManager] Generating access token. {\"mode\":\"legacy\"} {\"correlation_id\":\"856f8092-8c5e-409d-8aa6-ffb8efd8bfa1\",\"trace_id\":\"dba432b8-cb4b-4e76-8684-01d96f91e453\"}\n[2026-04-27 10:31:10] local.INFO: [CrmOwnerResolver] Integration owner matched as CRM Owner {\"crm_provider\":\"salesforce\",\"crm_owner\":143,\"team_id\":1} {\"correlation_id\":\"856f8092-8c5e-409d-8aa6-ffb8efd8bfa1\",\"trace_id\":\"dba432b8-cb4b-4e76-8684-01d96f91e453\"}\n[2026-04-27 10:31:10] local.INFO: [SocialAccountService] Fetching token {\"socialAccountId\":1500,\"provider\":\"salesforce\"} {\"correlation_id\":\"856f8092-8c5e-409d-8aa6-ffb8efd8bfa1\",\"trace_id\":\"dba432b8-cb4b-4e76-8684-01d96f91e453\"}\n[2026-04-27 10:31:10] local.INFO: [SocialAccountService] Token retrieved {\"socialAccountId\":1500,\"provider\":\"salesforce\"} {\"correlation_id\":\"856f8092-8c5e-409d-8aa6-ffb8efd8bfa1\",\"trace_id\":\"dba432b8-cb4b-4e76-8684-01d96f91e453\"}\n[2026-04-27 10:31:10] local.INFO: [EncryptedTokenManager] Generating access token. {\"mode\":\"legacy\"} {\"correlation_id\":\"856f8092-8c5e-409d-8aa6-ffb8efd8bfa1\",\"trace_id\":\"dba432b8-cb4b-4e76-8684-01d96f91e453\"}\n[2026-04-27 10:31:10] local.INFO: [SyncActivity] Start {\"import_id\":811742,\"provider\":\"groove\",\"provider_id\":228,\"team\":\"jiminny\",\"team_id\":1} {\"correlation_id\":\"856f8092-8c5e-409d-8aa6-ffb8efd8bfa1\",\"trace_id\":\"dba432b8-cb4b-4e76-8684-01d96f91e453\"}\n[2026-04-27 10:31:10] local.INFO: [Salesforce] Performing query {\"query\":\"\n SELECT call_recording_url__c,TaskSubtype,CreatedDate,CallType,CallDurationInSeconds,Id,OwnerId,WhoId,WhatId,Priority,ActivityDate,Subject,Description,Status,Type\n FROM Task\n WHERE IsDeleted = false\n AND LastModifiedDate >= :from\n AND LastModifiedDate <= :to\n ORDER BY LastModifiedDate ASC\n LIMIT :limit\",\"params\":{\"from\":\"2026-04-27T10:14:00Z\",\"to\":\"2026-04-27T10:30:00Z\",\"ownerId\":null,\"subType\":null,\"limit\":5000}} {\"correlation_id\":\"856f8092-8c5e-409d-8aa6-ffb8efd8bfa1\",\"trace_id\":\"dba432b8-cb4b-4e76-8684-01d96f91e453\"}\n[2026-04-27 10:31:10] local.INFO: [Salesforce] Sending request {\"endpoint\":\"https://jiminny--stagingenv.sandbox.my.salesforce.com/services/data/v50.0/query/?q=%0A++++++++++++SELECT+call_recording_url__c%2CTaskSubtype%2CCreatedDate%2CCallType%2CCallDurationInSeconds%2CId%2COwnerId%2CWhoId%2CWhatId%2CPriority%2CActivityDate%2CSubject%2CDescription%2CStatus%2CType%0A++++++++++++++FROM+Task%0A+++++++++++++WHERE+IsDeleted+%3D+false%0A+++++++++++++++AND+LastModifiedDate+%3E%3D+2026-04-27T10%3A14%3A00Z%0A+++++++++++++++AND+LastModifiedDate+%3C%3D+2026-04-27T10%3A30%3A00Z%0A++++++++++ORDER+BY+LastModifiedDate+ASC%0A+++++++++++++LIMIT+5000 GET\",\"team_id\":1} {\"correlation_id\":\"856f8092-8c5e-409d-8aa6-ffb8efd8bfa1\",\"trace_id\":\"dba432b8-cb4b-4e76-8684-01d96f91e453\"}\n[2026-04-27 10:31:10] local.ERROR: [Salesforce] Request exception [400] \nSELECT call_recording_url__c,TaskSubtype\n ^\nERROR at Row:1:Column:8\nNo such column 'call_recording_url__c' on entity 'Task'. If you are attempting to use a custom field, be sure to append the '__c' after the custom field name. Please reference your WSDL or the describe call for the appropriate names. {\"url\":\"https://jiminny--stagingenv.sandbox.my.salesforce.com/services/data/v50.0/query/?q=%0A++++++++++++SELECT+call_recording_url__c%2CTaskSubtype%2CCreatedDate%2CCallType%2CCallDurationInSeconds%2CId%2COwnerId%2CWhoId%2CWhatId%2CPriority%2CActivityDate%2CSubject%2CDescription%2CStatus%2CType%0A++++++++++++++FROM+Task%0A+++++++++++++WHERE+IsDeleted+%3D+false%0A+++++++++++++++AND+LastModifiedDate+%3E%3D+2026-04-27T10%3A14%3A00Z%0A+++++++++++++++AND+LastModifiedDate+%3C%3D+2026-04-27T10%3A30%3A00Z%0A++++++++++ORDER+BY+LastModifiedDate+ASC%0A+++++++++++++LIMIT+5000\",\"data\":{\"headers\":{\"Authorization\":\"Bearer 00D2g0000008hH4!AQEAQC41CLa_xP1bAbi9E2OUbAOpW..Wx961OtqTwJ3oWpOKs6yPdm47B4yKRBHbRmmJ.Lvd34sjr0gnAkV8fLb8IZWzIW93\"}},\"response\":{\"GuzzleHttp\\\\Psr7\\\\Stream\":\"[{\\\"message\\\":\\\"\\\\nSELECT call_recording_url__c,TaskSubtype\\\\n ^\\\\nERROR at Row:1:Column:8\\\\nNo such column 'call_recording_url__c' on entity 'Task'. If you are attempting to use a custom field, be sure to append the '__c' after the custom field name. Please reference your WSDL or the describe call for the appropriate names.\\\",\\\"errorCode\\\":\\\"INVALID_FIELD\\\"}]\"},\"fields\":[]} {\"correlation_id\":\"856f8092-8c5e-409d-8aa6-ffb8efd8bfa1\",\"trace_id\":\"dba432b8-cb4b-4e76-8684-01d96f91e453\"}\n[2026-04-27 10:31:10] local.ALERT: [SyncActivity] Failed {\"import_id\":811742,\"provider\":\"groove\",\"provider_id\":228,\"team\":\"jiminny\",\"team_id\":1,\"reason\":\"\nSELECT call_recording_url__c,TaskSubtype\n ^\nERROR at Row:1:Column:8\nNo such column 'call_recording_url__c' on entity 'Task'. If you are attempting to use a custom field, be sure to append the '__c' after the custom field name. Please reference your WSDL or the describe call for the appropriate names.\",\"file\":\"/home/jiminny/app/Services/Crm/Salesforce/Client.php\",\"line\":564} {\"correlation_id\":\"856f8092-8c5e-409d-8aa6-ffb8efd8bfa1\",\"trace_id\":\"dba432b8-cb4b-4e76-8684-01d96f91e453\"}\n[2026-04-27 10:31:10] local.INFO: [SocialAccountService] Fetching token {\"socialAccountId\":1500,\"provider\":\"salesforce\"} {\"correlation_id\":\"0ec6aef6-345c-4602-b6a1-646b95e481c0\",\"trace_id\":\"dba432b8-cb4b-4e76-8684-01d96f91e453\"}\n[2026-04-27 10:31:10] local.INFO: [SocialAccountService] Token retrieved {\"socialAccountId\":1500,\"provider\":\"salesforce\"} {\"correlation_id\":\"0ec6aef6-345c-4602-b6a1-646b95e481c0\",\"trace_id\":\"dba432b8-cb4b-4e76-8684-01d96f91e453\"}\n[2026-04-27 10:31:10] local.INFO: [EncryptedTokenManager] Generating access token. {\"mode\":\"legacy\"} {\"correlation_id\":\"0ec6aef6-345c-4602-b6a1-646b95e481c0\",\"trace_id\":\"dba432b8-cb4b-4e76-8684-01d96f91e453\"}\n[2026-04-27 10:31:10] local.INFO: [CrmOwnerResolver] Integration owner matched as CRM Owner {\"crm_provider\":\"salesforce\",\"crm_owner\":143,\"team_id\":1} {\"correlation_id\":\"0ec6aef6-345c-4602-b6a1-646b95e481c0\",\"trace_id\":\"dba432b8-cb4b-4e76-8684-01d96f91e453\"}\n[2026-04-27 10:31:10] local.INFO: [SocialAccountService] Fetching token {\"socialAccountId\":1500,\"provider\":\"salesforce\"} {\"correlation_id\":\"0ec6aef6-345c-4602-b6a1-646b95e481c0\",\"trace_id\":\"dba432b8-cb4b-4e76-8684-01d96f91e453\"}\n[2026-04-27 10:31:10] local.INFO: [SocialAccountService] Token retrieved {\"socialAccountId\":1500,\"provider\":\"salesforce\"} {\"correlation_id\":\"0ec6aef6-345c-4602-b6a1-646b95e481c0\",\"trace_id\":\"dba432b8-cb4b-4e76-8684-01d96f91e453\"}\n[2026-04-27 10:31:10] local.INFO: [EncryptedTokenManager] Generating access token. {\"mode\":\"legacy\"} {\"correlation_id\":\"0ec6aef6-345c-4602-b6a1-646b95e481c0\",\"trace_id\":\"dba432b8-cb4b-4e76-8684-01d96f91e453\"}\n[2026-04-27 10:31:10] local.INFO: [SyncActivity] Start {\"import_id\":811743,\"provider\":\"twilio-video\",\"provider_id\":243,\"team\":\"jiminny\",\"team_id\":1} {\"correlation_id\":\"0ec6aef6-345c-4602-b6a1-646b95e481c0\",\"trace_id\":\"dba432b8-cb4b-4e76-8684-01d96f91e453\"}\n[2026-04-27 10:31:10] local.INFO: [Salesforce] Performing query {\"query\":\"SELECT Id,OwnerId,WhoId,WhatId,Priority,ActivityDate,Subject,Description,Status,Type,twilio_call_sid__c,Lead_UUID__c,Opportunity__c\n FROM Task\n WHERE Type = 'Video'\n AND isClosed = true\n AND IsDeleted = false\n AND LastModifiedDate >= :from\n AND twilio_call_sid__c != NULL AND LastModifiedDate <= :to ORDER BY LastModifiedDate ASC\n LIMIT :limit\",\"params\":{\"from\":\"2026-04-27T10:14:00Z\",\"to\":\"2026-04-27T10:30:00Z\",\"ownerId\":null,\"subType\":null,\"limit\":5000}} {\"correlation_id\":\"0ec6aef6-345c-4602-b6a1-646b95e481c0\",\"trace_id\":\"dba432b8-cb4b-4e76-8684-01d96f91e453\"}\n[2026-04-27 10:31:10] local.INFO: [Salesforce] Sending request {\"endpoint\":\"https://jiminny--stagingenv.sandbox.my.salesforce.com/services/data/v50.0/query/?q=SELECT+Id%2COwnerId%2CWhoId%2CWhatId%2CPriority%2CActivityDate%2CSubject%2CDescription%2CStatus%2CType%2Ctwilio_call_sid__c%2CLead_UUID__c%2COpportunity__c%0A++++++++++++++FROM+Task%0A++++++++++++WHERE+Type+%3D+%27Video%27%0A++++++++++++++AND+isClosed+%3D+true%0A++++++++++++++AND+IsDeleted+%3D+false%0A++++++++++++++AND+LastModifiedDate+%3E%3D+2026-04-27T10%3A14%3A00Z%0A++++++++++++++AND+twilio_call_sid__c+%21%3D+NULL+AND+LastModifiedDate+%3C%3D+2026-04-27T10%3A30%3A00Z+ORDER+BY+LastModifiedDate+ASC%0A+++++++++++++LIMIT+5000 GET\",\"team_id\":1} {\"correlation_id\":\"0ec6aef6-345c-4602-b6a1-646b95e481c0\",\"trace_id\":\"dba432b8-cb4b-4e76-8684-01d96f91e453\"}\n[2026-04-27 10:31:11] local.INFO: [Twilio Video] No calls found. {\"correlation_id\":\"0ec6aef6-345c-4602-b6a1-646b95e481c0\",\"trace_id\":\"dba432b8-cb4b-4e76-8684-01d96f91e453\"}\n[2026-04-27 10:31:11] local.INFO: [SyncActivity] End {\"import_id\":811743,\"provider\":\"twilio-video\",\"provider_id\":243,\"team\":\"jiminny\",\"team_id\":1} {\"correlation_id\":\"0ec6aef6-345c-4602-b6a1-646b95e481c0\",\"trace_id\":\"dba432b8-cb4b-4e76-8684-01d96f91e453\"}\n[2026-04-27 10:31:11] local.INFO: [SyncActivity] Memory usage {\"import_id\":811743,\"provider\":\"twilio-video\",\"provider_id\":243,\"team\":\"jiminny\",\"team_id\":1,\"memory_usage\":25679680,\"memory_real_usage\":65011712,\"pid\":13616} {\"correlation_id\":\"0ec6aef6-345c-4602-b6a1-646b95e481c0\",\"trace_id\":\"dba432b8-cb4b-4e76-8684-01d96f91e453\"}\n[2026-04-27 10:31:11] local.INFO: [SocialAccountService] Fetching token {\"socialAccountId\":1499,\"provider\":\"hubspot\"} {\"correlation_id\":\"3b9d7e85-b8c2-4ae2-863e-c343c277afdc\",\"trace_id\":\"dba432b8-cb4b-4e76-8684-01d96f91e453\"}\n[2026-04-27 10:31:11] local.INFO: [SocialAccountService] Token retrieved {\"socialAccountId\":1499,\"provider\":\"hubspot\"} {\"correlation_id\":\"3b9d7e85-b8c2-4ae2-863e-c343c277afdc\",\"trace_id\":\"dba432b8-cb4b-4e76-8684-01d96f91e453\"}\n[2026-04-27 10:31:11] local.INFO: [EncryptedTokenManager] Generating access token. {\"mode\":\"legacy\"} {\"correlation_id\":\"3b9d7e85-b8c2-4ae2-863e-c343c277afdc\",\"trace_id\":\"dba432b8-cb4b-4e76-8684-01d96f91e453\"}\n[2026-04-27 10:31:11] local.INFO: [CrmOwnerResolver] Integration owner matched as CRM Owner {\"crm_provider\":\"hubspot\",\"crm_owner\":89,\"team_id\":2} {\"correlation_id\":\"3b9d7e85-b8c2-4ae2-863e-c343c277afdc\",\"trace_id\":\"dba432b8-cb4b-4e76-8684-01d96f91e453\"}\n[2026-04-27 10:31:11] local.INFO: [SocialAccountService] Fetching token {\"socialAccountId\":408,\"provider\":\"hubspot\"} {\"correlation_id\":\"3b9d7e85-b8c2-4ae2-863e-c343c277afdc\",\"trace_id\":\"dba432b8-cb4b-4e76-8684-01d96f91e453\"}\n[2026-04-27 10:31:11] local.INFO: [SocialAccountService] Token needs refreshing {\"socialAccountId\":408,\"provider\":\"hubspot\"} {\"correlation_id\":\"3b9d7e85-b8c2-4ae2-863e-c343c277afdc\",\"trace_id\":\"dba432b8-cb4b-4e76-8684-01d96f91e453\"}\n[2026-04-27 10:31:11] local.INFO: [EncryptedTokenManager] Generating access token. {\"mode\":\"legacy\"} {\"correlation_id\":\"3b9d7e85-b8c2-4ae2-863e-c343c277afdc\",\"trace_id\":\"dba432b8-cb4b-4e76-8684-01d96f91e453\"}\n[2026-04-27 10:31:11] local.INFO: [SocialAccountService] Refreshing token from provider {\"socialAccountId\":408,\"provider\":\"hubspot\",\"refreshToken\":\"de4e47eb985578f4218833e763e31059e88b562e87e10749b3389be2328f0aa7\",\"state\":\"connected\"} {\"correlation_id\":\"3b9d7e85-b8c2-4ae2-863e-c343c277afdc\",\"trace_id\":\"dba432b8-cb4b-4e76-8684-01d96f91e453\"}\n[2026-04-27 10:31:11] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage before starting command {\"command\":\"crm:bullhorn:ping\",\"memoryBeforeCommandInMb\":60.0,\"memoryPeakBeforeCommandInMb\":99.727} {\"correlation_id\":\"a2085168-092a-44b0-8dd2-c8845bc36f86\",\"trace_id\":\"9445425b-6aaf-4a23-9323-ecf85939f967\"}\n[2026-04-27 10:31:11] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage for command {\"command\":\"crm:bullhorn:ping\",\"memoryBeforeCommandInMb\":60.0,\"memoryAfterCommandInMB\":62.0,\"memoryPeakBeforeCommandInMb\":99.727,\"memoryPeakAfterCommandInMB\":99.727} {\"correlation_id\":\"a2085168-092a-44b0-8dd2-c8845bc36f86\",\"trace_id\":\"9445425b-6aaf-4a23-9323-ecf85939f967\"}\n[2026-04-27 10:31:12] local.INFO: [SocialAccountObserver] Saving model {\"correlation_id\":\"3b9d7e85-b8c2-4ae2-863e-c343c277afdc\",\"trace_id\":\"dba432b8-cb4b-4e76-8684-01d96f91e453\"}","depth":4,"bounds":{"left":0.52859044,"top":0.0726257,"width":0.45977393,"height":0.9273743},"on_screen":true,"value":"[2026-04-27 10:26:25] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage before starting command {\"command\":\"mailbox:sync\",\"memoryBeforeCommandInMb\":60.0,\"memoryPeakBeforeCommandInMb\":99.727} {\"correlation_id\":\"7b1ea2fb-8f36-4897-9970-91b4abba2cc7\",\"trace_id\":\"265be7c0-bf8f-4037-a0d2-57df3ef1d7e1\"}\n[2026-04-27 10:26:25] local.INFO: [EmailSchedule] STARTING Inbox Sync {\"host\":\"docker_lamp_1\"} {\"correlation_id\":\"7b1ea2fb-8f36-4897-9970-91b4abba2cc7\",\"trace_id\":\"265be7c0-bf8f-4037-a0d2-57df3ef1d7e1\"}\n[2026-04-27 10:26:25] local.INFO: [EmailSchedule] FINISHED Inbox Sync {\"host\":\"docker_lamp_1\",\"events\":2} {\"correlation_id\":\"7b1ea2fb-8f36-4897-9970-91b4abba2cc7\",\"trace_id\":\"265be7c0-bf8f-4037-a0d2-57df3ef1d7e1\"}\n[2026-04-27 10:26:25] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage for command {\"command\":\"mailbox:sync\",\"memoryBeforeCommandInMb\":60.0,\"memoryAfterCommandInMB\":62.0,\"memoryPeakBeforeCommandInMb\":99.727,\"memoryPeakAfterCommandInMB\":99.727} {\"correlation_id\":\"7b1ea2fb-8f36-4897-9970-91b4abba2cc7\",\"trace_id\":\"265be7c0-bf8f-4037-a0d2-57df3ef1d7e1\"}\n[2026-04-27 10:26:26] local.INFO: [automated-reports] Started {\"correlation_id\":\"d3f79034-397b-4936-9305-bdd99b23f2dd\",\"trace_id\":\"aa0f8702-73eb-4947-a765-2e89244599c3\"}\n[2026-04-27 10:26:26] local.INFO: [automated-reports] Checking conditions {\"isMonday\":true,\"isWeekend\":false,\"isFirstDayOfMonth\":false,\"currentMonth\":4,\"isQuarterlyMonth\":true} {\"correlation_id\":\"d3f79034-397b-4936-9305-bdd99b23f2dd\",\"trace_id\":\"aa0f8702-73eb-4947-a765-2e89244599c3\"}\n[2026-04-27 10:26:26] local.INFO: [automated-reports] Processing daily reports {\"correlation_id\":\"d3f79034-397b-4936-9305-bdd99b23f2dd\",\"trace_id\":\"aa0f8702-73eb-4947-a765-2e89244599c3\"}\n[2026-04-27 10:26:26] local.INFO: [automated-reports] Found 1 daily reports to process {\"correlation_id\":\"d3f79034-397b-4936-9305-bdd99b23f2dd\",\"trace_id\":\"aa0f8702-73eb-4947-a765-2e89244599c3\"}\n[2026-04-27 10:26:26] local.INFO: [automated-reports] Dispatching Generate Report job for report {\"reportUuid\":\"4f6ca2b5-1993-48aa-99ad-b66f19f15d43\",\"teamId\":1,\"frequency\":\"weekly\",\"type\":\"ask_jiminny\"} {\"correlation_id\":\"d3f79034-397b-4936-9305-bdd99b23f2dd\",\"trace_id\":\"aa0f8702-73eb-4947-a765-2e89244599c3\"}\n[2026-04-27 10:26:26] local.INFO: [automated-reports] Processing weekly reports {\"correlation_id\":\"d3f79034-397b-4936-9305-bdd99b23f2dd\",\"trace_id\":\"aa0f8702-73eb-4947-a765-2e89244599c3\"}\n[2026-04-27 10:26:26] local.INFO: [automated-reports] Found 1 weekly reports to process {\"correlation_id\":\"d3f79034-397b-4936-9305-bdd99b23f2dd\",\"trace_id\":\"aa0f8702-73eb-4947-a765-2e89244599c3\"}\n[2026-04-27 10:26:26] local.INFO: [automated-reports] Dispatching Generate Report job for report {\"reportUuid\":\"4f6ca2b5-1993-48aa-99ad-b66f19f15d43\",\"teamId\":1,\"frequency\":\"weekly\",\"type\":\"ask_jiminny\"} {\"correlation_id\":\"d3f79034-397b-4936-9305-bdd99b23f2dd\",\"trace_id\":\"aa0f8702-73eb-4947-a765-2e89244599c3\"}\n[2026-04-27 10:26:26] local.INFO: [automated-reports] Completed {\"correlation_id\":\"d3f79034-397b-4936-9305-bdd99b23f2dd\",\"trace_id\":\"aa0f8702-73eb-4947-a765-2e89244599c3\"}\n[2026-04-27 10:26:26] local.INFO: [HubSpot Journal Polling] Getting offset from database {\"offset\":\"\",\"jiminny_team_id\":1} {\"correlation_id\":\"b453f0f4-0d2c-491c-a935-14c60265ec18\",\"trace_id\":\"a50202d6-5601-46b4-a02e-cf5ac8d061fa\"}\n[2026-04-27 10:26:26] local.INFO: [HubSpot Journal API] Fetching latest journal entry {\"url\":\"https://api.hubapi.com/webhooks/v4/journal/latest\"} {\"correlation_id\":\"b453f0f4-0d2c-491c-a935-14c60265ec18\",\"trace_id\":\"a50202d6-5601-46b4-a02e-cf5ac8d061fa\"}\n[2026-04-27 10:26:26] local.INFO: [AskJiminnyReport:Generate] Started {\"automatedReportUuid\":\"4f6ca2b5-1993-48aa-99ad-b66f19f15d43\"} {\"correlation_id\":\"c6277ddd-09a6-4b04-8d42-bd395fc07d0a\",\"trace_id\":\"aa0f8702-73eb-4947-a765-2e89244599c3\"}\n[2026-04-27 10:26:26] local.INFO: [HubSpot Journal Polling] No data {\"correlation_id\":\"b453f0f4-0d2c-491c-a935-14c60265ec18\",\"trace_id\":\"a50202d6-5601-46b4-a02e-cf5ac8d061fa\"}\n[2026-04-27 10:26:26] local.WARNING: [HubSpot Journal Polling] Maximum empty results reached, stopping {\"empty_results\":5,\"max_empty_results\":5} {\"correlation_id\":\"b453f0f4-0d2c-491c-a935-14c60265ec18\",\"trace_id\":\"a50202d6-5601-46b4-a02e-cf5ac8d061fa\"}\n[2026-04-27 10:26:26] local.WARNING: [HubSpot Journal Polling] Maximum empty results reached, stopping {\"empty_results\":5,\"max_empty_results\":5} {\"correlation_id\":\"b453f0f4-0d2c-491c-a935-14c60265ec18\",\"trace_id\":\"a50202d6-5601-46b4-a02e-cf5ac8d061fa\"}\n[2026-04-27 10:26:26] local.INFO: [HubSpot Journal Polling] Service ending {\"runtime_seconds\":56,\"total_cycles\":5,\"files_downloaded\":0,\"empty_files\":0,\"other_portal_skipped\":0,\"total_events\":0,\"events_per_file\":0,\"avg_api_ms\":273.7,\"avg_download_ms\":0.0,\"avg_transform_ms\":0.0,\"avg_process_ms\":0.0,\"peak_memory_mb\":99.73} {\"correlation_id\":\"b453f0f4-0d2c-491c-a935-14c60265ec18\",\"trace_id\":\"a50202d6-5601-46b4-a02e-cf5ac8d061fa\"}\n[2026-04-27 10:26:26] local.INFO: [HubSpot Journal Polling] Released polling lock {\"correlation_id\":\"b453f0f4-0d2c-491c-a935-14c60265ec18\",\"trace_id\":\"a50202d6-5601-46b4-a02e-cf5ac8d061fa\"}\n[2026-04-27 10:26:27] local.INFO: [AskJiminnyReport] Fetched activity IDs for saved search {\"saved_search_id\":1977,\"user_id\":143,\"activity_count\":0} {\"correlation_id\":\"c6277ddd-09a6-4b04-8d42-bd395fc07d0a\",\"trace_id\":\"aa0f8702-73eb-4947-a765-2e89244599c3\"}\n[2026-04-27 10:26:27] local.INFO: [AskJiminnyReport:Generate] Fetched activity IDs {\"automatedReportUuid\":\"4f6ca2b5-1993-48aa-99ad-b66f19f15d43\",\"activityCount\":0} {\"correlation_id\":\"c6277ddd-09a6-4b04-8d42-bd395fc07d0a\",\"trace_id\":\"aa0f8702-73eb-4947-a765-2e89244599c3\"}\n[2026-04-27 10:26:27] local.INFO: [AskJiminnyReport:Generate] Not enough activities, skipped {\"automatedReportUuid\":\"4f6ca2b5-1993-48aa-99ad-b66f19f15d43\",\"activityCount\":0} {\"correlation_id\":\"c6277ddd-09a6-4b04-8d42-bd395fc07d0a\",\"trace_id\":\"aa0f8702-73eb-4947-a765-2e89244599c3\"}\n[2026-04-27 10:26:27] local.INFO: [AskJiminnyReport:Generate] Dispatched not-generated notifications {\"automatedReportUuid\":\"4f6ca2b5-1993-48aa-99ad-b66f19f15d43\",\"recipientsCount\":1} {\"correlation_id\":\"c6277ddd-09a6-4b04-8d42-bd395fc07d0a\",\"trace_id\":\"aa0f8702-73eb-4947-a765-2e89244599c3\"}\n[2026-04-27 10:26:27] local.INFO: [AskJiminnyReport:Generate] Started {\"automatedReportUuid\":\"4f6ca2b5-1993-48aa-99ad-b66f19f15d43\"} {\"correlation_id\":\"f1d3e4df-9591-487f-ac2a-a43ddb89a466\",\"trace_id\":\"aa0f8702-73eb-4947-a765-2e89244599c3\"}\n[2026-04-27 10:26:27] local.INFO: [AskJiminnyReport] Fetched activity IDs for saved search {\"saved_search_id\":1977,\"user_id\":143,\"activity_count\":0} {\"correlation_id\":\"f1d3e4df-9591-487f-ac2a-a43ddb89a466\",\"trace_id\":\"aa0f8702-73eb-4947-a765-2e89244599c3\"}\n[2026-04-27 10:26:27] local.INFO: [AskJiminnyReport:Generate] Fetched activity IDs {\"automatedReportUuid\":\"4f6ca2b5-1993-48aa-99ad-b66f19f15d43\",\"activityCount\":0} {\"correlation_id\":\"f1d3e4df-9591-487f-ac2a-a43ddb89a466\",\"trace_id\":\"aa0f8702-73eb-4947-a765-2e89244599c3\"}\n[2026-04-27 10:26:27] local.INFO: [AskJiminnyReport:Generate] Not enough activities, skipped {\"automatedReportUuid\":\"4f6ca2b5-1993-48aa-99ad-b66f19f15d43\",\"activityCount\":0} {\"correlation_id\":\"f1d3e4df-9591-487f-ac2a-a43ddb89a466\",\"trace_id\":\"aa0f8702-73eb-4947-a765-2e89244599c3\"}\n[2026-04-27 10:26:27] local.INFO: [AskJiminnyReport:Generate] Dispatched not-generated notifications {\"automatedReportUuid\":\"4f6ca2b5-1993-48aa-99ad-b66f19f15d43\",\"recipientsCount\":1} {\"correlation_id\":\"f1d3e4df-9591-487f-ac2a-a43ddb89a466\",\"trace_id\":\"aa0f8702-73eb-4947-a765-2e89244599c3\"}\n[2026-04-27 10:26:27] local.INFO: [Send Report Not Generated Mail] Email sent {\"uuid\":\"cd5b5081-22f8-4d1a-9d6f-f82aca1121f6\",\"email\":\"lukas.kovalik@jiminny.com\"} {\"correlation_id\":\"4255983f-07a4-4872-9ea6-5491fa32bdd5\",\"trace_id\":\"aa0f8702-73eb-4947-a765-2e89244599c3\"}\n[2026-04-27 10:26:28] local.INFO: [Sync Mailbox] Sync start {\"inbox_id\":59} {\"correlation_id\":\"280d5bf6-c34c-45ea-bea7-05d9c028e9d3\",\"trace_id\":\"265be7c0-bf8f-4037-a0d2-57df3ef1d7e1\"}\n[2026-04-27 10:26:28] local.INFO: [Inbox service] Skipping METADATA SYNC for inbox 59 due to unauthorized access to the mailbox {\"correlation_id\":\"280d5bf6-c34c-45ea-bea7-05d9c028e9d3\",\"trace_id\":\"265be7c0-bf8f-4037-a0d2-57df3ef1d7e1\"}\n[2026-04-27 10:26:28] local.INFO: [Sync Mailbox] Sync complete {\"inbox_id\":59} {\"correlation_id\":\"280d5bf6-c34c-45ea-bea7-05d9c028e9d3\",\"trace_id\":\"265be7c0-bf8f-4037-a0d2-57df3ef1d7e1\"}\n[2026-04-27 10:26:28] local.INFO: [Sync Mailbox] Sync start {\"inbox_id\":212} {\"correlation_id\":\"d3e3eb3d-5c83-4d6e-9592-aaa38c1843a4\",\"trace_id\":\"265be7c0-bf8f-4037-a0d2-57df3ef1d7e1\"}\n[2026-04-27 10:26:28] local.INFO: [SocialAccountService] Fetching token {\"socialAccountId\":1500,\"provider\":\"salesforce\"} {\"correlation_id\":\"d3e3eb3d-5c83-4d6e-9592-aaa38c1843a4\",\"trace_id\":\"265be7c0-bf8f-4037-a0d2-57df3ef1d7e1\"}\n[2026-04-27 10:26:28] local.INFO: [SocialAccountService] Token retrieved {\"socialAccountId\":1500,\"provider\":\"salesforce\"} {\"correlation_id\":\"d3e3eb3d-5c83-4d6e-9592-aaa38c1843a4\",\"trace_id\":\"265be7c0-bf8f-4037-a0d2-57df3ef1d7e1\"}\n[2026-04-27 10:26:28] local.INFO: [EncryptedTokenManager] Generating access token. {\"mode\":\"legacy\"} {\"correlation_id\":\"d3e3eb3d-5c83-4d6e-9592-aaa38c1843a4\",\"trace_id\":\"265be7c0-bf8f-4037-a0d2-57df3ef1d7e1\"}\n[2026-04-27 10:26:28] local.INFO: [CrmOwnerResolver] Integration owner matched as CRM Owner {\"crm_provider\":\"salesforce\",\"crm_owner\":143,\"team_id\":1} {\"correlation_id\":\"d3e3eb3d-5c83-4d6e-9592-aaa38c1843a4\",\"trace_id\":\"265be7c0-bf8f-4037-a0d2-57df3ef1d7e1\"}\n[2026-04-27 10:26:28] local.INFO: [SocialAccountService] Fetching token {\"socialAccountId\":1354,\"provider\":\"google\"} {\"correlation_id\":\"d3e3eb3d-5c83-4d6e-9592-aaa38c1843a4\",\"trace_id\":\"265be7c0-bf8f-4037-a0d2-57df3ef1d7e1\"}\n[2026-04-27 10:26:28] local.INFO: [SocialAccountService] Token retrieved {\"socialAccountId\":1354,\"provider\":\"google\"} {\"correlation_id\":\"d3e3eb3d-5c83-4d6e-9592-aaa38c1843a4\",\"trace_id\":\"265be7c0-bf8f-4037-a0d2-57df3ef1d7e1\"}\n[2026-04-27 10:26:28] local.INFO: [EncryptedTokenManager] Generating access token. {\"mode\":\"legacy\"} {\"correlation_id\":\"d3e3eb3d-5c83-4d6e-9592-aaa38c1843a4\",\"trace_id\":\"265be7c0-bf8f-4037-a0d2-57df3ef1d7e1\"}\n[2026-04-27 10:26:28] local.INFO: [Gmail] Performing incremental sync for inbox 212 using history ID: @1777284288 {\"correlation_id\":\"d3e3eb3d-5c83-4d6e-9592-aaa38c1843a4\",\"trace_id\":\"265be7c0-bf8f-4037-a0d2-57df3ef1d7e1\"}\n[2026-04-27 10:26:28] local.INFO: [Gmail] imported 7 emails via full sync workflow for inbox 212 {\"correlation_id\":\"d3e3eb3d-5c83-4d6e-9592-aaa38c1843a4\",\"trace_id\":\"265be7c0-bf8f-4037-a0d2-57df3ef1d7e1\"}\n[2026-04-27 10:26:28] local.INFO: [Gmail] seeding inbox 212 with last message time : 2026-04-27 10:24:57 {\"correlation_id\":\"d3e3eb3d-5c83-4d6e-9592-aaa38c1843a4\",\"trace_id\":\"265be7c0-bf8f-4037-a0d2-57df3ef1d7e1\"}\n[2026-04-27 10:26:28] local.INFO: [Sync Mailbox] Sync complete {\"inbox_id\":212} {\"correlation_id\":\"d3e3eb3d-5c83-4d6e-9592-aaa38c1843a4\",\"trace_id\":\"265be7c0-bf8f-4037-a0d2-57df3ef1d7e1\"}\n[2026-04-27 10:27:04] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage before starting command {\"command\":\"meeting-bot:schedule-bot\",\"memoryBeforeCommandInMb\":60.0,\"memoryPeakBeforeCommandInMb\":99.727} {\"correlation_id\":\"f55fbf77-7e5f-4c87-accd-011697ca0fa3\",\"trace_id\":\"9bd1c431-26de-4e31-83d8-3129ed9b3b79\"}\n[2026-04-27 10:27:04] local.INFO: [ScheduleBotCommand] Number of activities to be captured: 0 {\"correlation_id\":\"f55fbf77-7e5f-4c87-accd-011697ca0fa3\",\"trace_id\":\"9bd1c431-26de-4e31-83d8-3129ed9b3b79\"}\n[2026-04-27 10:27:04] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage for command {\"command\":\"meeting-bot:schedule-bot\",\"memoryBeforeCommandInMb\":60.0,\"memoryAfterCommandInMB\":62.0,\"memoryPeakBeforeCommandInMb\":99.727,\"memoryPeakAfterCommandInMB\":99.727} {\"correlation_id\":\"f55fbf77-7e5f-4c87-accd-011697ca0fa3\",\"trace_id\":\"9bd1c431-26de-4e31-83d8-3129ed9b3b79\"}\n[2026-04-27 10:27:05] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage before starting command {\"command\":\"dialers:monitor-activities\",\"memoryBeforeCommandInMb\":60.0,\"memoryPeakBeforeCommandInMb\":99.727} {\"correlation_id\":\"da241053-5f3b-4d02-9504-4ef2987cdbd7\",\"trace_id\":\"364d3fc7-0f11-40c6-8697-d18eff2c4cb6\"}\n[2026-04-27 10:27:05] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage for command {\"command\":\"dialers:monitor-activities\",\"memoryBeforeCommandInMb\":60.0,\"memoryAfterCommandInMB\":62.0,\"memoryPeakBeforeCommandInMb\":99.727,\"memoryPeakAfterCommandInMB\":99.727} {\"correlation_id\":\"da241053-5f3b-4d02-9504-4ef2987cdbd7\",\"trace_id\":\"364d3fc7-0f11-40c6-8697-d18eff2c4cb6\"}\n[2026-04-27 10:27:06] local.NOTICE: Monitoring start {\"correlation_id\":\"ad0d871a-a1fd-4725-b2f7-ac8f0fc1857e\",\"trace_id\":\"cb504e84-d924-43a0-82d5-faf1840134ba\"}\n[2026-04-27 10:27:06] local.NOTICE: Monitoring end {\"correlation_id\":\"ad0d871a-a1fd-4725-b2f7-ac8f0fc1857e\",\"trace_id\":\"cb504e84-d924-43a0-82d5-faf1840134ba\"}\n[2026-04-27 10:27:08] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage before starting command {\"command\":\"mailbox:skip-lists:refresh\",\"memoryBeforeCommandInMb\":60.0,\"memoryPeakBeforeCommandInMb\":99.727} {\"correlation_id\":\"b8652f0d-1cee-44ae-a0ea-3d3137a882c1\",\"trace_id\":\"7fe19940-0813-4199-a2a8-958de09bd0ee\"}\n[2026-04-27 10:27:08] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage for command {\"command\":\"mailbox:skip-lists:refresh\",\"memoryBeforeCommandInMb\":60.0,\"memoryAfterCommandInMB\":62.0,\"memoryPeakBeforeCommandInMb\":99.727,\"memoryPeakAfterCommandInMB\":99.727} {\"correlation_id\":\"b8652f0d-1cee-44ae-a0ea-3d3137a882c1\",\"trace_id\":\"7fe19940-0813-4199-a2a8-958de09bd0ee\"}\n[2026-04-27 10:27:09] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage before starting command {\"command\":\"mailbox:batch:process\",\"memoryBeforeCommandInMb\":60.0,\"memoryPeakBeforeCommandInMb\":99.727} {\"correlation_id\":\"06190462-0f47-403f-8e95-ad5d05bff1fe\",\"trace_id\":\"4aa6bed6-05a1-4661-a9c9-edadc61d84ac\"}\n[2026-04-27 10:27:09] local.INFO: [EmailSchedule] STARTING batch process {\"host\":\"docker_lamp_1\"} {\"correlation_id\":\"06190462-0f47-403f-8e95-ad5d05bff1fe\",\"trace_id\":\"4aa6bed6-05a1-4661-a9c9-edadc61d84ac\"}\n[2026-04-27 10:27:09] local.INFO: [EmailSchedule] FINISHED batch process {\"host\":\"docker_lamp_1\",\"processed\":0} {\"correlation_id\":\"06190462-0f47-403f-8e95-ad5d05bff1fe\",\"trace_id\":\"4aa6bed6-05a1-4661-a9c9-edadc61d84ac\"}\n[2026-04-27 10:27:09] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage for command {\"command\":\"mailbox:batch:process\",\"memoryBeforeCommandInMb\":60.0,\"memoryAfterCommandInMB\":62.0,\"memoryPeakBeforeCommandInMb\":99.727,\"memoryPeakAfterCommandInMB\":99.727} {\"correlation_id\":\"06190462-0f47-403f-8e95-ad5d05bff1fe\",\"trace_id\":\"4aa6bed6-05a1-4661-a9c9-edadc61d84ac\"}\n[2026-04-27 10:27:11] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage before starting command {\"command\":\"mailbox:batch:create\",\"memoryBeforeCommandInMb\":60.0,\"memoryPeakBeforeCommandInMb\":99.727} {\"correlation_id\":\"b561c5d5-e6e3-4091-9517-b0458190a360\",\"trace_id\":\"bf9679fb-b785-40cb-b814-04a1dc385a1c\"}\n[2026-04-27 10:27:11] local.INFO: [EmailSchedule] STARTING batch create {\"host\":\"docker_lamp_1\"} {\"correlation_id\":\"b561c5d5-e6e3-4091-9517-b0458190a360\",\"trace_id\":\"bf9679fb-b785-40cb-b814-04a1dc385a1c\"}\n[2026-04-27 10:27:11] local.INFO: [EmailSchedule] FINISHED batch create {\"host\":\"docker_lamp_1\"} {\"correlation_id\":\"b561c5d5-e6e3-4091-9517-b0458190a360\",\"trace_id\":\"bf9679fb-b785-40cb-b814-04a1dc385a1c\"}\n[2026-04-27 10:27:11] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage for command {\"command\":\"mailbox:batch:create\",\"memoryBeforeCommandInMb\":60.0,\"memoryAfterCommandInMB\":62.0,\"memoryPeakBeforeCommandInMb\":99.727,\"memoryPeakAfterCommandInMB\":99.727} {\"correlation_id\":\"b561c5d5-e6e3-4091-9517-b0458190a360\",\"trace_id\":\"bf9679fb-b785-40cb-b814-04a1dc385a1c\"}\n[2026-04-27 10:27:14] local.INFO: [Jiminny\\Jobs\\Mailbox\\CreateBatches] processed 2 inboxes and created 1 batches {\"userId\":null,\"batchSize\":30,\"maxBatches\":1000} {\"correlation_id\":\"98416296-43c2-4834-927e-4a0e86dc99cf\",\"trace_id\":\"bf9679fb-b785-40cb-b814-04a1dc385a1c\"}\n[2026-04-27 10:28:04] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage before starting command {\"command\":\"meeting-bot:schedule-bot\",\"memoryBeforeCommandInMb\":60.0,\"memoryPeakBeforeCommandInMb\":99.727} {\"correlation_id\":\"5852bcf1-af92-4d24-8a2d-08a9d4a5cfac\",\"trace_id\":\"25af5e8c-a148-493e-ac9f-4d53f1960177\"}\n[2026-04-27 10:28:04] local.INFO: [ScheduleBotCommand] Number of activities to be captured: 0 {\"correlation_id\":\"5852bcf1-af92-4d24-8a2d-08a9d4a5cfac\",\"trace_id\":\"25af5e8c-a148-493e-ac9f-4d53f1960177\"}\n[2026-04-27 10:28:04] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage for command {\"command\":\"meeting-bot:schedule-bot\",\"memoryBeforeCommandInMb\":60.0,\"memoryAfterCommandInMB\":62.0,\"memoryPeakBeforeCommandInMb\":99.727,\"memoryPeakAfterCommandInMB\":99.727} {\"correlation_id\":\"5852bcf1-af92-4d24-8a2d-08a9d4a5cfac\",\"trace_id\":\"25af5e8c-a148-493e-ac9f-4d53f1960177\"}\n[2026-04-27 10:28:06] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage before starting command {\"command\":\"dialers:monitor-activities\",\"memoryBeforeCommandInMb\":60.0,\"memoryPeakBeforeCommandInMb\":99.727} {\"correlation_id\":\"cc1cff1f-71b4-4bb1-800f-66e27c0f2b3a\",\"trace_id\":\"c03103a3-9ab8-480b-9753-7c7c6488f246\"}\n[2026-04-27 10:28:06] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage for command {\"command\":\"dialers:monitor-activities\",\"memoryBeforeCommandInMb\":60.0,\"memoryAfterCommandInMB\":62.0,\"memoryPeakBeforeCommandInMb\":99.727,\"memoryPeakAfterCommandInMB\":99.727} {\"correlation_id\":\"cc1cff1f-71b4-4bb1-800f-66e27c0f2b3a\",\"trace_id\":\"c03103a3-9ab8-480b-9753-7c7c6488f246\"}\n[2026-04-27 10:28:09] local.ERROR: Target class [Jiminny\\Repositories\\AjReportsRepository] does not exist. {\"exception\":\"[object] (Illuminate\\\\Contracts\\\\Container\\\\BindingResolutionException(code: 0): Target class [Jiminny\\\\Repositories\\\\AjReportsRepository] does not exist. at /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php:1124)\n[stacktrace]\n#0 /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php(933): Illuminate\\\\Container\\\\Container->build('Jiminny\\\\\\\\Reposit...')\n#1 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Application.php(1078): Illuminate\\\\Container\\\\Container->resolve('Jiminny\\\\\\\\Reposit...', Array, true)\n#2 /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php(864): Illuminate\\\\Foundation\\\\Application->resolve('Jiminny\\\\\\\\Reposit...', Array)\n#3 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Application.php(1058): Illuminate\\\\Container\\\\Container->make('Jiminny\\\\\\\\Reposit...', Array)\n#4 /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php(1336): Illuminate\\\\Foundation\\\\Application->make('Jiminny\\\\\\\\Reposit...')\n#5 /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php(1237): Illuminate\\\\Container\\\\Container->resolveClass(Object(ReflectionParameter))\n#6 /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php(1162): Illuminate\\\\Container\\\\Container->resolveDependencies(Array)\n#7 /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php(933): Illuminate\\\\Container\\\\Container->build('Jiminny\\\\\\\\Http\\\\\\\\Co...')\n#8 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Application.php(1078): Illuminate\\\\Container\\\\Container->resolve('Jiminny\\\\\\\\Http\\\\\\\\Co...', Array, true)\n#9 /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php(864): Illuminate\\\\Foundation\\\\Application->resolve('Jiminny\\\\\\\\Http\\\\\\\\Co...', Array)\n#10 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Application.php(1058): Illuminate\\\\Container\\\\Container->make('Jiminny\\\\\\\\Http\\\\\\\\Co...', Array)\n#11 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Route.php(286): Illuminate\\\\Foundation\\\\Application->make('Jiminny\\\\\\\\Http\\\\\\\\Co...')\n#12 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Route.php(1133): Illuminate\\\\Routing\\\\Route->getController()\n#13 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Route.php(1062): Illuminate\\\\Routing\\\\Route->controllerMiddleware()\n#14 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Router.php(834): Illuminate\\\\Routing\\\\Route->gatherMiddleware()\n#15 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Router.php(816): Illuminate\\\\Routing\\\\Router->gatherRouteMiddleware(Object(Illuminate\\\\Routing\\\\Route))\n#16 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Router.php(800): Illuminate\\\\Routing\\\\Router->runRouteWithinStack(Object(Illuminate\\\\Routing\\\\Route), Object(Illuminate\\\\Http\\\\Request))\n#17 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Router.php(764): Illuminate\\\\Routing\\\\Router->runRoute(Object(Illuminate\\\\Http\\\\Request), Object(Illuminate\\\\Routing\\\\Route))\n#18 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Router.php(753): Illuminate\\\\Routing\\\\Router->dispatchToRoute(Object(Illuminate\\\\Http\\\\Request))\n#19 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Http/Kernel.php(200): Illuminate\\\\Routing\\\\Router->dispatch(Object(Illuminate\\\\Http\\\\Request))\n#20 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(180): Illuminate\\\\Foundation\\\\Http\\\\Kernel->Illuminate\\\\Foundation\\\\Http\\\\{closure}(Object(Illuminate\\\\Http\\\\Request))\n#21 /home/jiminny/vendor/barryvdh/laravel-debugbar/src/Middleware/InjectDebugbar.php(59): Illuminate\\\\Pipeline\\\\Pipeline->Illuminate\\\\Pipeline\\\\{closure}(Object(Illuminate\\\\Http\\\\Request))\n#22 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(219): Barryvdh\\\\Debugbar\\\\Middleware\\\\InjectDebugbar->handle(Object(Illuminate\\\\Http\\\\Request), Object(Closure))\n#23 /home/jiminny/vendor/laravel/framework/src/Illuminate/Http/Middleware/HandleCors.php(74): Illuminate\\\\Pipeline\\\\Pipeline->Illuminate\\\\Pipeline\\\\{closure}(Object(Illuminate\\\\Http\\\\Request))\n#24 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(219): Illuminate\\\\Http\\\\Middleware\\\\HandleCors->handle(Object(Illuminate\\\\Http\\\\Request), Object(Closure))\n#25 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Http/Middleware/TransformsRequest.php(21): Illuminate\\\\Pipeline\\\\Pipeline->Illuminate\\\\Pipeline\\\\{closure}(Object(Illuminate\\\\Http\\\\Request))\n#26 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Http/Middleware/TrimStrings.php(51): Illuminate\\\\Foundation\\\\Http\\\\Middleware\\\\TransformsRequest->handle(Object(Illuminate\\\\Http\\\\Request), Object(Closure))\n#27 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(219): Illuminate\\\\Foundation\\\\Http\\\\Middleware\\\\TrimStrings->handle(Object(Illuminate\\\\Http\\\\Request), Object(Closure))\n#28 /home/jiminny/vendor/bepsvpt/secure-headers/src/SecureHeadersMiddleware.php(18): Illuminate\\\\Pipeline\\\\Pipeline->Illuminate\\\\Pipeline\\\\{closure}(Object(Illuminate\\\\Http\\\\Request))\n#29 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(219): Bepsvpt\\\\SecureHeaders\\\\SecureHeadersMiddleware->handle(Object(Illuminate\\\\Http\\\\Request), Object(Closure))\n#30 /home/jiminny/app/Http/Middleware/SentryContext.php(60): Illuminate\\\\Pipeline\\\\Pipeline->Illuminate\\\\Pipeline\\\\{closure}(Object(Illuminate\\\\Http\\\\Request))\n#31 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(219): Jiminny\\\\Http\\\\Middleware\\\\SentryContext->handle(Object(Illuminate\\\\Http\\\\Request), Object(Closure))\n#32 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Http/Middleware/PreventRequestsDuringMaintenance.php(109): Illuminate\\\\Pipeline\\\\Pipeline->Illuminate\\\\Pipeline\\\\{closure}(Object(Illuminate\\\\Http\\\\Request))\n#33 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(219): Illuminate\\\\Foundation\\\\Http\\\\Middleware\\\\PreventRequestsDuringMaintenance->handle(Object(Illuminate\\\\Http\\\\Request), Object(Closure))\n#34 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Http/Middleware/InvokeDeferredCallbacks.php(22): Illuminate\\\\Pipeline\\\\Pipeline->Illuminate\\\\Pipeline\\\\{closure}(Object(Illuminate\\\\Http\\\\Request))\n#35 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(219): Illuminate\\\\Foundation\\\\Http\\\\Middleware\\\\InvokeDeferredCallbacks->handle(Object(Illuminate\\\\Http\\\\Request), Object(Closure))\n#36 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(137): Illuminate\\\\Pipeline\\\\Pipeline->Illuminate\\\\Pipeline\\\\{closure}(Object(Illuminate\\\\Http\\\\Request))\n#37 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Http/Kernel.php(175): Illuminate\\\\Pipeline\\\\Pipeline->then(Object(Closure))\n#38 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Http/Kernel.php(144): Illuminate\\\\Foundation\\\\Http\\\\Kernel->sendRequestThroughRouter(Object(Illuminate\\\\Http\\\\Request))\n#39 /home/jiminny/public/index.php(51): Illuminate\\\\Foundation\\\\Http\\\\Kernel->handle(Object(Illuminate\\\\Http\\\\Request))\n#40 {main}\n\n[previous exception] [object] (ReflectionException(code: -1): Class \\\"Jiminny\\\\Repositories\\\\AjReportsRepository\\\" does not exist at /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php:1122)\n[stacktrace]\n#0 /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php(1122): ReflectionClass->__construct('Jiminny\\\\\\\\Reposit...')\n#1 /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php(933): Illuminate\\\\Container\\\\Container->build('Jiminny\\\\\\\\Reposit...')\n#2 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Application.php(1078): Illuminate\\\\Container\\\\Container->resolve('Jiminny\\\\\\\\Reposit...', Array, true)\n#3 /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php(864): Illuminate\\\\Foundation\\\\Application->resolve('Jiminny\\\\\\\\Reposit...', Array)\n#4 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Application.php(1058): Illuminate\\\\Container\\\\Container->make('Jiminny\\\\\\\\Reposit...', Array)\n#5 /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php(1336): Illuminate\\\\Foundation\\\\Application->make('Jiminny\\\\\\\\Reposit...')\n#6 /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php(1237): Illuminate\\\\Container\\\\Container->resolveClass(Object(ReflectionParameter))\n#7 /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php(1162): Illuminate\\\\Container\\\\Container->resolveDependencies(Array)\n#8 /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php(933): Illuminate\\\\Container\\\\Container->build('Jiminny\\\\\\\\Http\\\\\\\\Co...')\n#9 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Application.php(1078): Illuminate\\\\Container\\\\Container->resolve('Jiminny\\\\\\\\Http\\\\\\\\Co...', Array, true)\n#10 /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php(864): Illuminate\\\\Foundation\\\\Application->resolve('Jiminny\\\\\\\\Http\\\\\\\\Co...', Array)\n#11 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Application.php(1058): Illuminate\\\\Container\\\\Container->make('Jiminny\\\\\\\\Http\\\\\\\\Co...', Array)\n#12 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Route.php(286): Illuminate\\\\Foundation\\\\Application->make('Jiminny\\\\\\\\Http\\\\\\\\Co...')\n#13 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Route.php(1133): Illuminate\\\\Routing\\\\Route->getController()\n#14 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Route.php(1062): Illuminate\\\\Routing\\\\Route->controllerMiddleware()\n#15 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Router.php(834): Illuminate\\\\Routing\\\\Route->gatherMiddleware()\n#16 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Router.php(816): Illuminate\\\\Routing\\\\Router->gatherRouteMiddleware(Object(Illuminate\\\\Routing\\\\Route))\n#17 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Router.php(800): Illuminate\\\\Routing\\\\Router->runRouteWithinStack(Object(Illuminate\\\\Routing\\\\Route), Object(Illuminate\\\\Http\\\\Request))\n#18 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Router.php(764): Illuminate\\\\Routing\\\\Router->runRoute(Object(Illuminate\\\\Http\\\\Request), Object(Illuminate\\\\Routing\\\\Route))\n#19 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Router.php(753): Illuminate\\\\Routing\\\\Router->dispatchToRoute(Object(Illuminate\\\\Http\\\\Request))\n#20 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Http/Kernel.php(200): Illuminate\\\\Routing\\\\Router->dispatch(Object(Illuminate\\\\Http\\\\Request))\n#21 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(180): Illuminate\\\\Foundation\\\\Http\\\\Kernel->Illuminate\\\\Foundation\\\\Http\\\\{closure}(Object(Illuminate\\\\Http\\\\Request))\n#22 /home/jiminny/vendor/barryvdh/laravel-debugbar/src/Middleware/InjectDebugbar.php(59): Illuminate\\\\Pipeline\\\\Pipeline->Illuminate\\\\Pipeline\\\\{closure}(Object(Illuminate\\\\Http\\\\Request))\n#23 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(219): Barryvdh\\\\Debugbar\\\\Middleware\\\\InjectDebugbar->handle(Object(Illuminate\\\\Http\\\\Request), Object(Closure))\n#24 /home/jiminny/vendor/laravel/framework/src/Illuminate/Http/Middleware/HandleCors.php(74): Illuminate\\\\Pipeline\\\\Pipeline->Illuminate\\\\Pipeline\\\\{closure}(Object(Illuminate\\\\Http\\\\Request))\n#25 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(219): Illuminate\\\\Http\\\\Middleware\\\\HandleCors->handle(Object(Illuminate\\\\Http\\\\Request), Object(Closure))\n#26 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Http/Middleware/TransformsRequest.php(21): Illuminate\\\\Pipeline\\\\Pipeline->Illuminate\\\\Pipeline\\\\{closure}(Object(Illuminate\\\\Http\\\\Request))\n#27 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Http/Middleware/TrimStrings.php(51): Illuminate\\\\Foundation\\\\Http\\\\Middleware\\\\TransformsRequest->handle(Object(Illuminate\\\\Http\\\\Request), Object(Closure))\n#28 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(219): Illuminate\\\\Foundation\\\\Http\\\\Middleware\\\\TrimStrings->handle(Object(Illuminate\\\\Http\\\\Request), Object(Closure))\n#29 /home/jiminny/vendor/bepsvpt/secure-headers/src/SecureHeadersMiddleware.php(18): Illuminate\\\\Pipeline\\\\Pipeline->Illuminate\\\\Pipeline\\\\{closure}(Object(Illuminate\\\\Http\\\\Request))\n#30 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(219): Bepsvpt\\\\SecureHeaders\\\\SecureHeadersMiddleware->handle(Object(Illuminate\\\\Http\\\\Request), Object(Closure))\n#31 /home/jiminny/app/Http/Middleware/SentryContext.php(60): Illuminate\\\\Pipeline\\\\Pipeline->Illuminate\\\\Pipeline\\\\{closure}(Object(Illuminate\\\\Http\\\\Request))\n#32 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(219): Jiminny\\\\Http\\\\Middleware\\\\SentryContext->handle(Object(Illuminate\\\\Http\\\\Request), Object(Closure))\n#33 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Http/Middleware/PreventRequestsDuringMaintenance.php(109): Illuminate\\\\Pipeline\\\\Pipeline->Illuminate\\\\Pipeline\\\\{closure}(Object(Illuminate\\\\Http\\\\Request))\n#34 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(219): Illuminate\\\\Foundation\\\\Http\\\\Middleware\\\\PreventRequestsDuringMaintenance->handle(Object(Illuminate\\\\Http\\\\Request), Object(Closure))\n#35 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Http/Middleware/InvokeDeferredCallbacks.php(22): Illuminate\\\\Pipeline\\\\Pipeline->Illuminate\\\\Pipeline\\\\{closure}(Object(Illuminate\\\\Http\\\\Request))\n#36 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(219): Illuminate\\\\Foundation\\\\Http\\\\Middleware\\\\InvokeDeferredCallbacks->handle(Object(Illuminate\\\\Http\\\\Request), Object(Closure))\n#37 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(137): Illuminate\\\\Pipeline\\\\Pipeline->Illuminate\\\\Pipeline\\\\{closure}(Object(Illuminate\\\\Http\\\\Request))\n#38 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Http/Kernel.php(175): Illuminate\\\\Pipeline\\\\Pipeline->then(Object(Closure))\n#39 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Http/Kernel.php(144): Illuminate\\\\Foundation\\\\Http\\\\Kernel->sendRequestThroughRouter(Object(Illuminate\\\\Http\\\\Request))\n#40 /home/jiminny/public/index.php(51): Illuminate\\\\Foundation\\\\Http\\\\Kernel->handle(Object(Illuminate\\\\Http\\\\Request))\n#41 {main}\n\"} {\"correlation_id\":\"dfad8817-1dde-4622-8320-f5fc1616a8fe\",\"trace_id\":\"d21232b2-60fb-4ca0-838c-ba42e61593f9\"}\n[2026-04-27 10:28:09] local.NOTICE: Monitoring start {\"correlation_id\":\"93744c67-4444-41ff-aaad-6aa9cafe294e\",\"trace_id\":\"80e396b0-b1ac-4ec8-838c-00021f98072c\"}\n[2026-04-27 10:28:09] local.NOTICE: Monitoring end {\"correlation_id\":\"93744c67-4444-41ff-aaad-6aa9cafe294e\",\"trace_id\":\"80e396b0-b1ac-4ec8-838c-00021f98072c\"}\n[2026-04-27 10:28:10] local.ERROR: Target class [Jiminny\\Repositories\\AjReportsRepository] does not exist. {\"exception\":\"[object] (Illuminate\\\\Contracts\\\\Container\\\\BindingResolutionException(code: 0): Target class [Jiminny\\\\Repositories\\\\AjReportsRepository] does not exist. at /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php:1124)\n[stacktrace]\n#0 /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php(933): Illuminate\\\\Container\\\\Container->build('Jiminny\\\\\\\\Reposit...')\n#1 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Application.php(1078): Illuminate\\\\Container\\\\Container->resolve('Jiminny\\\\\\\\Reposit...', Array, true)\n#2 /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php(864): Illuminate\\\\Foundation\\\\Application->resolve('Jiminny\\\\\\\\Reposit...', Array)\n#3 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Application.php(1058): Illuminate\\\\Container\\\\Container->make('Jiminny\\\\\\\\Reposit...', Array)\n#4 /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php(1336): Illuminate\\\\Foundation\\\\Application->make('Jiminny\\\\\\\\Reposit...')\n#5 /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php(1237): Illuminate\\\\Container\\\\Container->resolveClass(Object(ReflectionParameter))\n#6 /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php(1162): Illuminate\\\\Container\\\\Container->resolveDependencies(Array)\n#7 /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php(933): Illuminate\\\\Container\\\\Container->build('Jiminny\\\\\\\\Http\\\\\\\\Co...')\n#8 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Application.php(1078): Illuminate\\\\Container\\\\Container->resolve('Jiminny\\\\\\\\Http\\\\\\\\Co...', Array, true)\n#9 /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php(864): Illuminate\\\\Foundation\\\\Application->resolve('Jiminny\\\\\\\\Http\\\\\\\\Co...', Array)\n#10 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Application.php(1058): Illuminate\\\\Container\\\\Container->make('Jiminny\\\\\\\\Http\\\\\\\\Co...', Array)\n#11 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Route.php(286): Illuminate\\\\Foundation\\\\Application->make('Jiminny\\\\\\\\Http\\\\\\\\Co...')\n#12 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Route.php(1133): Illuminate\\\\Routing\\\\Route->getController()\n#13 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Route.php(1062): Illuminate\\\\Routing\\\\Route->controllerMiddleware()\n#14 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Router.php(834): Illuminate\\\\Routing\\\\Route->gatherMiddleware()\n#15 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Router.php(816): Illuminate\\\\Routing\\\\Router->gatherRouteMiddleware(Object(Illuminate\\\\Routing\\\\Route))\n#16 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Router.php(800): Illuminate\\\\Routing\\\\Router->runRouteWithinStack(Object(Illuminate\\\\Routing\\\\Route), Object(Illuminate\\\\Http\\\\Request))\n#17 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Router.php(764): Illuminate\\\\Routing\\\\Router->runRoute(Object(Illuminate\\\\Http\\\\Request), Object(Illuminate\\\\Routing\\\\Route))\n#18 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Router.php(753): Illuminate\\\\Routing\\\\Router->dispatchToRoute(Object(Illuminate\\\\Http\\\\Request))\n#19 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Http/Kernel.php(200): Illuminate\\\\Routing\\\\Router->dispatch(Object(Illuminate\\\\Http\\\\Request))\n#20 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(180): Illuminate\\\\Foundation\\\\Http\\\\Kernel->Illuminate\\\\Foundation\\\\Http\\\\{closure}(Object(Illuminate\\\\Http\\\\Request))\n#21 /home/jiminny/vendor/barryvdh/laravel-debugbar/src/Middleware/InjectDebugbar.php(59): Illuminate\\\\Pipeline\\\\Pipeline->Illuminate\\\\Pipeline\\\\{closure}(Object(Illuminate\\\\Http\\\\Request))\n#22 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(219): Barryvdh\\\\Debugbar\\\\Middleware\\\\InjectDebugbar->handle(Object(Illuminate\\\\Http\\\\Request), Object(Closure))\n#23 /home/jiminny/vendor/laravel/framework/src/Illuminate/Http/Middleware/HandleCors.php(74): Illuminate\\\\Pipeline\\\\Pipeline->Illuminate\\\\Pipeline\\\\{closure}(Object(Illuminate\\\\Http\\\\Request))\n#24 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(219): Illuminate\\\\Http\\\\Middleware\\\\HandleCors->handle(Object(Illuminate\\\\Http\\\\Request), Object(Closure))\n#25 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Http/Middleware/TransformsRequest.php(21): Illuminate\\\\Pipeline\\\\Pipeline->Illuminate\\\\Pipeline\\\\{closure}(Object(Illuminate\\\\Http\\\\Request))\n#26 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Http/Middleware/TrimStrings.php(51): Illuminate\\\\Foundation\\\\Http\\\\Middleware\\\\TransformsRequest->handle(Object(Illuminate\\\\Http\\\\Request), Object(Closure))\n#27 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(219): Illuminate\\\\Foundation\\\\Http\\\\Middleware\\\\TrimStrings->handle(Object(Illuminate\\\\Http\\\\Request), Object(Closure))\n#28 /home/jiminny/vendor/bepsvpt/secure-headers/src/SecureHeadersMiddleware.php(18): Illuminate\\\\Pipeline\\\\Pipeline->Illuminate\\\\Pipeline\\\\{closure}(Object(Illuminate\\\\Http\\\\Request))\n#29 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(219): Bepsvpt\\\\SecureHeaders\\\\SecureHeadersMiddleware->handle(Object(Illuminate\\\\Http\\\\Request), Object(Closure))\n#30 /home/jiminny/app/Http/Middleware/SentryContext.php(60): Illuminate\\\\Pipeline\\\\Pipeline->Illuminate\\\\Pipeline\\\\{closure}(Object(Illuminate\\\\Http\\\\Request))\n#31 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(219): Jiminny\\\\Http\\\\Middleware\\\\SentryContext->handle(Object(Illuminate\\\\Http\\\\Request), Object(Closure))\n#32 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Http/Middleware/PreventRequestsDuringMaintenance.php(109): Illuminate\\\\Pipeline\\\\Pipeline->Illuminate\\\\Pipeline\\\\{closure}(Object(Illuminate\\\\Http\\\\Request))\n#33 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(219): Illuminate\\\\Foundation\\\\Http\\\\Middleware\\\\PreventRequestsDuringMaintenance->handle(Object(Illuminate\\\\Http\\\\Request), Object(Closure))\n#34 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Http/Middleware/InvokeDeferredCallbacks.php(22): Illuminate\\\\Pipeline\\\\Pipeline->Illuminate\\\\Pipeline\\\\{closure}(Object(Illuminate\\\\Http\\\\Request))\n#35 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(219): Illuminate\\\\Foundation\\\\Http\\\\Middleware\\\\InvokeDeferredCallbacks->handle(Object(Illuminate\\\\Http\\\\Request), Object(Closure))\n#36 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(137): Illuminate\\\\Pipeline\\\\Pipeline->Illuminate\\\\Pipeline\\\\{closure}(Object(Illuminate\\\\Http\\\\Request))\n#37 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Http/Kernel.php(175): Illuminate\\\\Pipeline\\\\Pipeline->then(Object(Closure))\n#38 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Http/Kernel.php(144): Illuminate\\\\Foundation\\\\Http\\\\Kernel->sendRequestThroughRouter(Object(Illuminate\\\\Http\\\\Request))\n#39 /home/jiminny/public/index.php(51): Illuminate\\\\Foundation\\\\Http\\\\Kernel->handle(Object(Illuminate\\\\Http\\\\Request))\n#40 {main}\n\n[previous exception] [object] (ReflectionException(code: -1): Class \\\"Jiminny\\\\Repositories\\\\AjReportsRepository\\\" does not exist at /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php:1122)\n[stacktrace]\n#0 /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php(1122): ReflectionClass->__construct('Jiminny\\\\\\\\Reposit...')\n#1 /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php(933): Illuminate\\\\Container\\\\Container->build('Jiminny\\\\\\\\Reposit...')\n#2 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Application.php(1078): Illuminate\\\\Container\\\\Container->resolve('Jiminny\\\\\\\\Reposit...', Array, true)\n#3 /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php(864): Illuminate\\\\Foundation\\\\Application->resolve('Jiminny\\\\\\\\Reposit...', Array)\n#4 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Application.php(1058): Illuminate\\\\Container\\\\Container->make('Jiminny\\\\\\\\Reposit...', Array)\n#5 /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php(1336): Illuminate\\\\Foundation\\\\Application->make('Jiminny\\\\\\\\Reposit...')\n#6 /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php(1237): Illuminate\\\\Container\\\\Container->resolveClass(Object(ReflectionParameter))\n#7 /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php(1162): Illuminate\\\\Container\\\\Container->resolveDependencies(Array)\n#8 /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php(933): Illuminate\\\\Container\\\\Container->build('Jiminny\\\\\\\\Http\\\\\\\\Co...')\n#9 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Application.php(1078): Illuminate\\\\Container\\\\Container->resolve('Jiminny\\\\\\\\Http\\\\\\\\Co...', Array, true)\n#10 /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php(864): Illuminate\\\\Foundation\\\\Application->resolve('Jiminny\\\\\\\\Http\\\\\\\\Co...', Array)\n#11 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Application.php(1058): Illuminate\\\\Container\\\\Container->make('Jiminny\\\\\\\\Http\\\\\\\\Co...', Array)\n#12 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Route.php(286): Illuminate\\\\Foundation\\\\Application->make('Jiminny\\\\\\\\Http\\\\\\\\Co...')\n#13 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Route.php(1133): Illuminate\\\\Routing\\\\Route->getController()\n#14 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Route.php(1062): Illuminate\\\\Routing\\\\Route->controllerMiddleware()\n#15 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Router.php(834): Illuminate\\\\Routing\\\\Route->gatherMiddleware()\n#16 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Router.php(816): Illuminate\\\\Routing\\\\Router->gatherRouteMiddleware(Object(Illuminate\\\\Routing\\\\Route))\n#17 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Router.php(800): Illuminate\\\\Routing\\\\Router->runRouteWithinStack(Object(Illuminate\\\\Routing\\\\Route), Object(Illuminate\\\\Http\\\\Request))\n#18 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Router.php(764): Illuminate\\\\Routing\\\\Router->runRoute(Object(Illuminate\\\\Http\\\\Request), Object(Illuminate\\\\Routing\\\\Route))\n#19 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Router.php(753): Illuminate\\\\Routing\\\\Router->dispatchToRoute(Object(Illuminate\\\\Http\\\\Request))\n#20 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Http/Kernel.php(200): Illuminate\\\\Routing\\\\Router->dispatch(Object(Illuminate\\\\Http\\\\Request))\n#21 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(180): Illuminate\\\\Foundation\\\\Http\\\\Kernel->Illuminate\\\\Foundation\\\\Http\\\\{closure}(Object(Illuminate\\\\Http\\\\Request))\n#22 /home/jiminny/vendor/barryvdh/laravel-debugbar/src/Middleware/InjectDebugbar.php(59): Illuminate\\\\Pipeline\\\\Pipeline->Illuminate\\\\Pipeline\\\\{closure}(Object(Illuminate\\\\Http\\\\Request))\n#23 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(219): Barryvdh\\\\Debugbar\\\\Middleware\\\\InjectDebugbar->handle(Object(Illuminate\\\\Http\\\\Request), Object(Closure))\n#24 /home/jiminny/vendor/laravel/framework/src/Illuminate/Http/Middleware/HandleCors.php(74): Illuminate\\\\Pipeline\\\\Pipeline->Illuminate\\\\Pipeline\\\\{closure}(Object(Illuminate\\\\Http\\\\Request))\n#25 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(219): Illuminate\\\\Http\\\\Middleware\\\\HandleCors->handle(Object(Illuminate\\\\Http\\\\Request), Object(Closure))\n#26 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Http/Middleware/TransformsRequest.php(21): Illuminate\\\\Pipeline\\\\Pipeline->Illuminate\\\\Pipeline\\\\{closure}(Object(Illuminate\\\\Http\\\\Request))\n#27 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Http/Middleware/TrimStrings.php(51): Illuminate\\\\Foundation\\\\Http\\\\Middleware\\\\TransformsRequest->handle(Object(Illuminate\\\\Http\\\\Request), Object(Closure))\n#28 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(219): Illuminate\\\\Foundation\\\\Http\\\\Middleware\\\\TrimStrings->handle(Object(Illuminate\\\\Http\\\\Request), Object(Closure))\n#29 /home/jiminny/vendor/bepsvpt/secure-headers/src/SecureHeadersMiddleware.php(18): Illuminate\\\\Pipeline\\\\Pipeline->Illuminate\\\\Pipeline\\\\{closure}(Object(Illuminate\\\\Http\\\\Request))\n#30 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(219): Bepsvpt\\\\SecureHeaders\\\\SecureHeadersMiddleware->handle(Object(Illuminate\\\\Http\\\\Request), Object(Closure))\n#31 /home/jiminny/app/Http/Middleware/SentryContext.php(60): Illuminate\\\\Pipeline\\\\Pipeline->Illuminate\\\\Pipeline\\\\{closure}(Object(Illuminate\\\\Http\\\\Request))\n#32 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(219): Jiminny\\\\Http\\\\Middleware\\\\SentryContext->handle(Object(Illuminate\\\\Http\\\\Request), Object(Closure))\n#33 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Http/Middleware/PreventRequestsDuringMaintenance.php(109): Illuminate\\\\Pipeline\\\\Pipeline->Illuminate\\\\Pipeline\\\\{closure}(Object(Illuminate\\\\Http\\\\Request))\n#34 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(219): Illuminate\\\\Foundation\\\\Http\\\\Middleware\\\\PreventRequestsDuringMaintenance->handle(Object(Illuminate\\\\Http\\\\Request), Object(Closure))\n#35 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Http/Middleware/InvokeDeferredCallbacks.php(22): Illuminate\\\\Pipeline\\\\Pipeline->Illuminate\\\\Pipeline\\\\{closure}(Object(Illuminate\\\\Http\\\\Request))\n#36 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(219): Illuminate\\\\Foundation\\\\Http\\\\Middleware\\\\InvokeDeferredCallbacks->handle(Object(Illuminate\\\\Http\\\\Request), Object(Closure))\n#37 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(137): Illuminate\\\\Pipeline\\\\Pipeline->Illuminate\\\\Pipeline\\\\{closure}(Object(Illuminate\\\\Http\\\\Request))\n#38 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Http/Kernel.php(175): Illuminate\\\\Pipeline\\\\Pipeline->then(Object(Closure))\n#39 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Http/Kernel.php(144): Illuminate\\\\Foundation\\\\Http\\\\Kernel->sendRequestThroughRouter(Object(Illuminate\\\\Http\\\\Request))\n#40 /home/jiminny/public/index.php(51): Illuminate\\\\Foundation\\\\Http\\\\Kernel->handle(Object(Illuminate\\\\Http\\\\Request))\n#41 {main}\n\"} {\"correlation_id\":\"de768781-5c05-418c-baf3-79f77a8c8694\",\"trace_id\":\"fec74062-3f6d-4cfc-968a-1bb285496db3\"}\n[2026-04-27 10:28:12] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage before starting command {\"command\":\"mailbox:skip-lists:refresh\",\"memoryBeforeCommandInMb\":60.0,\"memoryPeakBeforeCommandInMb\":99.727} {\"correlation_id\":\"907d15ed-8c84-41a1-a473-590eebb55dbc\",\"trace_id\":\"ec7bd353-516e-491d-89dd-de44136c1672\"}\n[2026-04-27 10:28:12] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage for command {\"command\":\"mailbox:skip-lists:refresh\",\"memoryBeforeCommandInMb\":60.0,\"memoryAfterCommandInMB\":62.0,\"memoryPeakBeforeCommandInMb\":99.727,\"memoryPeakAfterCommandInMB\":99.727} {\"correlation_id\":\"907d15ed-8c84-41a1-a473-590eebb55dbc\",\"trace_id\":\"ec7bd353-516e-491d-89dd-de44136c1672\"}\n[2026-04-27 10:28:13] local.ERROR: Target class [Jiminny\\Repositories\\AjReportsRepository] does not exist. {\"exception\":\"[object] (Illuminate\\\\Contracts\\\\Container\\\\BindingResolutionException(code: 0): Target class [Jiminny\\\\Repositories\\\\AjReportsRepository] does not exist. at /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php:1124)\n[stacktrace]\n#0 /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php(933): Illuminate\\\\Container\\\\Container->build('Jiminny\\\\\\\\Reposit...')\n#1 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Application.php(1078): Illuminate\\\\Container\\\\Container->resolve('Jiminny\\\\\\\\Reposit...', Array, true)\n#2 /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php(864): Illuminate\\\\Foundation\\\\Application->resolve('Jiminny\\\\\\\\Reposit...', Array)\n#3 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Application.php(1058): Illuminate\\\\Container\\\\Container->make('Jiminny\\\\\\\\Reposit...', Array)\n#4 /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php(1336): Illuminate\\\\Foundation\\\\Application->make('Jiminny\\\\\\\\Reposit...')\n#5 /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php(1237): Illuminate\\\\Container\\\\Container->resolveClass(Object(ReflectionParameter))\n#6 /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php(1162): Illuminate\\\\Container\\\\Container->resolveDependencies(Array)\n#7 /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php(933): Illuminate\\\\Container\\\\Container->build('Jiminny\\\\\\\\Http\\\\\\\\Co...')\n#8 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Application.php(1078): Illuminate\\\\Container\\\\Container->resolve('Jiminny\\\\\\\\Http\\\\\\\\Co...', Array, true)\n#9 /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php(864): Illuminate\\\\Foundation\\\\Application->resolve('Jiminny\\\\\\\\Http\\\\\\\\Co...', Array)\n#10 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Application.php(1058): Illuminate\\\\Container\\\\Container->make('Jiminny\\\\\\\\Http\\\\\\\\Co...', Array)\n#11 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Route.php(286): Illuminate\\\\Foundation\\\\Application->make('Jiminny\\\\\\\\Http\\\\\\\\Co...')\n#12 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Route.php(1133): Illuminate\\\\Routing\\\\Route->getController()\n#13 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Route.php(1062): Illuminate\\\\Routing\\\\Route->controllerMiddleware()\n#14 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Router.php(834): Illuminate\\\\Routing\\\\Route->gatherMiddleware()\n#15 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Router.php(816): Illuminate\\\\Routing\\\\Router->gatherRouteMiddleware(Object(Illuminate\\\\Routing\\\\Route))\n#16 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Router.php(800): Illuminate\\\\Routing\\\\Router->runRouteWithinStack(Object(Illuminate\\\\Routing\\\\Route), Object(Illuminate\\\\Http\\\\Request))\n#17 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Router.php(764): Illuminate\\\\Routing\\\\Router->runRoute(Object(Illuminate\\\\Http\\\\Request), Object(Illuminate\\\\Routing\\\\Route))\n#18 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Router.php(753): Illuminate\\\\Routing\\\\Router->dispatchToRoute(Object(Illuminate\\\\Http\\\\Request))\n#19 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Http/Kernel.php(200): Illuminate\\\\Routing\\\\Router->dispatch(Object(Illuminate\\\\Http\\\\Request))\n#20 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(180): Illuminate\\\\Foundation\\\\Http\\\\Kernel->Illuminate\\\\Foundation\\\\Http\\\\{closure}(Object(Illuminate\\\\Http\\\\Request))\n#21 /home/jiminny/vendor/barryvdh/laravel-debugbar/src/Middleware/InjectDebugbar.php(59): Illuminate\\\\Pipeline\\\\Pipeline->Illuminate\\\\Pipeline\\\\{closure}(Object(Illuminate\\\\Http\\\\Request))\n#22 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(219): Barryvdh\\\\Debugbar\\\\Middleware\\\\InjectDebugbar->handle(Object(Illuminate\\\\Http\\\\Request), Object(Closure))\n#23 /home/jiminny/vendor/laravel/framework/src/Illuminate/Http/Middleware/HandleCors.php(74): Illuminate\\\\Pipeline\\\\Pipeline->Illuminate\\\\Pipeline\\\\{closure}(Object(Illuminate\\\\Http\\\\Request))\n#24 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(219): Illuminate\\\\Http\\\\Middleware\\\\HandleCors->handle(Object(Illuminate\\\\Http\\\\Request), Object(Closure))\n#25 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Http/Middleware/TransformsRequest.php(21): Illuminate\\\\Pipeline\\\\Pipeline->Illuminate\\\\Pipeline\\\\{closure}(Object(Illuminate\\\\Http\\\\Request))\n#26 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Http/Middleware/TrimStrings.php(51): Illuminate\\\\Foundation\\\\Http\\\\Middleware\\\\TransformsRequest->handle(Object(Illuminate\\\\Http\\\\Request), Object(Closure))\n#27 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(219): Illuminate\\\\Foundation\\\\Http\\\\Middleware\\\\TrimStrings->handle(Object(Illuminate\\\\Http\\\\Request), Object(Closure))\n#28 /home/jiminny/vendor/bepsvpt/secure-headers/src/SecureHeadersMiddleware.php(18): Illuminate\\\\Pipeline\\\\Pipeline->Illuminate\\\\Pipeline\\\\{closure}(Object(Illuminate\\\\Http\\\\Request))\n#29 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(219): Bepsvpt\\\\SecureHeaders\\\\SecureHeadersMiddleware->handle(Object(Illuminate\\\\Http\\\\Request), Object(Closure))\n#30 /home/jiminny/app/Http/Middleware/SentryContext.php(60): Illuminate\\\\Pipeline\\\\Pipeline->Illuminate\\\\Pipeline\\\\{closure}(Object(Illuminate\\\\Http\\\\Request))\n#31 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(219): Jiminny\\\\Http\\\\Middleware\\\\SentryContext->handle(Object(Illuminate\\\\Http\\\\Request), Object(Closure))\n#32 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Http/Middleware/PreventRequestsDuringMaintenance.php(109): Illuminate\\\\Pipeline\\\\Pipeline->Illuminate\\\\Pipeline\\\\{closure}(Object(Illuminate\\\\Http\\\\Request))\n#33 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(219): Illuminate\\\\Foundation\\\\Http\\\\Middleware\\\\PreventRequestsDuringMaintenance->handle(Object(Illuminate\\\\Http\\\\Request), Object(Closure))\n#34 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Http/Middleware/InvokeDeferredCallbacks.php(22): Illuminate\\\\Pipeline\\\\Pipeline->Illuminate\\\\Pipeline\\\\{closure}(Object(Illuminate\\\\Http\\\\Request))\n#35 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(219): Illuminate\\\\Foundation\\\\Http\\\\Middleware\\\\InvokeDeferredCallbacks->handle(Object(Illuminate\\\\Http\\\\Request), Object(Closure))\n#36 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(137): Illuminate\\\\Pipeline\\\\Pipeline->Illuminate\\\\Pipeline\\\\{closure}(Object(Illuminate\\\\Http\\\\Request))\n#37 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Http/Kernel.php(175): Illuminate\\\\Pipeline\\\\Pipeline->then(Object(Closure))\n#38 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Http/Kernel.php(144): Illuminate\\\\Foundation\\\\Http\\\\Kernel->sendRequestThroughRouter(Object(Illuminate\\\\Http\\\\Request))\n#39 /home/jiminny/public/index.php(51): Illuminate\\\\Foundation\\\\Http\\\\Kernel->handle(Object(Illuminate\\\\Http\\\\Request))\n#40 {main}\n\n[previous exception] [object] (ReflectionException(code: -1): Class \\\"Jiminny\\\\Repositories\\\\AjReportsRepository\\\" does not exist at /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php:1122)\n[stacktrace]\n#0 /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php(1122): ReflectionClass->__construct('Jiminny\\\\\\\\Reposit...')\n#1 /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php(933): Illuminate\\\\Container\\\\Container->build('Jiminny\\\\\\\\Reposit...')\n#2 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Application.php(1078): Illuminate\\\\Container\\\\Container->resolve('Jiminny\\\\\\\\Reposit...', Array, true)\n#3 /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php(864): Illuminate\\\\Foundation\\\\Application->resolve('Jiminny\\\\\\\\Reposit...', Array)\n#4 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Application.php(1058): Illuminate\\\\Container\\\\Container->make('Jiminny\\\\\\\\Reposit...', Array)\n#5 /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php(1336): Illuminate\\\\Foundation\\\\Application->make('Jiminny\\\\\\\\Reposit...')\n#6 /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php(1237): Illuminate\\\\Container\\\\Container->resolveClass(Object(ReflectionParameter))\n#7 /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php(1162): Illuminate\\\\Container\\\\Container->resolveDependencies(Array)\n#8 /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php(933): Illuminate\\\\Container\\\\Container->build('Jiminny\\\\\\\\Http\\\\\\\\Co...')\n#9 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Application.php(1078): Illuminate\\\\Container\\\\Container->resolve('Jiminny\\\\\\\\Http\\\\\\\\Co...', Array, true)\n#10 /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php(864): Illuminate\\\\Foundation\\\\Application->resolve('Jiminny\\\\\\\\Http\\\\\\\\Co...', Array)\n#11 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Application.php(1058): Illuminate\\\\Container\\\\Container->make('Jiminny\\\\\\\\Http\\\\\\\\Co...', Array)\n#12 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Route.php(286): Illuminate\\\\Foundation\\\\Application->make('Jiminny\\\\\\\\Http\\\\\\\\Co...')\n#13 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Route.php(1133): Illuminate\\\\Routing\\\\Route->getController()\n#14 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Route.php(1062): Illuminate\\\\Routing\\\\Route->controllerMiddleware()\n#15 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Router.php(834): Illuminate\\\\Routing\\\\Route->gatherMiddleware()\n#16 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Router.php(816): Illuminate\\\\Routing\\\\Router->gatherRouteMiddleware(Object(Illuminate\\\\Routing\\\\Route))\n#17 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Router.php(800): Illuminate\\\\Routing\\\\Router->runRouteWithinStack(Object(Illuminate\\\\Routing\\\\Route), Object(Illuminate\\\\Http\\\\Request))\n#18 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Router.php(764): Illuminate\\\\Routing\\\\Router->runRoute(Object(Illuminate\\\\Http\\\\Request), Object(Illuminate\\\\Routing\\\\Route))\n#19 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Router.php(753): Illuminate\\\\Routing\\\\Router->dispatchToRoute(Object(Illuminate\\\\Http\\\\Request))\n#20 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Http/Kernel.php(200): Illuminate\\\\Routing\\\\Router->dispatch(Object(Illuminate\\\\Http\\\\Request))\n#21 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(180): Illuminate\\\\Foundation\\\\Http\\\\Kernel->Illuminate\\\\Foundation\\\\Http\\\\{closure}(Object(Illuminate\\\\Http\\\\Request))\n#22 /home/jiminny/vendor/barryvdh/laravel-debugbar/src/Middleware/InjectDebugbar.php(59): Illuminate\\\\Pipeline\\\\Pipeline->Illuminate\\\\Pipeline\\\\{closure}(Object(Illuminate\\\\Http\\\\Request))\n#23 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(219): Barryvdh\\\\Debugbar\\\\Middleware\\\\InjectDebugbar->handle(Object(Illuminate\\\\Http\\\\Request), Object(Closure))\n#24 /home/jiminny/vendor/laravel/framework/src/Illuminate/Http/Middleware/HandleCors.php(74): Illuminate\\\\Pipeline\\\\Pipeline->Illuminate\\\\Pipeline\\\\{closure}(Object(Illuminate\\\\Http\\\\Request))\n#25 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(219): Illuminate\\\\Http\\\\Middleware\\\\HandleCors->handle(Object(Illuminate\\\\Http\\\\Request), Object(Closure))\n#26 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Http/Middleware/TransformsRequest.php(21): Illuminate\\\\Pipeline\\\\Pipeline->Illuminate\\\\Pipeline\\\\{closure}(Object(Illuminate\\\\Http\\\\Request))\n#27 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Http/Middleware/TrimStrings.php(51): Illuminate\\\\Foundation\\\\Http\\\\Middleware\\\\TransformsRequest->handle(Object(Illuminate\\\\Http\\\\Request), Object(Closure))\n#28 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(219): Illuminate\\\\Foundation\\\\Http\\\\Middleware\\\\TrimStrings->handle(Object(Illuminate\\\\Http\\\\Request), Object(Closure))\n#29 /home/jiminny/vendor/bepsvpt/secure-headers/src/SecureHeadersMiddleware.php(18): Illuminate\\\\Pipeline\\\\Pipeline->Illuminate\\\\Pipeline\\\\{closure}(Object(Illuminate\\\\Http\\\\Request))\n#30 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(219): Bepsvpt\\\\SecureHeaders\\\\SecureHeadersMiddleware->handle(Object(Illuminate\\\\Http\\\\Request), Object(Closure))\n#31 /home/jiminny/app/Http/Middleware/SentryContext.php(60): Illuminate\\\\Pipeline\\\\Pipeline->Illuminate\\\\Pipeline\\\\{closure}(Object(Illuminate\\\\Http\\\\Request))\n#32 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(219): Jiminny\\\\Http\\\\Middleware\\\\SentryContext->handle(Object(Illuminate\\\\Http\\\\Request), Object(Closure))\n#33 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Http/Middleware/PreventRequestsDuringMaintenance.php(109): Illuminate\\\\Pipeline\\\\Pipeline->Illuminate\\\\Pipeline\\\\{closure}(Object(Illuminate\\\\Http\\\\Request))\n#34 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(219): Illuminate\\\\Foundation\\\\Http\\\\Middleware\\\\PreventRequestsDuringMaintenance->handle(Object(Illuminate\\\\Http\\\\Request), Object(Closure))\n#35 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Http/Middleware/InvokeDeferredCallbacks.php(22): Illuminate\\\\Pipeline\\\\Pipeline->Illuminate\\\\Pipeline\\\\{closure}(Object(Illuminate\\\\Http\\\\Request))\n#36 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(219): Illuminate\\\\Foundation\\\\Http\\\\Middleware\\\\InvokeDeferredCallbacks->handle(Object(Illuminate\\\\Http\\\\Request), Object(Closure))\n#37 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(137): Illuminate\\\\Pipeline\\\\Pipeline->Illuminate\\\\Pipeline\\\\{closure}(Object(Illuminate\\\\Http\\\\Request))\n#38 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Http/Kernel.php(175): Illuminate\\\\Pipeline\\\\Pipeline->then(Object(Closure))\n#39 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Http/Kernel.php(144): Illuminate\\\\Foundation\\\\Http\\\\Kernel->sendRequestThroughRouter(Object(Illuminate\\\\Http\\\\Request))\n#40 /home/jiminny/public/index.php(51): Illuminate\\\\Foundation\\\\Http\\\\Kernel->handle(Object(Illuminate\\\\Http\\\\Request))\n#41 {main}\n\"} {\"correlation_id\":\"d8709bb9-0f8c-4798-a8c7-e68e24ab6265\",\"trace_id\":\"c5bea1cc-53fe-4bda-a8e2-bec002030bc2\"}\n[2026-04-27 10:28:14] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage before starting command {\"command\":\"mailbox:batch:process\",\"memoryBeforeCommandInMb\":60.0,\"memoryPeakBeforeCommandInMb\":99.727} {\"correlation_id\":\"5fb3c8c8-3160-4964-ac56-02d6e2cb998c\",\"trace_id\":\"3a6d5e64-ecf2-4700-98cd-3edd56a1dc34\"}\n[2026-04-27 10:28:14] local.INFO: [EmailSchedule] STARTING batch process {\"host\":\"docker_lamp_1\"} {\"correlation_id\":\"5fb3c8c8-3160-4964-ac56-02d6e2cb998c\",\"trace_id\":\"3a6d5e64-ecf2-4700-98cd-3edd56a1dc34\"}\n[2026-04-27 10:28:14] local.INFO: Processing email batch 98446 for inbox 212 {\"correlation_id\":\"5fb3c8c8-3160-4964-ac56-02d6e2cb998c\",\"trace_id\":\"3a6d5e64-ecf2-4700-98cd-3edd56a1dc34\"}\n[2026-04-27 10:28:14] local.INFO: [SocialAccountService] Fetching token {\"socialAccountId\":1500,\"provider\":\"salesforce\"} {\"correlation_id\":\"5fb3c8c8-3160-4964-ac56-02d6e2cb998c\",\"trace_id\":\"3a6d5e64-ecf2-4700-98cd-3edd56a1dc34\"}\n[2026-04-27 10:28:14] local.INFO: [SocialAccountService] Token retrieved {\"socialAccountId\":1500,\"provider\":\"salesforce\"} {\"correlation_id\":\"5fb3c8c8-3160-4964-ac56-02d6e2cb998c\",\"trace_id\":\"3a6d5e64-ecf2-4700-98cd-3edd56a1dc34\"}\n[2026-04-27 10:28:14] local.INFO: [EncryptedTokenManager] Generating access token. {\"mode\":\"legacy\"} {\"correlation_id\":\"5fb3c8c8-3160-4964-ac56-02d6e2cb998c\",\"trace_id\":\"3a6d5e64-ecf2-4700-98cd-3edd56a1dc34\"}\n[2026-04-27 10:28:14] local.INFO: [CrmOwnerResolver] Integration owner matched as CRM Owner {\"crm_provider\":\"salesforce\",\"crm_owner\":143,\"team_id\":1} {\"correlation_id\":\"5fb3c8c8-3160-4964-ac56-02d6e2cb998c\",\"trace_id\":\"3a6d5e64-ecf2-4700-98cd-3edd56a1dc34\"}\n[2026-04-27 10:28:14] local.INFO: [SocialAccountService] Fetching token {\"socialAccountId\":1354,\"provider\":\"google\"} {\"correlation_id\":\"5fb3c8c8-3160-4964-ac56-02d6e2cb998c\",\"trace_id\":\"3a6d5e64-ecf2-4700-98cd-3edd56a1dc34\"}\n[2026-04-27 10:28:14] local.INFO: [SocialAccountService] Token retrieved {\"socialAccountId\":1354,\"provider\":\"google\"} {\"correlation_id\":\"5fb3c8c8-3160-4964-ac56-02d6e2cb998c\",\"trace_id\":\"3a6d5e64-ecf2-4700-98cd-3edd56a1dc34\"}\n[2026-04-27 10:28:14] local.INFO: [EncryptedTokenManager] Generating access token. {\"mode\":\"legacy\"} {\"correlation_id\":\"5fb3c8c8-3160-4964-ac56-02d6e2cb998c\",\"trace_id\":\"3a6d5e64-ecf2-4700-98cd-3edd56a1dc34\"}\n[2026-04-27 10:28:15] local.INFO: Processing an email from inbox batch {\"batch\":98446,\"inbox_id\":212,\"email\":\"lukas.kovalik@jiminny.com\",\"email_id\":\"19dce788ac964bbf\",\"from\":\"Nikolay Yankov <notifications@github.com>\",\"to\":\"\\\"jiminny/app\\\" <app@noreply.github.com>\",\"cc\":\"Push <push@noreply.github.com>\"} {\"correlation_id\":\"5fb3c8c8-3160-4964-ac56-02d6e2cb998c\",\"trace_id\":\"3a6d5e64-ecf2-4700-98cd-3edd56a1dc34\"}\n[2026-04-27 10:28:15] local.INFO: [SocialAccountService] Fetching token {\"socialAccountId\":1500,\"provider\":\"salesforce\"} {\"correlation_id\":\"5fb3c8c8-3160-4964-ac56-02d6e2cb998c\",\"trace_id\":\"3a6d5e64-ecf2-4700-98cd-3edd56a1dc34\"}\n[2026-04-27 10:28:15] local.INFO: [SocialAccountService] Token retrieved {\"socialAccountId\":1500,\"provider\":\"salesforce\"} {\"correlation_id\":\"5fb3c8c8-3160-4964-ac56-02d6e2cb998c\",\"trace_id\":\"3a6d5e64-ecf2-4700-98cd-3edd56a1dc34\"}\n[2026-04-27 10:28:15] local.INFO: [EncryptedTokenManager] Generating access token. {\"mode\":\"legacy\"} {\"correlation_id\":\"5fb3c8c8-3160-4964-ac56-02d6e2cb998c\",\"trace_id\":\"3a6d5e64-ecf2-4700-98cd-3edd56a1dc34\"}\n[2026-04-27 10:28:15] local.INFO: [CrmOwnerResolver] Integration owner matched as CRM Owner {\"crm_provider\":\"salesforce\",\"crm_owner\":143,\"team_id\":1} {\"correlation_id\":\"5fb3c8c8-3160-4964-ac56-02d6e2cb998c\",\"trace_id\":\"3a6d5e64-ecf2-4700-98cd-3edd56a1dc34\"}\n[2026-04-27 10:28:15] local.INFO: [EmailImport\\ParticipantsResolver] The sender email is blacklisted, skipping {\"email\":\"notifications@github.com\",\"inbox_id\":212,\"message_provider_id\":\"19dce788ac964bbf\",\"team_id\":1} {\"correlation_id\":\"5fb3c8c8-3160-4964-ac56-02d6e2cb998c\",\"trace_id\":\"3a6d5e64-ecf2-4700-98cd-3edd56a1dc34\"}\n[2026-04-27 10:28:15] local.INFO: [EmailImport\\ParticipantsValidator] Email participants are less than 2 {\"inbox_id\":212,\"message_provider_id\":\"19dce788ac964bbf\",\"message_id\":\"<jiminny/app/pull/11998/before/2b8a97d6be8b053441d5a845b72dd04803cbb53c/after/8a68a94d39cc76ae546e19f354e8b0dabd577314@github.com>\"} {\"correlation_id\":\"5fb3c8c8-3160-4964-ac56-02d6e2cb998c\",\"trace_id\":\"3a6d5e64-ecf2-4700-98cd-3edd56a1dc34\"}\n[2026-04-27 10:28:15] local.INFO: Processing an email from inbox batch {\"batch\":98446,\"inbox_id\":212,\"email\":\"lukas.kovalik@jiminny.com\",\"email_id\":\"19dce7883f2fea19\",\"from\":\"The Jiminny Team <no-reply@dev.jiminny.com>\",\"to\":\"lukas.kovalik@jiminny.com\",\"cc\":null} {\"correlation_id\":\"5fb3c8c8-3160-4964-ac56-02d6e2cb998c\",\"trace_id\":\"3a6d5e64-ecf2-4700-98cd-3edd56a1dc34\"}\n[2026-04-27 10:28:15] local.INFO: [SocialAccountService] Fetching token {\"socialAccountId\":1500,\"provider\":\"salesforce\"} {\"correlation_id\":\"5fb3c8c8-3160-4964-ac56-02d6e2cb998c\",\"trace_id\":\"3a6d5e64-ecf2-4700-98cd-3edd56a1dc34\"}\n[2026-04-27 10:28:15] local.INFO: [SocialAccountService] Token retrieved {\"socialAccountId\":1500,\"provider\":\"salesforce\"} {\"correlation_id\":\"5fb3c8c8-3160-4964-ac56-02d6e2cb998c\",\"trace_id\":\"3a6d5e64-ecf2-4700-98cd-3edd56a1dc34\"}\n[2026-04-27 10:28:15] local.INFO: [EncryptedTokenManager] Generating access token. {\"mode\":\"legacy\"} {\"correlation_id\":\"5fb3c8c8-3160-4964-ac56-02d6e2cb998c\",\"trace_id\":\"3a6d5e64-ecf2-4700-98cd-3edd56a1dc34\"}\n[2026-04-27 10:28:15] local.INFO: [CrmOwnerResolver] Integration owner matched as CRM Owner {\"crm_provider\":\"salesforce\",\"crm_owner\":143,\"team_id\":1} {\"correlation_id\":\"5fb3c8c8-3160-4964-ac56-02d6e2cb998c\",\"trace_id\":\"3a6d5e64-ecf2-4700-98cd-3edd56a1dc34\"}\n[2026-04-27 10:28:15] local.INFO: [EmailImport\\ParticipantsResolver] The sender email is blacklisted, skipping {\"email\":\"no-reply@dev.jiminny.com\",\"inbox_id\":212,\"message_provider_id\":\"19dce7883f2fea19\",\"team_id\":1} {\"correlation_id\":\"5fb3c8c8-3160-4964-ac56-02d6e2cb998c\",\"trace_id\":\"3a6d5e64-ecf2-4700-98cd-3edd56a1dc34\"}\n[2026-04-27 10:28:15] local.INFO: [EmailImport\\ParticipantsValidator] Email participants are less than 2 {\"inbox_id\":212,\"message_provider_id\":\"19dce7883f2fea19\",\"message_id\":\"<d70689e8-7b14-42ac-a3bf-5f1e81da943e@mtasv.net>\"} {\"correlation_id\":\"5fb3c8c8-3160-4964-ac56-02d6e2cb998c\",\"trace_id\":\"3a6d5e64-ecf2-4700-98cd-3edd56a1dc34\"}\n[2026-04-27 10:28:15] local.INFO: Processing an email from inbox batch {\"batch\":98446,\"inbox_id\":212,\"email\":\"lukas.kovalik@jiminny.com\",\"email_id\":\"19dce761cb049fba\",\"from\":\"The Jiminny Team <no-reply@dev.jiminny.com>\",\"to\":\"lukas.kovalik@jiminny.com\",\"cc\":null} {\"correlation_id\":\"5fb3c8c8-3160-4964-ac56-02d6e2cb998c\",\"trace_id\":\"3a6d5e64-ecf2-4700-98cd-3edd56a1dc34\"}\n[2026-04-27 10:28:15] local.INFO: [SocialAccountService] Fetching token {\"socialAccountId\":1500,\"provider\":\"salesforce\"} {\"correlation_id\":\"5fb3c8c8-3160-4964-ac56-02d6e2cb998c\",\"trace_id\":\"3a6d5e64-ecf2-4700-98cd-3edd56a1dc34\"}\n[2026-04-27 10:28:15] local.INFO: [SocialAccountService] Token retrieved {\"socialAccountId\":1500,\"provider\":\"salesforce\"} {\"correlation_id\":\"5fb3c8c8-3160-4964-ac56-02d6e2cb998c\",\"trace_id\":\"3a6d5e64-ecf2-4700-98cd-3edd56a1dc34\"}\n[2026-04-27 10:28:15] local.INFO: [EncryptedTokenManager] Generating access token. {\"mode\":\"legacy\"} {\"correlation_id\":\"5fb3c8c8-3160-4964-ac56-02d6e2cb998c\",\"trace_id\":\"3a6d5e64-ecf2-4700-98cd-3edd56a1dc34\"}\n[2026-04-27 10:28:15] local.INFO: [CrmOwnerResolver] Integration owner matched as CRM Owner {\"crm_provider\":\"salesforce\",\"crm_owner\":143,\"team_id\":1} {\"correlation_id\":\"5fb3c8c8-3160-4964-ac56-02d6e2cb998c\",\"trace_id\":\"3a6d5e64-ecf2-4700-98cd-3edd56a1dc34\"}\n[2026-04-27 10:28:15] local.INFO: [EmailImport\\ParticipantsResolver] The sender email is blacklisted, skipping {\"email\":\"no-reply@dev.jiminny.com\",\"inbox_id\":212,\"message_provider_id\":\"19dce761cb049fba\",\"team_id\":1} {\"correlation_id\":\"5fb3c8c8-3160-4964-ac56-02d6e2cb998c\",\"trace_id\":\"3a6d5e64-ecf2-4700-98cd-3edd56a1dc34\"}\n[2026-04-27 10:28:15] local.INFO: [EmailImport\\ParticipantsValidator] Email participants are less than 2 {\"inbox_id\":212,\"message_provider_id\":\"19dce761cb049fba\",\"message_id\":\"<017f0e66-9989-446e-9788-7ae83a99137e@mtasv.net>\"} {\"correlation_id\":\"5fb3c8c8-3160-4964-ac56-02d6e2cb998c\",\"trace_id\":\"3a6d5e64-ecf2-4700-98cd-3edd56a1dc34\"}\n[2026-04-27 10:28:15] local.INFO: Processing an email from inbox batch {\"batch\":98446,\"inbox_id\":212,\"email\":\"lukas.kovalik@jiminny.com\",\"email_id\":\"19dce74ed398d2c1\",\"from\":\"\\\"sonarqubecloud[bot]\\\" <notifications@github.com>\",\"to\":\"\\\"jiminny/app\\\" <app@noreply.github.com>\",\"cc\":\"Lukas Kovalik <kovaliklukas@gmail.com>, Author <author@noreply.github.com>\"} {\"correlation_id\":\"5fb3c8c8-3160-4964-ac56-02d6e2cb998c\",\"trace_id\":\"3a6d5e64-ecf2-4700-98cd-3edd56a1dc34\"}\n[2026-04-27 10:28:15] local.INFO: [SocialAccountService] Fetching token {\"socialAccountId\":1500,\"provider\":\"salesforce\"} {\"correlation_id\":\"5fb3c8c8-3160-4964-ac56-02d6e2cb998c\",\"trace_id\":\"3a6d5e64-ecf2-4700-98cd-3edd56a1dc34\"}\n[2026-04-27 10:28:15] local.INFO: [SocialAccountService] Token retrieved {\"socialAccountId\":1500,\"provider\":\"salesforce\"} {\"correlation_id\":\"5fb3c8c8-3160-4964-ac56-02d6e2cb998c\",\"trace_id\":\"3a6d5e64-ecf2-4700-98cd-3edd56a1dc34\"}\n[2026-04-27 10:28:15] local.INFO: [EncryptedTokenManager] Generating access token. {\"mode\":\"legacy\"} {\"correlation_id\":\"5fb3c8c8-3160-4964-ac56-02d6e2cb998c\",\"trace_id\":\"3a6d5e64-ecf2-4700-98cd-3edd56a1dc34\"}\n[2026-04-27 10:28:15] local.INFO: [CrmOwnerResolver] Integration owner matched as CRM Owner {\"crm_provider\":\"salesforce\",\"crm_owner\":143,\"team_id\":1} {\"correlation_id\":\"5fb3c8c8-3160-4964-ac56-02d6e2cb998c\",\"trace_id\":\"3a6d5e64-ecf2-4700-98cd-3edd56a1dc34\"}\n[2026-04-27 10:28:15] local.INFO: [EmailImport\\ParticipantsResolver] The sender email is blacklisted, skipping {\"email\":\"notifications@github.com\",\"inbox_id\":212,\"message_provider_id\":\"19dce74ed398d2c1\",\"team_id\":1} {\"correlation_id\":\"5fb3c8c8-3160-4964-ac56-02d6e2cb998c\",\"trace_id\":\"3a6d5e64-ecf2-4700-98cd-3edd56a1dc34\"}\n[2026-04-27 10:28:15] local.INFO: [EmailImport\\ParticipantsValidator] Email participants are less than 2 {\"inbox_id\":212,\"message_provider_id\":\"19dce74ed398d2c1\",\"message_id\":\"<jiminny/app/pull/12016/c4326142661@github.com>\"} {\"correlation_id\":\"5fb3c8c8-3160-4964-ac56-02d6e2cb998c\",\"trace_id\":\"3a6d5e64-ecf2-4700-98cd-3edd56a1dc34\"}\n[2026-04-27 10:28:15] local.INFO: Processing an email from inbox batch {\"batch\":98446,\"inbox_id\":212,\"email\":\"lukas.kovalik@jiminny.com\",\"email_id\":\"19dce713dbecf41f\",\"from\":\"\\\"sonarqubecloud[bot]\\\" <notifications@github.com>\",\"to\":\"\\\"jiminny/infrastructure\\\" <infrastructure@noreply.github.com>\",\"cc\":\"Subscribed <subscribed@noreply.github.com>\"} {\"correlation_id\":\"5fb3c8c8-3160-4964-ac56-02d6e2cb998c\",\"trace_id\":\"3a6d5e64-ecf2-4700-98cd-3edd56a1dc34\"}\n[2026-04-27 10:28:15] local.INFO: [SocialAccountService] Fetching token {\"socialAccountId\":1500,\"provider\":\"salesforce\"} {\"correlation_id\":\"5fb3c8c8-3160-4964-ac56-02d6e2cb998c\",\"trace_id\":\"3a6d5e64-ecf2-4700-98cd-3edd56a1dc34\"}\n[2026-04-27 10:28:15] local.INFO: [SocialAccountService] Token retrieved {\"socialAccountId\":1500,\"provider\":\"salesforce\"} {\"correlation_id\":\"5fb3c8c8-3160-4964-ac56-02d6e2cb998c\",\"trace_id\":\"3a6d5e64-ecf2-4700-98cd-3edd56a1dc34\"}\n[2026-04-27 10:28:15] local.INFO: [EncryptedTokenManager] Generating access token. {\"mode\":\"legacy\"} {\"correlation_id\":\"5fb3c8c8-3160-4964-ac56-02d6e2cb998c\",\"trace_id\":\"3a6d5e64-ecf2-4700-98cd-3edd56a1dc34\"}\n[2026-04-27 10:28:15] local.INFO: [CrmOwnerResolver] Integration owner matched as CRM Owner {\"crm_provider\":\"salesforce\",\"crm_owner\":143,\"team_id\":1} {\"correlation_id\":\"5fb3c8c8-3160-4964-ac56-02d6e2cb998c\",\"trace_id\":\"3a6d5e64-ecf2-4700-98cd-3edd56a1dc34\"}\n[2026-04-27 10:28:15] local.INFO: [EmailImport\\ParticipantsResolver] The sender email is blacklisted, skipping {\"email\":\"notifications@github.com\",\"inbox_id\":212,\"message_provider_id\":\"19dce713dbecf41f\",\"team_id\":1} {\"correlation_id\":\"5fb3c8c8-3160-4964-ac56-02d6e2cb998c\",\"trace_id\":\"3a6d5e64-ecf2-4700-98cd-3edd56a1dc34\"}\n[2026-04-27 10:28:15] local.INFO: [EmailImport\\ParticipantsValidator] Email participants are less than 2 {\"inbox_id\":212,\"message_provider_id\":\"19dce713dbecf41f\",\"message_id\":\"<jiminny/infrastructure/pull/725/c4326108250@github.com>\"} {\"correlation_id\":\"5fb3c8c8-3160-4964-ac56-02d6e2cb998c\",\"trace_id\":\"3a6d5e64-ecf2-4700-98cd-3edd56a1dc34\"}\n[2026-04-27 10:28:15] local.INFO: Processing an email from inbox batch {\"batch\":98446,\"inbox_id\":212,\"email\":\"lukas.kovalik@jiminny.com\",\"email_id\":\"19dce708ed8b5acb\",\"from\":\"Veselin Kulov <notifications@github.com>\",\"to\":\"\\\"jiminny/infrastructure\\\" <infrastructure@noreply.github.com>\",\"cc\":\"Push <push@noreply.github.com>\"} {\"correlation_id\":\"5fb3c8c8-3160-4964-ac56-02d6e2cb998c\",\"trace_id\":\"3a6d5e64-ecf2-4700-98cd-3edd56a1dc34\"}\n[2026-04-27 10:28:15] local.INFO: [SocialAccountService] Fetching token {\"socialAccountId\":1500,\"provider\":\"salesforce\"} {\"correlation_id\":\"5fb3c8c8-3160-4964-ac56-02d6e2cb998c\",\"trace_id\":\"3a6d5e64-ecf2-4700-98cd-3edd56a1dc34\"}\n[2026-04-27 10:28:15] local.INFO: [SocialAccountService] Token retrieved {\"socialAccountId\":1500,\"provider\":\"salesforce\"} {\"correlation_id\":\"5fb3c8c8-3160-4964-ac56-02d6e2cb998c\",\"trace_id\":\"3a6d5e64-ecf2-4700-98cd-3edd56a1dc34\"}\n[2026-04-27 10:28:15] local.INFO: [EncryptedTokenManager] Generating access token. {\"mode\":\"legacy\"} {\"correlation_id\":\"5fb3c8c8-3160-4964-ac56-02d6e2cb998c\",\"trace_id\":\"3a6d5e64-ecf2-4700-98cd-3edd56a1dc34\"}\n[2026-04-27 10:28:15] local.INFO: [CrmOwnerResolver] Integration owner matched as CRM Owner {\"crm_provider\":\"salesforce\",\"crm_owner\":143,\"team_id\":1} {\"correlation_id\":\"5fb3c8c8-3160-4964-ac56-02d6e2cb998c\",\"trace_id\":\"3a6d5e64-ecf2-4700-98cd-3edd56a1dc34\"}\n[2026-04-27 10:28:15] local.INFO: [EmailImport\\ParticipantsResolver] The sender email is blacklisted, skipping {\"email\":\"notifications@github.com\",\"inbox_id\":212,\"message_provider_id\":\"19dce708ed8b5acb\",\"team_id\":1} {\"correlation_id\":\"5fb3c8c8-3160-4964-ac56-02d6e2cb998c\",\"trace_id\":\"3a6d5e64-ecf2-4700-98cd-3edd56a1dc34\"}\n[2026-04-27 10:28:15] local.INFO: [EmailImport\\ParticipantsValidator] Email participants are less than 2 {\"inbox_id\":212,\"message_provider_id\":\"19dce708ed8b5acb\",\"message_id\":\"<jiminny/infrastructure/pull/725/before/186952cc584428fcca17aa364f6cc535d65e804a/after/078246a06b25fb93ad61c7f14472ecb755483f4d@github.com>\"} {\"correlation_id\":\"5fb3c8c8-3160-4964-ac56-02d6e2cb998c\",\"trace_id\":\"3a6d5e64-ecf2-4700-98cd-3edd56a1dc34\"}\n[2026-04-27 10:28:15] local.INFO: Processing an email from inbox batch {\"batch\":98446,\"inbox_id\":212,\"email\":\"lukas.kovalik@jiminny.com\",\"email_id\":\"19dce6eea657f2fa\",\"from\":\"\\\"sonarqubecloud[bot]\\\" <notifications@github.com>\",\"to\":\"\\\"jiminny/app\\\" <app@noreply.github.com>\",\"cc\":\"Subscribed <subscribed@noreply.github.com>\"} {\"correlation_id\":\"5fb3c8c8-3160-4964-ac56-02d6e2cb998c\",\"trace_id\":\"3a6d5e64-ecf2-4700-98cd-3edd56a1dc34\"}\n[2026-04-27 10:28:15] local.INFO: [SocialAccountService] Fetching token {\"socialAccountId\":1500,\"provider\":\"salesforce\"} {\"correlation_id\":\"5fb3c8c8-3160-4964-ac56-02d6e2cb998c\",\"trace_id\":\"3a6d5e64-ecf2-4700-98cd-3edd56a1dc34\"}\n[2026-04-27 10:28:15] local.INFO: [SocialAccountService] Token retrieved {\"socialAccountId\":1500,\"provider\":\"salesforce\"} {\"correlation_id\":\"5fb3c8c8-3160-4964-ac56-02d6e2cb998c\",\"trace_id\":\"3a6d5e64-ecf2-4700-98cd-3edd56a1dc34\"}\n[2026-04-27 10:28:15] local.INFO: [EncryptedTokenManager] Generating access token. {\"mode\":\"legacy\"} {\"correlation_id\":\"5fb3c8c8-3160-4964-ac56-02d6e2cb998c\",\"trace_id\":\"3a6d5e64-ecf2-4700-98cd-3edd56a1dc34\"}\n[2026-04-27 10:28:15] local.INFO: [CrmOwnerResolver] Integration owner matched as CRM Owner {\"crm_provider\":\"salesforce\",\"crm_owner\":143,\"team_id\":1} {\"correlation_id\":\"5fb3c8c8-3160-4964-ac56-02d6e2cb998c\",\"trace_id\":\"3a6d5e64-ecf2-4700-98cd-3edd56a1dc34\"}\n[2026-04-27 10:28:15] local.INFO: [EmailImport\\ParticipantsResolver] The sender email is blacklisted, skipping {\"email\":\"notifications@github.com\",\"inbox_id\":212,\"message_provider_id\":\"19dce6eea657f2fa\",\"team_id\":1} {\"correlation_id\":\"5fb3c8c8-3160-4964-ac56-02d6e2cb998c\",\"trace_id\":\"3a6d5e64-ecf2-4700-98cd-3edd56a1dc34\"}\n[2026-04-27 10:28:15] local.INFO: [EmailImport\\ParticipantsValidator] Email participants are less than 2 {\"inbox_id\":212,\"message_provider_id\":\"19dce6eea657f2fa\",\"message_id\":\"<jiminny/app/pull/11998/c4326088534@github.com>\"} {\"correlation_id\":\"5fb3c8c8-3160-4964-ac56-02d6e2cb998c\",\"trace_id\":\"3a6d5e64-ecf2-4700-98cd-3edd56a1dc34\"}\n[2026-04-27 10:28:15] local.INFO: Deleting successfully processed batch 98446 for inbox 212 {\"correlation_id\":\"5fb3c8c8-3160-4964-ac56-02d6e2cb998c\",\"trace_id\":\"3a6d5e64-ecf2-4700-98cd-3edd56a1dc34\"}\n[2026-04-27 10:28:15] local.INFO: [EmailSchedule] FINISHED batch process {\"host\":\"docker_lamp_1\",\"processed\":1} {\"correlation_id\":\"5fb3c8c8-3160-4964-ac56-02d6e2cb998c\",\"trace_id\":\"3a6d5e64-ecf2-4700-98cd-3edd56a1dc34\"}\n[2026-04-27 10:28:15] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage for command {\"command\":\"mailbox:batch:process\",\"memoryBeforeCommandInMb\":60.0,\"memoryAfterCommandInMB\":62.0,\"memoryPeakBeforeCommandInMb\":99.727,\"memoryPeakAfterCommandInMB\":99.727} {\"correlation_id\":\"5fb3c8c8-3160-4964-ac56-02d6e2cb998c\",\"trace_id\":\"3a6d5e64-ecf2-4700-98cd-3edd56a1dc34\"}\n[2026-04-27 10:28:16] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage before starting command {\"command\":\"conference:monitor:count\",\"memoryBeforeCommandInMb\":60.0,\"memoryPeakBeforeCommandInMb\":99.727} {\"correlation_id\":\"ef5b3e1a-a06a-4527-9120-b2adfe4e1846\",\"trace_id\":\"d932d64a-3f63-4aca-8abf-bb13e0892cba\"}\n[2026-04-27 10:28:16] local.INFO: Running conference:monitor:count command for activities in (2026-04-27 10:26:00, 2026-04-27 10:28:00] {\"correlation_id\":\"ef5b3e1a-a06a-4527-9120-b2adfe4e1846\",\"trace_id\":\"d932d64a-3f63-4aca-8abf-bb13e0892cba\"}\n[2026-04-27 10:28:16] local.INFO: [conference:monitor:count] No activities found in (2026-04-27 10:26:00, 2026-04-27 10:28:00] {\"correlation_id\":\"ef5b3e1a-a06a-4527-9120-b2adfe4e1846\",\"trace_id\":\"d932d64a-3f63-4aca-8abf-bb13e0892cba\"}\n[2026-04-27 10:28:16] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage for command {\"command\":\"conference:monitor:count\",\"memoryBeforeCommandInMb\":60.0,\"memoryAfterCommandInMB\":62.0,\"memoryPeakBeforeCommandInMb\":99.727,\"memoryPeakAfterCommandInMB\":99.727} {\"correlation_id\":\"ef5b3e1a-a06a-4527-9120-b2adfe4e1846\",\"trace_id\":\"d932d64a-3f63-4aca-8abf-bb13e0892cba\"}\n[2026-04-27 10:28:17] local.ERROR: Target class [Jiminny\\Repositories\\AjReportsRepository] does not exist. {\"exception\":\"[object] (Illuminate\\\\Contracts\\\\Container\\\\BindingResolutionException(code: 0): Target class [Jiminny\\\\Repositories\\\\AjReportsRepository] does not exist. at /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php:1124)\n[stacktrace]\n#0 /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php(933): Illuminate\\\\Container\\\\Container->build('Jiminny\\\\\\\\Reposit...')\n#1 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Application.php(1078): Illuminate\\\\Container\\\\Container->resolve('Jiminny\\\\\\\\Reposit...', Array, true)\n#2 /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php(864): Illuminate\\\\Foundation\\\\Application->resolve('Jiminny\\\\\\\\Reposit...', Array)\n#3 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Application.php(1058): Illuminate\\\\Container\\\\Container->make('Jiminny\\\\\\\\Reposit...', Array)\n#4 /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php(1336): Illuminate\\\\Foundation\\\\Application->make('Jiminny\\\\\\\\Reposit...')\n#5 /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php(1237): Illuminate\\\\Container\\\\Container->resolveClass(Object(ReflectionParameter))\n#6 /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php(1162): Illuminate\\\\Container\\\\Container->resolveDependencies(Array)\n#7 /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php(933): Illuminate\\\\Container\\\\Container->build('Jiminny\\\\\\\\Http\\\\\\\\Co...')\n#8 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Application.php(1078): Illuminate\\\\Container\\\\Container->resolve('Jiminny\\\\\\\\Http\\\\\\\\Co...', Array, true)\n#9 /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php(864): Illuminate\\\\Foundation\\\\Application->resolve('Jiminny\\\\\\\\Http\\\\\\\\Co...', Array)\n#10 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Application.php(1058): Illuminate\\\\Container\\\\Container->make('Jiminny\\\\\\\\Http\\\\\\\\Co...', Array)\n#11 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Route.php(286): Illuminate\\\\Foundation\\\\Application->make('Jiminny\\\\\\\\Http\\\\\\\\Co...')\n#12 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Route.php(1133): Illuminate\\\\Routing\\\\Route->getController()\n#13 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Route.php(1062): Illuminate\\\\Routing\\\\Route->controllerMiddleware()\n#14 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Router.php(834): Illuminate\\\\Routing\\\\Route->gatherMiddleware()\n#15 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Router.php(816): Illuminate\\\\Routing\\\\Router->gatherRouteMiddleware(Object(Illuminate\\\\Routing\\\\Route))\n#16 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Router.php(800): Illuminate\\\\Routing\\\\Router->runRouteWithinStack(Object(Illuminate\\\\Routing\\\\Route), Object(Illuminate\\\\Http\\\\Request))\n#17 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Router.php(764): Illuminate\\\\Routing\\\\Router->runRoute(Object(Illuminate\\\\Http\\\\Request), Object(Illuminate\\\\Routing\\\\Route))\n#18 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Router.php(753): Illuminate\\\\Routing\\\\Router->dispatchToRoute(Object(Illuminate\\\\Http\\\\Request))\n#19 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Http/Kernel.php(200): Illuminate\\\\Routing\\\\Router->dispatch(Object(Illuminate\\\\Http\\\\Request))\n#20 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(180): Illuminate\\\\Foundation\\\\Http\\\\Kernel->Illuminate\\\\Foundation\\\\Http\\\\{closure}(Object(Illuminate\\\\Http\\\\Request))\n#21 /home/jiminny/vendor/barryvdh/laravel-debugbar/src/Middleware/InjectDebugbar.php(59): Illuminate\\\\Pipeline\\\\Pipeline->Illuminate\\\\Pipeline\\\\{closure}(Object(Illuminate\\\\Http\\\\Request))\n#22 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(219): Barryvdh\\\\Debugbar\\\\Middleware\\\\InjectDebugbar->handle(Object(Illuminate\\\\Http\\\\Request), Object(Closure))\n#23 /home/jiminny/vendor/laravel/framework/src/Illuminate/Http/Middleware/HandleCors.php(74): Illuminate\\\\Pipeline\\\\Pipeline->Illuminate\\\\Pipeline\\\\{closure}(Object(Illuminate\\\\Http\\\\Request))\n#24 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(219): Illuminate\\\\Http\\\\Middleware\\\\HandleCors->handle(Object(Illuminate\\\\Http\\\\Request), Object(Closure))\n#25 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Http/Middleware/TransformsRequest.php(21): Illuminate\\\\Pipeline\\\\Pipeline->Illuminate\\\\Pipeline\\\\{closure}(Object(Illuminate\\\\Http\\\\Request))\n#26 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Http/Middleware/TrimStrings.php(51): Illuminate\\\\Foundation\\\\Http\\\\Middleware\\\\TransformsRequest->handle(Object(Illuminate\\\\Http\\\\Request), Object(Closure))\n#27 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(219): Illuminate\\\\Foundation\\\\Http\\\\Middleware\\\\TrimStrings->handle(Object(Illuminate\\\\Http\\\\Request), Object(Closure))\n#28 /home/jiminny/vendor/bepsvpt/secure-headers/src/SecureHeadersMiddleware.php(18): Illuminate\\\\Pipeline\\\\Pipeline->Illuminate\\\\Pipeline\\\\{closure}(Object(Illuminate\\\\Http\\\\Request))\n#29 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(219): Bepsvpt\\\\SecureHeaders\\\\SecureHeadersMiddleware->handle(Object(Illuminate\\\\Http\\\\Request), Object(Closure))\n#30 /home/jiminny/app/Http/Middleware/SentryContext.php(60): Illuminate\\\\Pipeline\\\\Pipeline->Illuminate\\\\Pipeline\\\\{closure}(Object(Illuminate\\\\Http\\\\Request))\n#31 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(219): Jiminny\\\\Http\\\\Middleware\\\\SentryContext->handle(Object(Illuminate\\\\Http\\\\Request), Object(Closure))\n#32 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Http/Middleware/PreventRequestsDuringMaintenance.php(109): Illuminate\\\\Pipeline\\\\Pipeline->Illuminate\\\\Pipeline\\\\{closure}(Object(Illuminate\\\\Http\\\\Request))\n#33 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(219): Illuminate\\\\Foundation\\\\Http\\\\Middleware\\\\PreventRequestsDuringMaintenance->handle(Object(Illuminate\\\\Http\\\\Request), Object(Closure))\n#34 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Http/Middleware/InvokeDeferredCallbacks.php(22): Illuminate\\\\Pipeline\\\\Pipeline->Illuminate\\\\Pipeline\\\\{closure}(Object(Illuminate\\\\Http\\\\Request))\n#35 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(219): Illuminate\\\\Foundation\\\\Http\\\\Middleware\\\\InvokeDeferredCallbacks->handle(Object(Illuminate\\\\Http\\\\Request), Object(Closure))\n#36 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(137): Illuminate\\\\Pipeline\\\\Pipeline->Illuminate\\\\Pipeline\\\\{closure}(Object(Illuminate\\\\Http\\\\Request))\n#37 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Http/Kernel.php(175): Illuminate\\\\Pipeline\\\\Pipeline->then(Object(Closure))\n#38 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Http/Kernel.php(144): Illuminate\\\\Foundation\\\\Http\\\\Kernel->sendRequestThroughRouter(Object(Illuminate\\\\Http\\\\Request))\n#39 /home/jiminny/public/index.php(51): Illuminate\\\\Foundation\\\\Http\\\\Kernel->handle(Object(Illuminate\\\\Http\\\\Request))\n#40 {main}\n\n[previous exception] [object] (ReflectionException(code: -1): Class \\\"Jiminny\\\\Repositories\\\\AjReportsRepository\\\" does not exist at /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php:1122)\n[stacktrace]\n#0 /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php(1122): ReflectionClass->__construct('Jiminny\\\\\\\\Reposit...')\n#1 /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php(933): Illuminate\\\\Container\\\\Container->build('Jiminny\\\\\\\\Reposit...')\n#2 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Application.php(1078): Illuminate\\\\Container\\\\Container->resolve('Jiminny\\\\\\\\Reposit...', Array, true)\n#3 /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php(864): Illuminate\\\\Foundation\\\\Application->resolve('Jiminny\\\\\\\\Reposit...', Array)\n#4 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Application.php(1058): Illuminate\\\\Container\\\\Container->make('Jiminny\\\\\\\\Reposit...', Array)\n#5 /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php(1336): Illuminate\\\\Foundation\\\\Application->make('Jiminny\\\\\\\\Reposit...')\n#6 /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php(1237): Illuminate\\\\Container\\\\Container->resolveClass(Object(ReflectionParameter))\n#7 /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php(1162): Illuminate\\\\Container\\\\Container->resolveDependencies(Array)\n#8 /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php(933): Illuminate\\\\Container\\\\Container->build('Jiminny\\\\\\\\Http\\\\\\\\Co...')\n#9 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Application.php(1078): Illuminate\\\\Container\\\\Container->resolve('Jiminny\\\\\\\\Http\\\\\\\\Co...', Array, true)\n#10 /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php(864): Illuminate\\\\Foundation\\\\Application->resolve('Jiminny\\\\\\\\Http\\\\\\\\Co...', Array)\n#11 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Application.php(1058): Illuminate\\\\Container\\\\Container->make('Jiminny\\\\\\\\Http\\\\\\\\Co...', Array)\n#12 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Route.php(286): Illuminate\\\\Foundation\\\\Application->make('Jiminny\\\\\\\\Http\\\\\\\\Co...')\n#13 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Route.php(1133): Illuminate\\\\Routing\\\\Route->getController()\n#14 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Route.php(1062): Illuminate\\\\Routing\\\\Route->controllerMiddleware()\n#15 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Router.php(834): Illuminate\\\\Routing\\\\Route->gatherMiddleware()\n#16 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Router.php(816): Illuminate\\\\Routing\\\\Router->gatherRouteMiddleware(Object(Illuminate\\\\Routing\\\\Route))\n#17 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Router.php(800): Illuminate\\\\Routing\\\\Router->runRouteWithinStack(Object(Illuminate\\\\Routing\\\\Route), Object(Illuminate\\\\Http\\\\Request))\n#18 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Router.php(764): Illuminate\\\\Routing\\\\Router->runRoute(Object(Illuminate\\\\Http\\\\Request), Object(Illuminate\\\\Routing\\\\Route))\n#19 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Router.php(753): Illuminate\\\\Routing\\\\Router->dispatchToRoute(Object(Illuminate\\\\Http\\\\Request))\n#20 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Http/Kernel.php(200): Illuminate\\\\Routing\\\\Router->dispatch(Object(Illuminate\\\\Http\\\\Request))\n#21 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(180): Illuminate\\\\Foundation\\\\Http\\\\Kernel->Illuminate\\\\Foundation\\\\Http\\\\{closure}(Object(Illuminate\\\\Http\\\\Request))\n#22 /home/jiminny/vendor/barryvdh/laravel-debugbar/src/Middleware/InjectDebugbar.php(59): Illuminate\\\\Pipeline\\\\Pipeline->Illuminate\\\\Pipeline\\\\{closure}(Object(Illuminate\\\\Http\\\\Request))\n#23 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(219): Barryvdh\\\\Debugbar\\\\Middleware\\\\InjectDebugbar->handle(Object(Illuminate\\\\Http\\\\Request), Object(Closure))\n#24 /home/jiminny/vendor/laravel/framework/src/Illuminate/Http/Middleware/HandleCors.php(74): Illuminate\\\\Pipeline\\\\Pipeline->Illuminate\\\\Pipeline\\\\{closure}(Object(Illuminate\\\\Http\\\\Request))\n#25 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(219): Illuminate\\\\Http\\\\Middleware\\\\HandleCors->handle(Object(Illuminate\\\\Http\\\\Request), Object(Closure))\n#26 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Http/Middleware/TransformsRequest.php(21): Illuminate\\\\Pipeline\\\\Pipeline->Illuminate\\\\Pipeline\\\\{closure}(Object(Illuminate\\\\Http\\\\Request))\n#27 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Http/Middleware/TrimStrings.php(51): Illuminate\\\\Foundation\\\\Http\\\\Middleware\\\\TransformsRequest->handle(Object(Illuminate\\\\Http\\\\Request), Object(Closure))\n#28 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(219): Illuminate\\\\Foundation\\\\Http\\\\Middleware\\\\TrimStrings->handle(Object(Illuminate\\\\Http\\\\Request), Object(Closure))\n#29 /home/jiminny/vendor/bepsvpt/secure-headers/src/SecureHeadersMiddleware.php(18): Illuminate\\\\Pipeline\\\\Pipeline->Illuminate\\\\Pipeline\\\\{closure}(Object(Illuminate\\\\Http\\\\Request))\n#30 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(219): Bepsvpt\\\\SecureHeaders\\\\SecureHeadersMiddleware->handle(Object(Illuminate\\\\Http\\\\Request), Object(Closure))\n#31 /home/jiminny/app/Http/Middleware/SentryContext.php(60): Illuminate\\\\Pipeline\\\\Pipeline->Illuminate\\\\Pipeline\\\\{closure}(Object(Illuminate\\\\Http\\\\Request))\n#32 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(219): Jiminny\\\\Http\\\\Middleware\\\\SentryContext->handle(Object(Illuminate\\\\Http\\\\Request), Object(Closure))\n#33 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Http/Middleware/PreventRequestsDuringMaintenance.php(109): Illuminate\\\\Pipeline\\\\Pipeline->Illuminate\\\\Pipeline\\\\{closure}(Object(Illuminate\\\\Http\\\\Request))\n#34 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(219): Illuminate\\\\Foundation\\\\Http\\\\Middleware\\\\PreventRequestsDuringMaintenance->handle(Object(Illuminate\\\\Http\\\\Request), Object(Closure))\n#35 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Http/Middleware/InvokeDeferredCallbacks.php(22): Illuminate\\\\Pipeline\\\\Pipeline->Illuminate\\\\Pipeline\\\\{closure}(Object(Illuminate\\\\Http\\\\Request))\n#36 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(219): Illuminate\\\\Foundation\\\\Http\\\\Middleware\\\\InvokeDeferredCallbacks->handle(Object(Illuminate\\\\Http\\\\Request), Object(Closure))\n#37 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(137): Illuminate\\\\Pipeline\\\\Pipeline->Illuminate\\\\Pipeline\\\\{closure}(Object(Illuminate\\\\Http\\\\Request))\n#38 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Http/Kernel.php(175): Illuminate\\\\Pipeline\\\\Pipeline->then(Object(Closure))\n#39 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Http/Kernel.php(144): Illuminate\\\\Foundation\\\\Http\\\\Kernel->sendRequestThroughRouter(Object(Illuminate\\\\Http\\\\Request))\n#40 /home/jiminny/public/index.php(51): Illuminate\\\\Foundation\\\\Http\\\\Kernel->handle(Object(Illuminate\\\\Http\\\\Request))\n#41 {main}\n\"} {\"correlation_id\":\"46b082fc-5a28-4cc4-8bd1-c670f8662e7f\",\"trace_id\":\"8ae7dc26-61c7-42e4-8617-0b85f53e54c1\"}\n[2026-04-27 10:28:19] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage before starting command {\"command\":\"calendar:sync\",\"memoryBeforeCommandInMb\":60.0,\"memoryPeakBeforeCommandInMb\":99.727} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:19] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage before starting command {\"command\":\"mailbox:batch:retry-failed\",\"memoryBeforeCommandInMb\":60.0,\"memoryPeakBeforeCommandInMb\":99.727} {\"correlation_id\":\"e155562a-84a1-45f5-8897-1779f96886f6\",\"trace_id\":\"12141267-1032-479b-aab5-e17e133007d9\"}\n[2026-04-27 10:28:19] local.NOTICE: Calendar sync start {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:19] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage for command {\"command\":\"mailbox:batch:retry-failed\",\"memoryBeforeCommandInMb\":60.0,\"memoryAfterCommandInMB\":62.0,\"memoryPeakBeforeCommandInMb\":99.727,\"memoryPeakAfterCommandInMB\":99.727} {\"correlation_id\":\"e155562a-84a1-45f5-8897-1779f96886f6\",\"trace_id\":\"12141267-1032-479b-aab5-e17e133007d9\"}\n[2026-04-27 10:28:19] local.INFO: [SocialAccountService] Fetching token {\"socialAccountId\":1393,\"provider\":\"google\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:19] local.INFO: [SocialAccountService] Token needs refreshing {\"socialAccountId\":1393,\"provider\":\"google\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:19] local.INFO: [EncryptedTokenManager] Generating access token. {\"mode\":\"legacy\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:19] local.INFO: [SocialAccountService] Refreshing token from provider {\"socialAccountId\":1393,\"provider\":\"google\",\"refreshToken\":\"5aa7e2d96b53201cd16fca5d2e4ef3ad03320971fc064781d18aee3ae7b99fbf\",\"state\":\"full-refresh\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:20] local.ERROR: [SocialAccountService] Failed to refresh token {\"socialAccountId\":1393,\"provider\":\"google\",\"responseBody\":{\"error\":\"invalid_grant\",\"error_description\":\"Account has been deleted\"}} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:20] local.INFO: [SocialAccountObserver] Saving model {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:20] local.ERROR: [SocialAccountService] Failed to refresh token {\"socialAccountId\":1393,\"provider\":\"google\",\"reason\":\"Flow refresh required.\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:20] local.INFO: [SocialAccountService] Fetching token {\"socialAccountId\":1387,\"provider\":\"google\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:20] local.INFO: [SocialAccountService] Token needs refreshing {\"socialAccountId\":1387,\"provider\":\"google\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:20] local.INFO: [EncryptedTokenManager] Generating access token. {\"mode\":\"legacy\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:20] local.INFO: [SocialAccountService] Refreshing token from provider {\"socialAccountId\":1387,\"provider\":\"google\",\"refreshToken\":\"8157ac6de94842937194009e9c50e459253600f799dacf6a40755ffdbeb5bba6\",\"state\":\"full-refresh\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:21] local.ERROR: [SocialAccountService] Failed to refresh token {\"socialAccountId\":1387,\"provider\":\"google\",\"responseBody\":{\"error\":\"invalid_grant\",\"error_description\":\"Account has been deleted\"}} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:21] local.INFO: [SocialAccountObserver] Saving model {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:21] local.ERROR: [SocialAccountService] Failed to refresh token {\"socialAccountId\":1387,\"provider\":\"google\",\"reason\":\"Flow refresh required.\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:21] local.INFO: [SocialAccountService] Fetching token {\"socialAccountId\":1348,\"provider\":\"google\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:21] local.INFO: [SocialAccountService] Token needs refreshing {\"socialAccountId\":1348,\"provider\":\"google\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:21] local.INFO: [EncryptedTokenManager] Generating access token. {\"mode\":\"legacy\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:21] local.INFO: [SocialAccountService] Refreshing token from provider {\"socialAccountId\":1348,\"provider\":\"google\",\"refreshToken\":\"9e7d13d3032d0cb1b79d8e95aef01383e8e91eb52ff8ee960c8a0b6b95cd8c73\",\"state\":\"full-refresh\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:21] local.ERROR: [SocialAccountService] Failed to refresh token {\"socialAccountId\":1348,\"provider\":\"google\",\"responseBody\":{\"error\":\"invalid_grant\",\"error_description\":\"Bad Request\"}} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:21] local.INFO: [SocialAccountObserver] Saving model {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:21] local.ERROR: [SocialAccountService] Failed to refresh token {\"socialAccountId\":1348,\"provider\":\"google\",\"reason\":\"Flow refresh required.\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:21] local.INFO: [SocialAccountService] Fetching token {\"socialAccountId\":1361,\"provider\":\"google\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:21] local.INFO: [SocialAccountService] Token needs refreshing {\"socialAccountId\":1361,\"provider\":\"google\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:21] local.INFO: [EncryptedTokenManager] Generating access token. {\"mode\":\"legacy\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:21] local.INFO: [SocialAccountService] Refreshing token from provider {\"socialAccountId\":1361,\"provider\":\"google\",\"refreshToken\":\"6c843da199c2b9907445329304fcc4ec5057a4ee748d8299641764395c08e1fd\",\"state\":\"full-refresh\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:22] local.ERROR: [SocialAccountService] Failed to refresh token {\"socialAccountId\":1361,\"provider\":\"google\",\"responseBody\":{\"error\":\"invalid_grant\",\"error_description\":\"Account has been deleted\"}} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:22] local.INFO: [SocialAccountObserver] Saving model {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:22] local.ERROR: [SocialAccountService] Failed to refresh token {\"socialAccountId\":1361,\"provider\":\"google\",\"reason\":\"Flow refresh required.\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:22] local.INFO: [SocialAccountService] Fetching token {\"socialAccountId\":1310,\"provider\":\"google\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:22] local.INFO: [SocialAccountService] Token needs refreshing {\"socialAccountId\":1310,\"provider\":\"google\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:22] local.INFO: [EncryptedTokenManager] Generating access token. {\"mode\":\"legacy\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:22] local.INFO: [SocialAccountService] Refreshing token from provider {\"socialAccountId\":1310,\"provider\":\"google\",\"refreshToken\":\"e34818922c2830a660813a63f6169a4a9a992ae2cccd7dc8dd7796cfdb470ef1\",\"state\":\"full-refresh\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:23] local.ERROR: [SocialAccountService] Failed to refresh token {\"socialAccountId\":1310,\"provider\":\"google\",\"responseBody\":{\"error\":\"invalid_grant\",\"error_description\":\"Bad Request\"}} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:23] local.INFO: [SocialAccountObserver] Saving model {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:23] local.ERROR: [SocialAccountService] Failed to refresh token {\"socialAccountId\":1310,\"provider\":\"google\",\"reason\":\"Flow refresh required.\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:23] local.INFO: [SocialAccountService] Fetching token {\"socialAccountId\":1333,\"provider\":\"google\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:23] local.INFO: [SocialAccountService] Token needs refreshing {\"socialAccountId\":1333,\"provider\":\"google\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:23] local.INFO: [EncryptedTokenManager] Generating access token. {\"mode\":\"legacy\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:23] local.INFO: [SocialAccountService] Refreshing token from provider {\"socialAccountId\":1333,\"provider\":\"google\",\"refreshToken\":\"6c902986546d8e8da1dc539b046cdc1d458f519acc972e5b5f1d6a1a295165e0\",\"state\":\"full-refresh\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:23] local.ERROR: [SocialAccountService] Failed to refresh token {\"socialAccountId\":1333,\"provider\":\"google\",\"responseBody\":{\"error\":\"unauthorized_client\",\"error_description\":\"Unauthorized\"}} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:23] local.INFO: [SocialAccountObserver] Saving model {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:23] local.ERROR: [SocialAccountService] Failed to refresh token {\"socialAccountId\":1333,\"provider\":\"google\",\"reason\":\"Flow refresh required.\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:24] local.INFO: [SocialAccountService] Fetching token {\"socialAccountId\":1368,\"provider\":\"google\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:24] local.INFO: [SocialAccountService] Token needs refreshing {\"socialAccountId\":1368,\"provider\":\"google\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:24] local.INFO: [EncryptedTokenManager] Generating access token. {\"mode\":\"legacy\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:24] local.INFO: [SocialAccountService] Refreshing token from provider {\"socialAccountId\":1368,\"provider\":\"google\",\"refreshToken\":\"d2f128898ff8543bd16b69cfae37896ab85119b0f5ed2b431d739593bb600333\",\"state\":\"full-refresh\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:24] local.ERROR: [SocialAccountService] Failed to refresh token {\"socialAccountId\":1368,\"provider\":\"google\",\"responseBody\":{\"error\":\"invalid_grant\",\"error_description\":\"Bad Request\"}} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:24] local.INFO: [SocialAccountObserver] Saving model {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:24] local.ERROR: [SocialAccountService] Failed to refresh token {\"socialAccountId\":1368,\"provider\":\"google\",\"reason\":\"Flow refresh required.\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:24] local.INFO: [SocialAccountService] Fetching token {\"socialAccountId\":1365,\"provider\":\"google\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:24] local.INFO: [SocialAccountService] Token needs refreshing {\"socialAccountId\":1365,\"provider\":\"google\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:24] local.INFO: [EncryptedTokenManager] Generating access token. {\"mode\":\"legacy\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:24] local.INFO: [SocialAccountService] Refreshing token from provider {\"socialAccountId\":1365,\"provider\":\"google\",\"refreshToken\":\"7676e4a9afcd082b413248ab5ec6e487021fec6a9bdf315860a59cefad9caad8\",\"state\":\"full-refresh\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:25] local.ERROR: [SocialAccountService] Failed to refresh token {\"socialAccountId\":1365,\"provider\":\"google\",\"responseBody\":{\"error\":\"unauthorized_client\",\"error_description\":\"Unauthorized\"}} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:25] local.INFO: [SocialAccountObserver] Saving model {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:25] local.ERROR: [SocialAccountService] Failed to refresh token {\"socialAccountId\":1365,\"provider\":\"google\",\"reason\":\"Flow refresh required.\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:25] local.INFO: [SocialAccountService] Fetching token {\"socialAccountId\":1364,\"provider\":\"google\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:25] local.INFO: [SocialAccountService] Token needs refreshing {\"socialAccountId\":1364,\"provider\":\"google\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:25] local.INFO: [EncryptedTokenManager] Generating access token. {\"mode\":\"legacy\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:25] local.INFO: [SocialAccountService] Refreshing token from provider {\"socialAccountId\":1364,\"provider\":\"google\",\"refreshToken\":\"dd5882ebce76e645292ce33ae74238abbb77c0a4ecc6a2bfe723cad82e72ba8e\",\"state\":\"full-refresh\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:25] local.ERROR: [SocialAccountService] Failed to refresh token {\"socialAccountId\":1364,\"provider\":\"google\",\"responseBody\":{\"error\":\"unauthorized_client\",\"error_description\":\"Unauthorized\"}} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:25] local.INFO: [SocialAccountObserver] Saving model {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:25] local.ERROR: [SocialAccountService] Failed to refresh token {\"socialAccountId\":1364,\"provider\":\"google\",\"reason\":\"Flow refresh required.\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:25] local.INFO: [SocialAccountService] Fetching token {\"socialAccountId\":1370,\"provider\":\"office\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:25] local.INFO: [SocialAccountService] Token needs refreshing {\"socialAccountId\":1370,\"provider\":\"office\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:25] local.INFO: [EncryptedTokenManager] Generating access token. {\"mode\":\"legacy\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:25] local.INFO: [SocialAccountService] Refreshing token from provider {\"socialAccountId\":1370,\"provider\":\"office\",\"refreshToken\":\"b7ee8035306d0043cea6e00e7c4fe14f745e44074a1194db62a31cdf8b70af3e\",\"state\":\"full-refresh\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:26] local.ERROR: [SocialAccountService] Failed to refresh token {\"socialAccountId\":1370,\"provider\":\"office\",\"responseBody\":\"{\\\"error\\\":\\\"invalid_client\\\",\\\"error_description\\\":\\\"AADSTS7000215: Invalid client secret provided. Ensure the secret being sent in the request is the client secret value, not the client secret ID, for a secret added to app 'bbcbb2ef-6200-4fae-82bd-d81f5dd738da'. Trace ID: 2be54cfa-1691-4388-a284-754fdf414400 Correlation ID: 9a934049-899b-45c5-99a5-15c9c470d8a2 Timestamp: 2026-04-27 10:28:26Z\\\",\\\"error_codes\\\":[7000215],\\\"timestamp\\\":\\\"2026-04-27 10:28:26Z\\\",\\\"trace_id\\\":\\\"2be54cfa-1691-4388-a284-754fdf414400\\\",\\\"correlation_id\\\":\\\"9a934049-899b-45c5-99a5-15c9c470d8a2\\\",\\\"error_uri\\\":\\\"https://login.microsoftonline.com/error?code=7000215\\\"}\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:26] local.INFO: [SocialAccountObserver] Saving model {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:26] local.ERROR: [SocialAccountService] Failed to refresh token {\"socialAccountId\":1370,\"provider\":\"office\",\"reason\":\"Flow refresh required.\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:26] local.INFO: [SocialAccountService] Fetching token {\"socialAccountId\":1202,\"provider\":\"office\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:26] local.INFO: [SocialAccountService] Token needs refreshing {\"socialAccountId\":1202,\"provider\":\"office\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:26] local.INFO: [EncryptedTokenManager] Generating access token. {\"mode\":\"legacy\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:26] local.INFO: [SocialAccountService] Refreshing token from provider {\"socialAccountId\":1202,\"provider\":\"office\",\"refreshToken\":\"b458799ccc29b21a6e2eb5260fdb63e49ccba21bf942a3973fb63799bd7f0afe\",\"state\":\"full-refresh\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:26] local.ERROR: [SocialAccountService] Failed to refresh token {\"socialAccountId\":1202,\"provider\":\"office\",\"responseBody\":\"{\\\"error\\\":\\\"invalid_client\\\",\\\"error_description\\\":\\\"AADSTS7000215: Invalid client secret provided. Ensure the secret being sent in the request is the client secret value, not the client secret ID, for a secret added to app 'bbcbb2ef-6200-4fae-82bd-d81f5dd738da'. Trace ID: 27729e9a-0943-41bd-9752-4ec9a8444500 Correlation ID: 824a9761-9387-4c53-ae31-5c242b1d4298 Timestamp: 2026-04-27 10:28:26Z\\\",\\\"error_codes\\\":[7000215],\\\"timestamp\\\":\\\"2026-04-27 10:28:26Z\\\",\\\"trace_id\\\":\\\"27729e9a-0943-41bd-9752-4ec9a8444500\\\",\\\"correlation_id\\\":\\\"824a9761-9387-4c53-ae31-5c242b1d4298\\\",\\\"error_uri\\\":\\\"https://login.microsoftonline.com/error?code=7000215\\\"}\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:26] local.INFO: [SocialAccountObserver] Saving model {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:26] local.ERROR: [SocialAccountService] Failed to refresh token {\"socialAccountId\":1202,\"provider\":\"office\",\"reason\":\"Flow refresh required.\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:26] local.INFO: [SocialAccountService] Fetching token {\"socialAccountId\":1502,\"provider\":\"google\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:26] local.INFO: [SocialAccountService] Token needs refreshing {\"socialAccountId\":1502,\"provider\":\"google\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:26] local.INFO: [EncryptedTokenManager] Generating access token. {\"mode\":\"legacy\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:26] local.INFO: [SocialAccountService] Refreshing token from provider {\"socialAccountId\":1502,\"provider\":\"google\",\"refreshToken\":\"d417c92ebaa137295a04675f715d0511ae8acac9d779b102eac50d6300116d3e\",\"state\":\"connected\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:27] local.INFO: [SocialAccountObserver] Saving model {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:27] local.INFO: [SocialAccountObserver] Access token was modified, encrypting {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:27] local.INFO: [SocialAccountService] Token refreshed {\"socialAccountId\":1502,\"provider\":\"google\",\"state\":\"connected\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:27] local.INFO: Calendar sync job dispatched {\"calendar_id\":501} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:27] local.INFO: [SocialAccountService] Fetching token {\"socialAccountId\":1300,\"provider\":\"google\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:27] local.INFO: [SocialAccountService] Token needs refreshing {\"socialAccountId\":1300,\"provider\":\"google\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:27] local.INFO: [EncryptedTokenManager] Generating access token. {\"mode\":\"legacy\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:27] local.INFO: [SocialAccountService] Refreshing token from provider {\"socialAccountId\":1300,\"provider\":\"google\",\"refreshToken\":\"4b811db0725fd9602a95943519a7da935e2a5065da7d9ebfcb170752e3e1ddb8\",\"state\":\"full-refresh\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:27] local.ERROR: [SocialAccountService] Failed to refresh token {\"socialAccountId\":1300,\"provider\":\"google\",\"responseBody\":{\"error\":\"invalid_grant\",\"error_description\":\"Account has been deleted\"}} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:27] local.INFO: [SocialAccountObserver] Saving model {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:27] local.ERROR: [SocialAccountService] Failed to refresh token {\"socialAccountId\":1300,\"provider\":\"google\",\"reason\":\"Flow refresh required.\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:27] local.INFO: [SocialAccountService] Fetching token {\"socialAccountId\":1409,\"provider\":\"google\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:27] local.INFO: [SocialAccountService] Token needs refreshing {\"socialAccountId\":1409,\"provider\":\"google\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:27] local.INFO: [EncryptedTokenManager] Generating access token. {\"mode\":\"legacy\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:27] local.INFO: [SocialAccountService] Refreshing token from provider {\"socialAccountId\":1409,\"provider\":\"google\",\"refreshToken\":\"e2a3f2d06894894eed1ee87d9db1ace77d4d42ee6e1288a8940ad2c10333b0c4\",\"state\":\"full-refresh\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:28] local.ERROR: [SocialAccountService] Failed to refresh token {\"socialAccountId\":1409,\"provider\":\"google\",\"responseBody\":{\"error\":\"invalid_grant\",\"error_description\":\"Bad Request\"}} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:28] local.INFO: [SocialAccountObserver] Saving model {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:28] local.ERROR: [SocialAccountService] Failed to refresh token {\"socialAccountId\":1409,\"provider\":\"google\",\"reason\":\"Flow refresh required.\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:28] local.INFO: [SocialAccountService] Fetching token {\"socialAccountId\":1352,\"provider\":\"google\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:28] local.INFO: [SocialAccountService] Token needs refreshing {\"socialAccountId\":1352,\"provider\":\"google\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:28] local.INFO: [EncryptedTokenManager] Generating access token. {\"mode\":\"legacy\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:28] local.INFO: [SocialAccountService] Refreshing token from provider {\"socialAccountId\":1352,\"provider\":\"google\",\"refreshToken\":\"dd4b16b00fdc1216da6b717c02338c073636e29162826b2de6db3f064fc029eb\",\"state\":\"full-refresh\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:28] local.ERROR: [SocialAccountService] Failed to refresh token {\"socialAccountId\":1352,\"provider\":\"google\",\"responseBody\":{\"error\":\"unauthorized_client\",\"error_description\":\"Unauthorized\"}} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:28] local.INFO: [SocialAccountObserver] Saving model {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:28] local.ERROR: [SocialAccountService] Failed to refresh token {\"socialAccountId\":1352,\"provider\":\"google\",\"reason\":\"Flow refresh required.\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:28] local.INFO: [SocialAccountService] Fetching token {\"socialAccountId\":1296,\"provider\":\"office\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:28] local.INFO: [SocialAccountService] Token needs refreshing {\"socialAccountId\":1296,\"provider\":\"office\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:28] local.INFO: [EncryptedTokenManager] Generating access token. {\"mode\":\"legacy\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:28] local.INFO: [SocialAccountService] Refreshing token from provider {\"socialAccountId\":1296,\"provider\":\"office\",\"refreshToken\":\"011ae723c9d800c674e0b4be76f49fc046dac7d501b66c59ef0d9549cfa56ae5\",\"state\":\"full-refresh\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:28] local.ERROR: [SocialAccountService] Failed to refresh token {\"socialAccountId\":1296,\"provider\":\"office\",\"responseBody\":\"{\\\"error\\\":\\\"invalid_client\\\",\\\"error_description\\\":\\\"AADSTS7000215: Invalid client secret provided. Ensure the secret being sent in the request is the client secret value, not the client secret ID, for a secret added to app 'bbcbb2ef-6200-4fae-82bd-d81f5dd738da'. Trace ID: 73b326f0-9fcd-4ff5-ad61-edd6a0504500 Correlation ID: 6bd8fef3-b24c-46d8-8bba-a9b2ab41c108 Timestamp: 2026-04-27 10:28:28Z\\\",\\\"error_codes\\\":[7000215],\\\"timestamp\\\":\\\"2026-04-27 10:28:28Z\\\",\\\"trace_id\\\":\\\"73b326f0-9fcd-4ff5-ad61-edd6a0504500\\\",\\\"correlation_id\\\":\\\"6bd8fef3-b24c-46d8-8bba-a9b2ab41c108\\\",\\\"error_uri\\\":\\\"https://login.microsoftonline.com/error?code=7000215\\\"}\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:28] local.INFO: [SocialAccountObserver] Saving model {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:28] local.ERROR: [SocialAccountService] Failed to refresh token {\"socialAccountId\":1296,\"provider\":\"office\",\"reason\":\"Flow refresh required.\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:28] local.INFO: [SocialAccountService] Fetching token {\"socialAccountId\":391,\"provider\":\"office\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:28] local.INFO: [SocialAccountService] Token needs refreshing {\"socialAccountId\":391,\"provider\":\"office\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:28] local.INFO: [EncryptedTokenManager] Generating access token. {\"mode\":\"legacy\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:28] local.INFO: [SocialAccountService] Refreshing token from provider {\"socialAccountId\":391,\"provider\":\"office\",\"refreshToken\":\"00045eebae0f39b34887c6d53f92ae78064f7145e1f4b67754aebd03cfb2d881\",\"state\":\"full-refresh\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:28] local.INFO: [SocialAccountService] Fetching token {\"socialAccountId\":1502,\"provider\":\"google\"} {\"correlation_id\":\"8a9488be-81de-40f5-8554-2503eec42566\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:28] local.INFO: [SocialAccountService] Token retrieved {\"socialAccountId\":1502,\"provider\":\"google\"} {\"correlation_id\":\"8a9488be-81de-40f5-8554-2503eec42566\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:28] local.INFO: [EncryptedTokenManager] Generating access token. {\"mode\":\"legacy\"} {\"correlation_id\":\"8a9488be-81de-40f5-8554-2503eec42566\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:29] local.INFO: [Calendar] Processing sync {\"calendarId\":\"a33076c1-8d97-431a-99f0-85c9524e118b\",\"from\":null,\"to\":null,\"delta\":\"CIiFh8TP44kDEIiFh8TP44kDGAUgkZvkzgIokZvkzgI=\",\"last_sync\":\"2024-12-09 07:12:53\",\"dateMode\":\"daily\"} {\"correlation_id\":\"8a9488be-81de-40f5-8554-2503eec42566\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:29] local.INFO: [CrmOwnerResolver] Integration owner matched as CRM Owner {\"crm_provider\":\"integration-app\",\"crm_owner\":1695,\"team_id\":3143} {\"correlation_id\":\"8a9488be-81de-40f5-8554-2503eec42566\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:29] local.INFO: [SocialAccountService] Fetching token {\"socialAccountId\":1502,\"provider\":\"google\"} {\"correlation_id\":\"8a9488be-81de-40f5-8554-2503eec42566\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:29] local.INFO: [SocialAccountService] Token retrieved {\"socialAccountId\":1502,\"provider\":\"google\"} {\"correlation_id\":\"8a9488be-81de-40f5-8554-2503eec42566\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:29] local.INFO: [EncryptedTokenManager] Generating access token. {\"mode\":\"legacy\"} {\"correlation_id\":\"8a9488be-81de-40f5-8554-2503eec42566\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:29] local.ERROR: [SocialAccountService] Failed to refresh token {\"socialAccountId\":391,\"provider\":\"office\",\"responseBody\":\"{\\\"error\\\":\\\"invalid_client\\\",\\\"error_description\\\":\\\"AADSTS7000215: Invalid client secret provided. Ensure the secret being sent in the request is the client secret value, not the client secret ID, for a secret added to app 'bbcbb2ef-6200-4fae-82bd-d81f5dd738da'. Trace ID: c5f1db6f-22fb-41cc-bd89-c46497634200 Correlation ID: 7b4df0cc-bf50-4017-9b1a-e25ffb6fe9bf Timestamp: 2026-04-27 10:28:29Z\\\",\\\"error_codes\\\":[7000215],\\\"timestamp\\\":\\\"2026-04-27 10:28:29Z\\\",\\\"trace_id\\\":\\\"c5f1db6f-22fb-41cc-bd89-c46497634200\\\",\\\"correlation_id\\\":\\\"7b4df0cc-bf50-4017-9b1a-e25ffb6fe9bf\\\",\\\"error_uri\\\":\\\"https://login.microsoftonline.com/error?code=7000215\\\"}\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:29] local.INFO: [SocialAccountObserver] Saving model {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:29] local.ERROR: [SocialAccountService] Failed to refresh token {\"socialAccountId\":391,\"provider\":\"office\",\"reason\":\"Flow refresh required.\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:29] local.INFO: [SocialAccountService] Fetching token {\"socialAccountId\":1271,\"provider\":\"office\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:29] local.INFO: [SocialAccountService] Token needs refreshing {\"socialAccountId\":1271,\"provider\":\"office\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:29] local.INFO: [EncryptedTokenManager] Generating access token. {\"mode\":\"legacy\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:29] local.INFO: [SocialAccountService] Refreshing token from provider {\"socialAccountId\":1271,\"provider\":\"office\",\"refreshToken\":\"118cde2c06993147b07ccaec4cbcd5026a819dea6c71081166a492933e392afb\",\"state\":\"full-refresh\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:29] local.INFO: [Google Calendar] Failed to watch channel for calendar {\"calendarId\":\"a33076c1-8d97-431a-99f0-85c9524e118b\",\"code\":400,\"reason\":\"{\n \\\"error\\\": {\n \\\"errors\\\": [\n {\n \\\"domain\\\": \\\"global\\\",\n \\\"reason\\\": \\\"push.webhookUrlNotHttps\\\",\n \\\"message\\\": \\\"WebHook callback must be HTTPS: /webhook/calendar/google?resourceType=event\\\"\n }\n ],\n \\\"code\\\": 400,\n \\\"message\\\": \\\"WebHook callback must be HTTPS: /webhook/calendar/google?resourceType=event\\\"\n }\n}\"} {\"correlation_id\":\"8a9488be-81de-40f5-8554-2503eec42566\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:29] local.WARNING: [Calendar] Sync failed {\"calendarId\":\"a33076c1-8d97-431a-99f0-85c9524e118b\",\"code\":400,\"reason\":\"{\n \\\"error\\\": {\n \\\"errors\\\": [\n {\n \\\"domain\\\": \\\"global\\\",\n \\\"reason\\\": \\\"push.webhookUrlNotHttps\\\",\n \\\"message\\\": \\\"WebHook callback must be HTTPS: /webhook/calendar/google?resourceType=event\\\"\n }\n ],\n \\\"code\\\": 400,\n \\\"message\\\": \\\"WebHook callback must be HTTPS: /webhook/calendar/google?resourceType=event\\\"\n }\n}\"} {\"correlation_id\":\"8a9488be-81de-40f5-8554-2503eec42566\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:29] local.ERROR: [SocialAccountService] Failed to refresh token {\"socialAccountId\":1271,\"provider\":\"office\",\"responseBody\":\"{\\\"error\\\":\\\"invalid_client\\\",\\\"error_description\\\":\\\"AADSTS7000215: Invalid client secret provided. Ensure the secret being sent in the request is the client secret value, not the client secret ID, for a secret added to app 'bbcbb2ef-6200-4fae-82bd-d81f5dd738da'. Trace ID: 8d6e82ba-88c3-4ec9-95ee-a4a905803900 Correlation ID: 6ede51d5-704d-4e24-9fb7-88f69f3da5db Timestamp: 2026-04-27 10:28:29Z\\\",\\\"error_codes\\\":[7000215],\\\"timestamp\\\":\\\"2026-04-27 10:28:29Z\\\",\\\"trace_id\\\":\\\"8d6e82ba-88c3-4ec9-95ee-a4a905803900\\\",\\\"correlation_id\\\":\\\"6ede51d5-704d-4e24-9fb7-88f69f3da5db\\\",\\\"error_uri\\\":\\\"https://login.microsoftonline.com/error?code=7000215\\\"}\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:30] local.INFO: [SocialAccountObserver] Saving model {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:30] local.ERROR: [SocialAccountService] Failed to refresh token {\"socialAccountId\":1271,\"provider\":\"office\",\"reason\":\"Flow refresh required.\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:30] local.INFO: [SocialAccountService] Fetching token {\"socialAccountId\":1351,\"provider\":\"google\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:30] local.INFO: [SocialAccountService] Token needs refreshing {\"socialAccountId\":1351,\"provider\":\"google\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:30] local.INFO: [EncryptedTokenManager] Generating access token. {\"mode\":\"legacy\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:30] local.INFO: [SocialAccountService] Refreshing token from provider {\"socialAccountId\":1351,\"provider\":\"google\",\"refreshToken\":\"4271d15b9e60a606439caddc68337f783e472c85b03dacff14d1b6dfded9051c\",\"state\":\"full-refresh\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:30] local.ERROR: [SocialAccountService] Failed to refresh token {\"socialAccountId\":1351,\"provider\":\"google\",\"responseBody\":{\"error\":\"invalid_grant\",\"error_description\":\"Token has been expired or revoked.\"}} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:30] local.INFO: [SocialAccountObserver] Saving model {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:30] local.ERROR: [SocialAccountService] Failed to refresh token {\"socialAccountId\":1351,\"provider\":\"google\",\"reason\":\"Flow refresh required.\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:30] local.INFO: [SocialAccountService] Fetching token {\"socialAccountId\":1366,\"provider\":\"google\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:30] local.INFO: [SocialAccountService] Token needs refreshing {\"socialAccountId\":1366,\"provider\":\"google\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:30] local.INFO: [EncryptedTokenManager] Generating access token. {\"mode\":\"legacy\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:30] local.INFO: [SocialAccountService] Refreshing token from provider {\"socialAccountId\":1366,\"provider\":\"google\",\"refreshToken\":\"ae21385059b2eebfd43f68aecd56eccd702a1aabb6598f1f7ab594ed8af491b4\",\"state\":\"full-refresh\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:30] local.ERROR: [SocialAccountService] Failed to refresh token {\"socialAccountId\":1366,\"provider\":\"google\",\"responseBody\":{\"error\":\"invalid_grant\",\"error_description\":\"Bad Request\"}} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:30] local.INFO: [SocialAccountObserver] Saving model {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:30] local.ERROR: [SocialAccountService] Failed to refresh token {\"socialAccountId\":1366,\"provider\":\"google\",\"reason\":\"Flow refresh required.\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:30] local.INFO: [SocialAccountService] Fetching token {\"socialAccountId\":1115,\"provider\":\"google\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:30] local.INFO: [SocialAccountService] Token needs refreshing {\"socialAccountId\":1115,\"provider\":\"google\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:30] local.INFO: [EncryptedTokenManager] Generating access token. {\"mode\":\"legacy\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:30] local.INFO: [SocialAccountService] Refreshing token from provider {\"socialAccountId\":1115,\"provider\":\"google\",\"refreshToken\":\"356b60f12e262a5e24d3042386ef47d6a6cfe3074c242f4426edcec8646192b1\",\"state\":\"connected\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:30] local.INFO: [SocialAccountObserver] Saving model {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:30] local.INFO: [SocialAccountObserver] Access token was modified, encrypting {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:31] local.INFO: [SocialAccountService] Token refreshed {\"socialAccountId\":1115,\"provider\":\"google\",\"state\":\"connected\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:31] local.INFO: Calendar sync job dispatched {\"calendar_id\":378} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:31] local.INFO: [SocialAccountService] Fetching token {\"socialAccountId\":1421,\"provider\":\"office\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:31] local.INFO: [SocialAccountService] Token needs refreshing {\"socialAccountId\":1421,\"provider\":\"office\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:31] local.INFO: [EncryptedTokenManager] Generating access token. {\"mode\":\"legacy\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:31] local.INFO: [SocialAccountService] Refreshing token from provider {\"socialAccountId\":1421,\"provider\":\"office\",\"refreshToken\":\"9e3e287aa0cc900726e3d4eead2600fb64aa34b437790f6a1fd8048306cb2db9\",\"state\":\"connected\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:31] local.INFO: [SocialAccountService] Fetching token {\"socialAccountId\":1115,\"provider\":\"google\"} {\"correlation_id\":\"8fdf411d-8a4f-46c1-a46a-cc84f3c2f2b1\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:31] local.INFO: [SocialAccountService] Token retrieved {\"socialAccountId\":1115,\"provider\":\"google\"} {\"correlation_id\":\"8fdf411d-8a4f-46c1-a46a-cc84f3c2f2b1\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:31] local.INFO: [EncryptedTokenManager] Generating access token. {\"mode\":\"legacy\"} {\"correlation_id\":\"8fdf411d-8a4f-46c1-a46a-cc84f3c2f2b1\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:31] local.INFO: [Calendar] Processing sync {\"calendarId\":\"2676cb6d-f86c-427e-bf78-591e388e3c1e\",\"from\":null,\"to\":null,\"delta\":\"CJ_x49O3jpIDEJ_x49O3jpIDGAUgw67KlwMow67KlwM=\",\"last_sync\":\"2026-01-19 07:48:40\",\"dateMode\":\"daily\"} {\"correlation_id\":\"8fdf411d-8a4f-46c1-a46a-cc84f3c2f2b1\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:31] local.WARNING: [Pipedrive] Account not connected for user {\"userId\":\"e6538737-e7b4-455f-a37a-3e79b665a220\",\"account\":{\"Jiminny\\\\Models\\\\SocialAccount\":{\"id\":1116,\"sociable_id\":241,\"provider_user_id\":\"19555731\",\"expires\":1775683749,\"refresh_token_expires\":null,\"provider\":\"pipedrive\",\"state\":\"full-refresh\",\"auth_scope\":\"base,deals:full,activities:full,contacts:full,search:read\",\"retry_after\":null,\"created_at\":\"2023-09-08 09:44:29\",\"updated_at\":\"2026-04-08 22:58:34\"}}} {\"correlation_id\":\"8fdf411d-8a4f-46c1-a46a-cc84f3c2f2b1\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:31] local.INFO: [CrmOwnerResolver] Integration owner is not connected, attempting team members {\"crm_provider\":\"pipedrive\",\"crm_owner\":241,\"team_id\":19} {\"correlation_id\":\"8fdf411d-8a4f-46c1-a46a-cc84f3c2f2b1\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:31] local.INFO: [CrmOwnerResolver] No team members found with active crm connection {\"crm_provider\":\"pipedrive\",\"team_id\":19} {\"correlation_id\":\"8fdf411d-8a4f-46c1-a46a-cc84f3c2f2b1\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:31] local.INFO: [CrmOwnerResolver] No team member found with active crm connection {\"crm_provider\":\"pipedrive\",\"team_id\":19} {\"correlation_id\":\"8fdf411d-8a4f-46c1-a46a-cc84f3c2f2b1\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:31] local.WARNING: [Calendar] CRM disconnected for user so events will not be matched {\"provider\":\"pipedrive\",\"user_id\":241,\"message\":\"Your Pipedrive account has become disconnected. Please login to Jiminny to reconnect.\"} {\"correlation_id\":\"8fdf411d-8a4f-46c1-a46a-cc84f3c2f2b1\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:31] local.INFO: [SocialAccountService] Fetching token {\"socialAccountId\":1115,\"provider\":\"google\"} {\"correlation_id\":\"8fdf411d-8a4f-46c1-a46a-cc84f3c2f2b1\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:31] local.INFO: [SocialAccountService] Token retrieved {\"socialAccountId\":1115,\"provider\":\"google\"} {\"correlation_id\":\"8fdf411d-8a4f-46c1-a46a-cc84f3c2f2b1\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:31] local.INFO: [EncryptedTokenManager] Generating access token. {\"mode\":\"legacy\"} {\"correlation_id\":\"8fdf411d-8a4f-46c1-a46a-cc84f3c2f2b1\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:31] local.INFO: [Google Calendar] Failed to watch channel for calendar {\"calendarId\":\"2676cb6d-f86c-427e-bf78-591e388e3c1e\",\"code\":400,\"reason\":\"{\n \\\"error\\\": {\n \\\"errors\\\": [\n {\n \\\"domain\\\": \\\"global\\\",\n \\\"reason\\\": \\\"push.webhookUrlNotHttps\\\",\n \\\"message\\\": \\\"WebHook callback must be HTTPS: /webhook/calendar/google?resourceType=event\\\"\n }\n ],\n \\\"code\\\": 400,\n \\\"message\\\": \\\"WebHook callback must be HTTPS: /webhook/calendar/google?resourceType=event\\\"\n }\n}\"} {\"correlation_id\":\"8fdf411d-8a4f-46c1-a46a-cc84f3c2f2b1\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:31] local.WARNING: [Calendar] Sync failed {\"calendarId\":\"2676cb6d-f86c-427e-bf78-591e388e3c1e\",\"code\":400,\"reason\":\"{\n \\\"error\\\": {\n \\\"errors\\\": [\n {\n \\\"domain\\\": \\\"global\\\",\n \\\"reason\\\": \\\"push.webhookUrlNotHttps\\\",\n \\\"message\\\": \\\"WebHook callback must be HTTPS: /webhook/calendar/google?resourceType=event\\\"\n }\n ],\n \\\"code\\\": 400,\n \\\"message\\\": \\\"WebHook callback must be HTTPS: /webhook/calendar/google?resourceType=event\\\"\n }\n}\"} {\"correlation_id\":\"8fdf411d-8a4f-46c1-a46a-cc84f3c2f2b1\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:32] local.INFO: [SocialAccountObserver] Saving model {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:32] local.INFO: [SocialAccountObserver] Access token was modified, encrypting {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:32] local.INFO: [SocialAccountObserver] Refresh token was modified, encrypting {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:32] local.INFO: [SocialAccountService] Token refreshed {\"socialAccountId\":1421,\"provider\":\"office\",\"state\":\"connected\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:32] local.INFO: Calendar sync job dispatched {\"calendar_id\":504} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:32] local.NOTICE: Calendar sync end {\"retrieved_calendars\":31,\"processed_calendars\":3} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:32] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage for command {\"command\":\"calendar:sync\",\"memoryBeforeCommandInMb\":60.0,\"memoryAfterCommandInMB\":62.0,\"memoryPeakBeforeCommandInMb\":99.727,\"memoryPeakAfterCommandInMB\":99.727} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:33] local.INFO: [SocialAccountService] Fetching token {\"socialAccountId\":1421,\"provider\":\"office\"} {\"correlation_id\":\"52b2ff86-82c7-4ed0-b528-35d7d00d69f0\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:33] local.INFO: [SocialAccountService] Token retrieved {\"socialAccountId\":1421,\"provider\":\"office\"} {\"correlation_id\":\"52b2ff86-82c7-4ed0-b528-35d7d00d69f0\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:33] local.INFO: [EncryptedTokenManager] Generating access token. {\"mode\":\"legacy\"} {\"correlation_id\":\"52b2ff86-82c7-4ed0-b528-35d7d00d69f0\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:34] local.INFO: [Calendar] Processing sync {\"calendarId\":\"9e8b1a2c-1a8f-42bd-b161-810fc0baf540\",\"from\":null,\"to\":null,\"delta\":\"R0usmcdvmMuZCBYV0hguCMx0nGVsTQEodM41jSlu-5eY2JUTYHZXE-bDro2tnepp8q_Vq3ITNS8CkncjGjcBfaHkza05dCTIlaXcSIoPNGOl9vNckYy_ZhVgE1SWvgRcCeVT-SyogdB9hP-oaWbH9r51OQrrzAwmalK5aCksyUVh9jG_A-mGVzRPnUB4SKa77o5P659lpx1egON48YU2tQ.D2oErKma-mQrBdXxAKdH7beUkp6HS2PXUWYLCgvbP2c\",\"last_sync\":\"2026-04-27 06:28:41\",\"dateMode\":\"daily\"} {\"correlation_id\":\"52b2ff86-82c7-4ed0-b528-35d7d00d69f0\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:34] local.INFO: [SocialAccountService] Fetching token {\"socialAccountId\":1499,\"provider\":\"hubspot\"} {\"correlation_id\":\"52b2ff86-82c7-4ed0-b528-35d7d00d69f0\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:34] local.INFO: [SocialAccountService] Token retrieved {\"socialAccountId\":1499,\"provider\":\"hubspot\"} {\"correlation_id\":\"52b2ff86-82c7-4ed0-b528-35d7d00d69f0\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:34] local.INFO: [EncryptedTokenManager] Generating access token. {\"mode\":\"legacy\"} {\"correlation_id\":\"52b2ff86-82c7-4ed0-b528-35d7d00d69f0\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:34] local.INFO: [CrmOwnerResolver] Integration owner matched as CRM Owner {\"crm_provider\":\"hubspot\",\"crm_owner\":89,\"team_id\":2} {\"correlation_id\":\"52b2ff86-82c7-4ed0-b528-35d7d00d69f0\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:34] local.INFO: [MS Office Calendar] Skipping delta sync for daily mode {\"calendarId\":\"9e8b1a2c-1a8f-42bd-b161-810fc0baf540\"} {\"correlation_id\":\"52b2ff86-82c7-4ed0-b528-35d7d00d69f0\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:29:06] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage before starting command {\"command\":\"meeting-bot:schedule-bot\",\"memoryBeforeCommandInMb\":60.0,\"memoryPeakBeforeCommandInMb\":99.727} {\"correlation_id\":\"7e95c05b-50c8-4e0b-a01c-efeed6af544c\",\"trace_id\":\"07db4d4f-d6e7-44d9-8e54-4f038ea69afb\"}\n[2026-04-27 10:29:06] local.INFO: [ScheduleBotCommand] Number of activities to be captured: 0 {\"correlation_id\":\"7e95c05b-50c8-4e0b-a01c-efeed6af544c\",\"trace_id\":\"07db4d4f-d6e7-44d9-8e54-4f038ea69afb\"}\n[2026-04-27 10:29:06] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage for command {\"command\":\"meeting-bot:schedule-bot\",\"memoryBeforeCommandInMb\":60.0,\"memoryAfterCommandInMB\":62.0,\"memoryPeakBeforeCommandInMb\":99.727,\"memoryPeakAfterCommandInMB\":99.727} {\"correlation_id\":\"7e95c05b-50c8-4e0b-a01c-efeed6af544c\",\"trace_id\":\"07db4d4f-d6e7-44d9-8e54-4f038ea69afb\"}\n[2026-04-27 10:29:09] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage before starting command {\"command\":\"dialers:monitor-activities\",\"memoryBeforeCommandInMb\":60.0,\"memoryPeakBeforeCommandInMb\":99.727} {\"correlation_id\":\"e6e7fbf8-6e80-4529-858e-a4e95d0d3805\",\"trace_id\":\"f91a115a-9fdc-46ce-b9f2-13a1e6d0e325\"}\n[2026-04-27 10:29:09] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage for command {\"command\":\"dialers:monitor-activities\",\"memoryBeforeCommandInMb\":60.0,\"memoryAfterCommandInMB\":62.0,\"memoryPeakBeforeCommandInMb\":99.727,\"memoryPeakAfterCommandInMB\":99.727} {\"correlation_id\":\"e6e7fbf8-6e80-4529-858e-a4e95d0d3805\",\"trace_id\":\"f91a115a-9fdc-46ce-b9f2-13a1e6d0e325\"}\n[2026-04-27 10:29:10] local.NOTICE: Monitoring start {\"correlation_id\":\"211e5e36-1c6c-4198-b928-d2f1525ab0ea\",\"trace_id\":\"c26e980d-6e32-426d-9667-f1d902432b8d\"}\n[2026-04-27 10:29:10] local.NOTICE: Monitoring end {\"correlation_id\":\"211e5e36-1c6c-4198-b928-d2f1525ab0ea\",\"trace_id\":\"c26e980d-6e32-426d-9667-f1d902432b8d\"}\n[2026-04-27 10:29:12] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage before starting command {\"command\":\"mailbox:skip-lists:refresh\",\"memoryBeforeCommandInMb\":60.0,\"memoryPeakBeforeCommandInMb\":99.727} {\"correlation_id\":\"684bdf68-76ee-42dd-9d97-62f414a33b1b\",\"trace_id\":\"284c98ef-5b7f-4572-996c-72c5bc2995f9\"}\n[2026-04-27 10:29:13] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage for command {\"command\":\"mailbox:skip-lists:refresh\",\"memoryBeforeCommandInMb\":60.0,\"memoryAfterCommandInMB\":62.0,\"memoryPeakBeforeCommandInMb\":99.727,\"memoryPeakAfterCommandInMB\":99.727} {\"correlation_id\":\"684bdf68-76ee-42dd-9d97-62f414a33b1b\",\"trace_id\":\"284c98ef-5b7f-4572-996c-72c5bc2995f9\"}\n[2026-04-27 10:29:18] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage before starting command {\"command\":\"mailbox:batch:process\",\"memoryBeforeCommandInMb\":60.0,\"memoryPeakBeforeCommandInMb\":99.727} {\"correlation_id\":\"6e74b148-ae54-42ff-ab72-2ded18d10ff4\",\"trace_id\":\"d05bc9a0-450c-45b2-be3b-225a551ed909\"}\n[2026-04-27 10:29:18] local.INFO: [EmailSchedule] STARTING batch process {\"host\":\"docker_lamp_1\"} {\"correlation_id\":\"6e74b148-ae54-42ff-ab72-2ded18d10ff4\",\"trace_id\":\"d05bc9a0-450c-45b2-be3b-225a551ed909\"}\n[2026-04-27 10:29:18] local.INFO: [EmailSchedule] FINISHED batch process {\"host\":\"docker_lamp_1\",\"processed\":0} {\"correlation_id\":\"6e74b148-ae54-42ff-ab72-2ded18d10ff4\",\"trace_id\":\"d05bc9a0-450c-45b2-be3b-225a551ed909\"}\n[2026-04-27 10:29:18] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage for command {\"command\":\"mailbox:batch:process\",\"memoryBeforeCommandInMb\":60.0,\"memoryAfterCommandInMB\":62.0,\"memoryPeakBeforeCommandInMb\":99.727,\"memoryPeakAfterCommandInMB\":99.727} {\"correlation_id\":\"6e74b148-ae54-42ff-ab72-2ded18d10ff4\",\"trace_id\":\"d05bc9a0-450c-45b2-be3b-225a551ed909\"}\n[2026-04-27 10:30:08] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage before starting command {\"command\":\"meeting-bot:schedule-bot\",\"memoryBeforeCommandInMb\":60.0,\"memoryPeakBeforeCommandInMb\":99.727} {\"correlation_id\":\"24b807e0-b413-4ecc-a89b-365280be88ac\",\"trace_id\":\"542ac70c-6756-4409-8224-0844a7576d9c\"}\n[2026-04-27 10:30:08] local.INFO: [ScheduleBotCommand] Number of activities to be captured: 0 {\"correlation_id\":\"24b807e0-b413-4ecc-a89b-365280be88ac\",\"trace_id\":\"542ac70c-6756-4409-8224-0844a7576d9c\"}\n[2026-04-27 10:30:08] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage for command {\"command\":\"meeting-bot:schedule-bot\",\"memoryBeforeCommandInMb\":60.0,\"memoryAfterCommandInMB\":62.0,\"memoryPeakBeforeCommandInMb\":99.727,\"memoryPeakAfterCommandInMB\":99.727} {\"correlation_id\":\"24b807e0-b413-4ecc-a89b-365280be88ac\",\"trace_id\":\"542ac70c-6756-4409-8224-0844a7576d9c\"}\n[2026-04-27 10:30:10] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage before starting command {\"command\":\"dialers:monitor-activities\",\"memoryBeforeCommandInMb\":60.0,\"memoryPeakBeforeCommandInMb\":99.727} {\"correlation_id\":\"2711ca04-b4a7-4f3a-b0b2-5826fea4da3b\",\"trace_id\":\"2613ffca-f181-4d91-ad55-ec1bdd97ee7e\"}\n[2026-04-27 10:30:10] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage for command {\"command\":\"dialers:monitor-activities\",\"memoryBeforeCommandInMb\":60.0,\"memoryAfterCommandInMB\":62.0,\"memoryPeakBeforeCommandInMb\":99.727,\"memoryPeakAfterCommandInMB\":99.727} {\"correlation_id\":\"2711ca04-b4a7-4f3a-b0b2-5826fea4da3b\",\"trace_id\":\"2613ffca-f181-4d91-ad55-ec1bdd97ee7e\"}\n[2026-04-27 10:30:17] local.NOTICE: Monitoring start {\"correlation_id\":\"e1537475-8481-4144-b9f5-596ea8034b8a\",\"trace_id\":\"f5561d0a-0cb7-4c72-8bde-cce617183529\"}\n[2026-04-27 10:30:17] local.NOTICE: Monitoring end {\"correlation_id\":\"e1537475-8481-4144-b9f5-596ea8034b8a\",\"trace_id\":\"f5561d0a-0cb7-4c72-8bde-cce617183529\"}\n[2026-04-27 10:30:21] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage before starting command {\"command\":\"mailbox:skip-lists:refresh\",\"memoryBeforeCommandInMb\":60.0,\"memoryPeakBeforeCommandInMb\":99.727} {\"correlation_id\":\"8a911b60-16a0-416e-934e-3b7e0280e452\",\"trace_id\":\"3c2bbdd6-985d-4624-b42b-fd9cc2b4756f\"}\n[2026-04-27 10:30:21] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage for command {\"command\":\"mailbox:skip-lists:refresh\",\"memoryBeforeCommandInMb\":60.0,\"memoryAfterCommandInMB\":62.0,\"memoryPeakBeforeCommandInMb\":99.727,\"memoryPeakAfterCommandInMB\":99.727} {\"correlation_id\":\"8a911b60-16a0-416e-934e-3b7e0280e452\",\"trace_id\":\"3c2bbdd6-985d-4624-b42b-fd9cc2b4756f\"}\n[2026-04-27 10:30:25] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage before starting command {\"command\":\"mailbox:batch:process\",\"memoryBeforeCommandInMb\":60.0,\"memoryPeakBeforeCommandInMb\":99.727} {\"correlation_id\":\"f62efaa6-201f-43b2-bf45-9619982dffb1\",\"trace_id\":\"f1ad3d60-af4e-4427-9d77-6aa71cfed172\"}\n[2026-04-27 10:30:25] local.INFO: [EmailSchedule] STARTING batch process {\"host\":\"docker_lamp_1\"} {\"correlation_id\":\"f62efaa6-201f-43b2-bf45-9619982dffb1\",\"trace_id\":\"f1ad3d60-af4e-4427-9d77-6aa71cfed172\"}\n[2026-04-27 10:30:25] local.INFO: [EmailSchedule] FINISHED batch process {\"host\":\"docker_lamp_1\",\"processed\":0} {\"correlation_id\":\"f62efaa6-201f-43b2-bf45-9619982dffb1\",\"trace_id\":\"f1ad3d60-af4e-4427-9d77-6aa71cfed172\"}\n[2026-04-27 10:30:25] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage for command {\"command\":\"mailbox:batch:process\",\"memoryBeforeCommandInMb\":60.0,\"memoryAfterCommandInMB\":62.0,\"memoryPeakBeforeCommandInMb\":99.727,\"memoryPeakAfterCommandInMB\":99.727} {\"correlation_id\":\"f62efaa6-201f-43b2-bf45-9619982dffb1\",\"trace_id\":\"f1ad3d60-af4e-4427-9d77-6aa71cfed172\"}\n[2026-04-27 10:30:30] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage before starting command {\"command\":\"conference:monitor:count\",\"memoryBeforeCommandInMb\":60.0,\"memoryPeakBeforeCommandInMb\":99.727} {\"correlation_id\":\"cf6dc132-ecb8-4efc-bead-06bf1c96cae6\",\"trace_id\":\"9e2b073b-940d-498a-babe-115f633dd954\"}\n[2026-04-27 10:30:30] local.INFO: Running conference:monitor:count command for activities in (2026-04-27 10:28:00, 2026-04-27 10:30:00] {\"correlation_id\":\"cf6dc132-ecb8-4efc-bead-06bf1c96cae6\",\"trace_id\":\"9e2b073b-940d-498a-babe-115f633dd954\"}\n[2026-04-27 10:30:30] local.INFO: [conference:monitor:count] No activities found in (2026-04-27 10:28:00, 2026-04-27 10:30:00] {\"correlation_id\":\"cf6dc132-ecb8-4efc-bead-06bf1c96cae6\",\"trace_id\":\"9e2b073b-940d-498a-babe-115f633dd954\"}\n[2026-04-27 10:30:30] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage for command {\"command\":\"conference:monitor:count\",\"memoryBeforeCommandInMb\":60.0,\"memoryAfterCommandInMB\":62.0,\"memoryPeakBeforeCommandInMb\":99.727,\"memoryPeakAfterCommandInMB\":99.727} {\"correlation_id\":\"cf6dc132-ecb8-4efc-bead-06bf1c96cae6\",\"trace_id\":\"9e2b073b-940d-498a-babe-115f633dd954\"}\n[2026-04-27 10:30:32] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage before starting command {\"command\":\"activity:purge-stale\",\"memoryBeforeCommandInMb\":60.0,\"memoryPeakBeforeCommandInMb\":99.727} {\"correlation_id\":\"f455260d-d1c1-456f-8937-d4bb026d9078\",\"trace_id\":\"f8c2e585-0137-4103-9e16-1b4ce05df2ef\"}\n[2026-04-27 10:30:32] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage for command {\"command\":\"activity:purge-stale\",\"memoryBeforeCommandInMb\":60.0,\"memoryAfterCommandInMB\":62.0,\"memoryPeakBeforeCommandInMb\":99.727,\"memoryPeakAfterCommandInMB\":99.727} {\"correlation_id\":\"f455260d-d1c1-456f-8937-d4bb026d9078\",\"trace_id\":\"f8c2e585-0137-4103-9e16-1b4ce05df2ef\"}\n[2026-04-27 10:30:35] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage before starting command {\"command\":\"mailbox:text-relay:sync\",\"memoryBeforeCommandInMb\":60.0,\"memoryPeakBeforeCommandInMb\":99.727} {\"correlation_id\":\"be7d3c6a-664a-42e8-a4b3-cbd021296404\",\"trace_id\":\"0b250827-4c02-4237-8b34-e5ddd004eba1\"}\n[2026-04-27 10:30:35] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage for command {\"command\":\"mailbox:text-relay:sync\",\"memoryBeforeCommandInMb\":60.0,\"memoryAfterCommandInMB\":62.0,\"memoryPeakBeforeCommandInMb\":99.727,\"memoryPeakAfterCommandInMB\":99.727} {\"correlation_id\":\"be7d3c6a-664a-42e8-a4b3-cbd021296404\",\"trace_id\":\"0b250827-4c02-4237-8b34-e5ddd004eba1\"}\n[2026-04-27 10:30:37] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage before starting command {\"command\":\"conference:pre-meeting-notification\",\"memoryBeforeCommandInMb\":60.0,\"memoryPeakBeforeCommandInMb\":99.727} {\"correlation_id\":\"5d8fbdf6-2737-4d73-babe-699c88279e1f\",\"trace_id\":\"0882a963-b02f-4049-b670-12b92f80829a\"}\n[2026-04-27 10:30:37] local.INFO: Running pre-meeting notification command {\"correlation_id\":\"5d8fbdf6-2737-4d73-babe-699c88279e1f\",\"trace_id\":\"0882a963-b02f-4049-b670-12b92f80829a\"}\n[2026-04-27 10:30:37] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage for command {\"command\":\"conference:pre-meeting-notification\",\"memoryBeforeCommandInMb\":60.0,\"memoryAfterCommandInMB\":62.0,\"memoryPeakBeforeCommandInMb\":99.727,\"memoryPeakAfterCommandInMB\":99.727} {\"correlation_id\":\"5d8fbdf6-2737-4d73-babe-699c88279e1f\",\"trace_id\":\"0882a963-b02f-4049-b670-12b92f80829a\"}\n[2026-04-27 10:30:39] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage before starting command {\"command\":\"conference:monitor:start\",\"memoryBeforeCommandInMb\":60.0,\"memoryPeakBeforeCommandInMb\":99.727} {\"correlation_id\":\"e8968e47-9acd-4bd4-9e7d-3c415f46df05\",\"trace_id\":\"287fba85-f395-41ab-813d-49cb5d0ea06d\"}\n[2026-04-27 10:30:39] local.INFO: Running conference:monitor:start command for activities in (2026-04-27 10:20:00, 2026-04-27 10:25:00] {\"correlation_id\":\"e8968e47-9acd-4bd4-9e7d-3c415f46df05\",\"trace_id\":\"287fba85-f395-41ab-813d-49cb5d0ea06d\"}\n[2026-04-27 10:30:39] local.INFO: [conference:monitor:start] No activities found in (2026-04-27 10:20:00, 2026-04-27 10:25:00] {\"correlation_id\":\"e8968e47-9acd-4bd4-9e7d-3c415f46df05\",\"trace_id\":\"287fba85-f395-41ab-813d-49cb5d0ea06d\"}\n[2026-04-27 10:30:39] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage for command {\"command\":\"conference:monitor:start\",\"memoryBeforeCommandInMb\":60.0,\"memoryAfterCommandInMB\":62.0,\"memoryPeakBeforeCommandInMb\":99.727,\"memoryPeakAfterCommandInMB\":99.727} {\"correlation_id\":\"e8968e47-9acd-4bd4-9e7d-3c415f46df05\",\"trace_id\":\"287fba85-f395-41ab-813d-49cb5d0ea06d\"}\n[2026-04-27 10:30:43] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage before starting command {\"command\":\"conference:monitor:end\",\"memoryBeforeCommandInMb\":60.0,\"memoryPeakBeforeCommandInMb\":99.727} {\"correlation_id\":\"b87eb011-7108-46bd-aa90-975ebe41f483\",\"trace_id\":\"1f54c2a6-fad1-4804-87d8-4972a378e91b\"}\n[2026-04-27 10:30:43] local.INFO: conference:monitor:end:Jiminny\\Console\\Commands\\Activities\\MonitorMeetingEndCommand::logActivitiesEnded {\"from\":\"10:25\",\"to\":\"10:30\"} {\"correlation_id\":\"b87eb011-7108-46bd-aa90-975ebe41f483\",\"trace_id\":\"1f54c2a6-fad1-4804-87d8-4972a378e91b\"}\n[2026-04-27 10:30:44] local.INFO: conference:monitor:end:Jiminny\\Console\\Commands\\Activities\\MonitorMeetingEndCommand::logActivitiesWithUnfinishedSession {\"from\":\"00:20\",\"to\":\"00:25\"} {\"correlation_id\":\"b87eb011-7108-46bd-aa90-975ebe41f483\",\"trace_id\":\"1f54c2a6-fad1-4804-87d8-4972a378e91b\"}\n[2026-04-27 10:30:44] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage for command {\"command\":\"conference:monitor:end\",\"memoryBeforeCommandInMb\":60.0,\"memoryAfterCommandInMB\":62.0,\"memoryPeakBeforeCommandInMb\":99.727,\"memoryPeakAfterCommandInMB\":99.727} {\"correlation_id\":\"b87eb011-7108-46bd-aa90-975ebe41f483\",\"trace_id\":\"1f54c2a6-fad1-4804-87d8-4972a378e91b\"}\n[2026-04-27 10:30:51] local.NOTICE: Repairing HubSpot tokens start {\"correlation_id\":\"2e6dfa3d-f07d-4eee-8232-6138133e2fbb\",\"trace_id\":\"41dea86e-0ecc-414e-8303-e60d8ae09976\"}\n[2026-04-27 10:30:51] local.INFO: Trying to refresh HubSpot token {\"account_id\":59,\"updated_at\":\"2025-10-03 09:32:05\"} {\"correlation_id\":\"2e6dfa3d-f07d-4eee-8232-6138133e2fbb\",\"trace_id\":\"41dea86e-0ecc-414e-8303-e60d8ae09976\"}\n[2026-04-27 10:30:51] local.INFO: [EncryptedTokenManager] Generating access token. {\"mode\":\"legacy\"} {\"correlation_id\":\"2e6dfa3d-f07d-4eee-8232-6138133e2fbb\",\"trace_id\":\"41dea86e-0ecc-414e-8303-e60d8ae09976\"}\n[2026-04-27 10:30:51] local.INFO: [SocialAccountService] Refreshing token from provider {\"socialAccountId\":59,\"provider\":\"hubspot\",\"refreshToken\":\"97b78f6e2cc49965c00c2492b602b02708b1392551e6b3f113fbaa48992af90b\",\"state\":\"full-refresh\"} {\"correlation_id\":\"2e6dfa3d-f07d-4eee-8232-6138133e2fbb\",\"trace_id\":\"41dea86e-0ecc-414e-8303-e60d8ae09976\"}\n[2026-04-27 10:30:52] local.ERROR: Failed to refresh HubSpot token {\"account_id\":59,\"updated_at\":\"2025-10-03 09:32:05\",\"reason\":\"missing or invalid refresh token\",\"previous\":\"\"} {\"correlation_id\":\"2e6dfa3d-f07d-4eee-8232-6138133e2fbb\",\"trace_id\":\"41dea86e-0ecc-414e-8303-e60d8ae09976\"}\n[2026-04-27 10:30:52] local.INFO: Trying to refresh HubSpot token {\"account_id\":306,\"updated_at\":\"2023-11-27 09:30:03\"} {\"correlation_id\":\"2e6dfa3d-f07d-4eee-8232-6138133e2fbb\",\"trace_id\":\"41dea86e-0ecc-414e-8303-e60d8ae09976\"}\n[2026-04-27 10:30:52] local.INFO: [EncryptedTokenManager] Generating access token. {\"mode\":\"legacy\"} {\"correlation_id\":\"2e6dfa3d-f07d-4eee-8232-6138133e2fbb\",\"trace_id\":\"41dea86e-0ecc-414e-8303-e60d8ae09976\"}\n[2026-04-27 10:30:52] local.INFO: [SocialAccountService] Refreshing token from provider {\"socialAccountId\":306,\"provider\":\"hubspot\",\"refreshToken\":\"6fa6aa8cc641d131231acc3470f5c03cb3b07b2e580fb18f8acb3b1dbb72549b\",\"state\":\"full-refresh\"} {\"correlation_id\":\"2e6dfa3d-f07d-4eee-8232-6138133e2fbb\",\"trace_id\":\"41dea86e-0ecc-414e-8303-e60d8ae09976\"}\n[2026-04-27 10:30:52] local.ERROR: Failed to refresh HubSpot token {\"account_id\":306,\"updated_at\":\"2023-11-27 09:30:03\",\"reason\":\"missing or invalid refresh token\",\"previous\":\"\"} {\"correlation_id\":\"2e6dfa3d-f07d-4eee-8232-6138133e2fbb\",\"trace_id\":\"41dea86e-0ecc-414e-8303-e60d8ae09976\"}\n[2026-04-27 10:30:52] local.INFO: Trying to refresh HubSpot token {\"account_id\":1372,\"updated_at\":\"2025-10-02 14:47:06\"} {\"correlation_id\":\"2e6dfa3d-f07d-4eee-8232-6138133e2fbb\",\"trace_id\":\"41dea86e-0ecc-414e-8303-e60d8ae09976\"}\n[2026-04-27 10:30:52] local.INFO: [EncryptedTokenManager] Generating access token. {\"mode\":\"legacy\"} {\"correlation_id\":\"2e6dfa3d-f07d-4eee-8232-6138133e2fbb\",\"trace_id\":\"41dea86e-0ecc-414e-8303-e60d8ae09976\"}\n[2026-04-27 10:30:52] local.INFO: [SocialAccountService] Refreshing token from provider {\"socialAccountId\":1372,\"provider\":\"hubspot\",\"refreshToken\":\"9aa73948c761da29dce46c177cf9aee1fde483a44169ca38723f9f0597d7a8c4\",\"state\":\"full-refresh\"} {\"correlation_id\":\"2e6dfa3d-f07d-4eee-8232-6138133e2fbb\",\"trace_id\":\"41dea86e-0ecc-414e-8303-e60d8ae09976\"}\n[2026-04-27 10:30:52] local.ERROR: Failed to refresh HubSpot token {\"account_id\":1372,\"updated_at\":\"2025-10-02 14:47:06\",\"reason\":\"missing or invalid refresh token\",\"previous\":\"\"} {\"correlation_id\":\"2e6dfa3d-f07d-4eee-8232-6138133e2fbb\",\"trace_id\":\"41dea86e-0ecc-414e-8303-e60d8ae09976\"}\n[2026-04-27 10:30:52] local.NOTICE: Repairing HubSpot tokens end {\"total\":3,\"fixed\":0,\"failed\":3} {\"correlation_id\":\"2e6dfa3d-f07d-4eee-8232-6138133e2fbb\",\"trace_id\":\"41dea86e-0ecc-414e-8303-e60d8ae09976\"}\n[2026-04-27 10:30:56] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage before starting command {\"command\":\"jiminny:transcription:retry-failed\",\"memoryBeforeCommandInMb\":60.0,\"memoryPeakBeforeCommandInMb\":99.727} {\"correlation_id\":\"a6655a64-38ad-4c65-96f1-f5a294dbb343\",\"trace_id\":\"52cdca32-c0c8-4215-81a2-4263bbde2158\"}\n[2026-04-27 10:30:56] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage before starting command {\"command\":\"conference:pre-meeting-reminder\",\"memoryBeforeCommandInMb\":60.0,\"memoryPeakBeforeCommandInMb\":99.727} {\"correlation_id\":\"ec2d49dd-ab36-4f4e-8a60-7bf5ea384cac\",\"trace_id\":\"b3e89960-9a27-4bf7-8b55-95f5704f9a10\"}\n[2026-04-27 10:30:57] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage for command {\"command\":\"jiminny:transcription:retry-failed\",\"memoryBeforeCommandInMb\":60.0,\"memoryAfterCommandInMB\":62.0,\"memoryPeakBeforeCommandInMb\":99.727,\"memoryPeakAfterCommandInMB\":99.727} {\"correlation_id\":\"a6655a64-38ad-4c65-96f1-f5a294dbb343\",\"trace_id\":\"52cdca32-c0c8-4215-81a2-4263bbde2158\"}\n[2026-04-27 10:30:57] local.INFO: [HubSpot Journal Polling] Getting offset from database {\"offset\":\"\",\"jiminny_team_id\":1} {\"correlation_id\":\"1f928e93-b17e-4518-a1b9-a8280d1d95e8\",\"trace_id\":\"ba203ed6-fe98-4fd7-aa62-8de9fb8ffeca\"}\n[2026-04-27 10:30:57] local.INFO: [HubSpot Journal Command] Starting polling service {\"correlation_id\":\"1f928e93-b17e-4518-a1b9-a8280d1d95e8\",\"trace_id\":\"ba203ed6-fe98-4fd7-aa62-8de9fb8ffeca\"}\n[2026-04-27 10:30:57] local.INFO: [HubSpot Journal Polling] Service starting {\"memory_limit\":\"256M\",\"max_execution_time\":\"0\",\"initial_memory_mb\":62.0} {\"correlation_id\":\"1f928e93-b17e-4518-a1b9-a8280d1d95e8\",\"trace_id\":\"ba203ed6-fe98-4fd7-aa62-8de9fb8ffeca\"}\n[2026-04-27 10:30:57] local.INFO: [HubSpot Journal Polling] Acquired polling lock {\"expires_at\":\"2026-04-27T10:32:57.492763Z\"} {\"correlation_id\":\"1f928e93-b17e-4518-a1b9-a8280d1d95e8\",\"trace_id\":\"ba203ed6-fe98-4fd7-aa62-8de9fb8ffeca\"}\n[2026-04-27 10:30:57] local.INFO: [HubSpot Journal Polling] Getting offset from database {\"offset\":\"\",\"jiminny_team_id\":1} {\"correlation_id\":\"1f928e93-b17e-4518-a1b9-a8280d1d95e8\",\"trace_id\":\"ba203ed6-fe98-4fd7-aa62-8de9fb8ffeca\"}\n[2026-04-27 10:30:57] local.INFO: [HubSpot Journal API] Fetching latest journal entry {\"url\":\"https://api.hubapi.com/webhooks/v4/journal/latest\"} {\"correlation_id\":\"1f928e93-b17e-4518-a1b9-a8280d1d95e8\",\"trace_id\":\"ba203ed6-fe98-4fd7-aa62-8de9fb8ffeca\"}\n[2026-04-27 10:30:57] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage for command {\"command\":\"conference:pre-meeting-reminder\",\"memoryBeforeCommandInMb\":60.0,\"memoryAfterCommandInMB\":62.0,\"memoryPeakBeforeCommandInMb\":99.727,\"memoryPeakAfterCommandInMB\":99.727} {\"correlation_id\":\"ec2d49dd-ab36-4f4e-8a60-7bf5ea384cac\",\"trace_id\":\"b3e89960-9a27-4bf7-8b55-95f5704f9a10\"}\n[2026-04-27 10:30:58] local.INFO: [HubSpot Journal Polling] No data {\"correlation_id\":\"1f928e93-b17e-4518-a1b9-a8280d1d95e8\",\"trace_id\":\"ba203ed6-fe98-4fd7-aa62-8de9fb8ffeca\"}\n[2026-04-27 10:31:02] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage before starting command {\"command\":\"crm:reset-governor\",\"memoryBeforeCommandInMb\":60.0,\"memoryPeakBeforeCommandInMb\":99.727} {\"correlation_id\":\"f261cd75-c916-4f21-b4f4-856fe0ed315c\",\"trace_id\":\"6db8819d-ffaa-4fe8-8ccd-0dcc326b8cdc\"}\n[2026-04-27 10:31:02] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage for command {\"command\":\"crm:reset-governor\",\"memoryBeforeCommandInMb\":60.0,\"memoryAfterCommandInMB\":62.0,\"memoryPeakBeforeCommandInMb\":99.727,\"memoryPeakAfterCommandInMB\":99.727} {\"correlation_id\":\"f261cd75-c916-4f21-b4f4-856fe0ed315c\",\"trace_id\":\"6db8819d-ffaa-4fe8-8ccd-0dcc326b8cdc\"}\n[2026-04-27 10:31:03] local.INFO: [HubSpot Journal Polling] Getting offset from database {\"offset\":\"\",\"jiminny_team_id\":1} {\"correlation_id\":\"1f928e93-b17e-4518-a1b9-a8280d1d95e8\",\"trace_id\":\"ba203ed6-fe98-4fd7-aa62-8de9fb8ffeca\"}\n[2026-04-27 10:31:03] local.INFO: [HubSpot Journal API] Fetching latest journal entry {\"url\":\"https://api.hubapi.com/webhooks/v4/journal/latest\"} {\"correlation_id\":\"1f928e93-b17e-4518-a1b9-a8280d1d95e8\",\"trace_id\":\"ba203ed6-fe98-4fd7-aa62-8de9fb8ffeca\"}\n[2026-04-27 10:31:03] local.INFO: [HubSpot Journal Polling] No data {\"correlation_id\":\"1f928e93-b17e-4518-a1b9-a8280d1d95e8\",\"trace_id\":\"ba203ed6-fe98-4fd7-aa62-8de9fb8ffeca\"}\n[2026-04-27 10:31:06] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage before starting command {\"command\":\"activity:sync\",\"memoryBeforeCommandInMb\":60.0,\"memoryPeakBeforeCommandInMb\":99.727} {\"correlation_id\":\"f9151ed9-60aa-4b2b-b962-9eab5f75405b\",\"trace_id\":\"dba432b8-cb4b-4e76-8684-01d96f91e453\"}\n[2026-04-27 10:31:07] local.INFO: Dispatching activity sync job {\"import_id\":811739,\"provider\":\"twilio-flex\",\"team\":\"jiminny\"} {\"correlation_id\":\"f9151ed9-60aa-4b2b-b962-9eab5f75405b\",\"trace_id\":\"dba432b8-cb4b-4e76-8684-01d96f91e453\"}\n[2026-04-27 10:31:07] local.INFO: Dispatching activity sync job {\"import_id\":811740,\"provider\":\"xant\",\"team\":\"jiminny\"} {\"correlation_id\":\"f9151ed9-60aa-4b2b-b962-9eab5f75405b\",\"trace_id\":\"dba432b8-cb4b-4e76-8684-01d96f91e453\"}\n[2026-04-27 10:31:07] local.INFO: Dispatching activity sync job {\"import_id\":811741,\"provider\":\"apollo\",\"team\":\"jiminny\"} {\"correlation_id\":\"f9151ed9-60aa-4b2b-b962-9eab5f75405b\",\"trace_id\":\"dba432b8-cb4b-4e76-8684-01d96f91e453\"}\n[2026-04-27 10:31:07] local.INFO: Dispatching activity sync job {\"import_id\":811742,\"provider\":\"groove\",\"team\":\"jiminny\"} {\"correlation_id\":\"f9151ed9-60aa-4b2b-b962-9eab5f75405b\",\"trace_id\":\"dba432b8-cb4b-4e76-8684-01d96f91e453\"}\n[2026-04-27 10:31:07] local.INFO: Dispatching activity sync job {\"import_id\":811743,\"provider\":\"twilio-video\",\"team\":\"jiminny\"} {\"correlation_id\":\"f9151ed9-60aa-4b2b-b962-9eab5f75405b\",\"trace_id\":\"dba432b8-cb4b-4e76-8684-01d96f91e453\"}\n[2026-04-27 10:31:07] local.INFO: Dispatching activity sync job {\"import_id\":811744,\"provider\":\"hubspot\",\"team\":\"hubspot\"} {\"correlation_id\":\"f9151ed9-60aa-4b2b-b962-9eab5f75405b\",\"trace_id\":\"dba432b8-cb4b-4e76-8684-01d96f91e453\"}\n[2026-04-27 10:31:07] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage for command {\"command\":\"activity:sync\",\"memoryBeforeCommandInMb\":60.0,\"memoryAfterCommandInMB\":62.0,\"memoryPeakBeforeCommandInMb\":99.727,\"memoryPeakAfterCommandInMB\":99.727} {\"correlation_id\":\"f9151ed9-60aa-4b2b-b962-9eab5f75405b\",\"trace_id\":\"dba432b8-cb4b-4e76-8684-01d96f91e453\"}\n[2026-04-27 10:31:07] local.WARNING: [Salesforce] Account not connected for user {\"userId\":\"cdf8b554-d951-4758-bc2b-c1b85d1cd0b9\",\"account\":null} {\"correlation_id\":\"e7a521c5-fad4-48c2-b880-d572b5d237f4\",\"trace_id\":\"dba432b8-cb4b-4e76-8684-01d96f91e453\"}\n[2026-04-27 10:31:07] local.INFO: [CrmOwnerResolver] Integration owner is not connected, attempting team members {\"crm_provider\":\"salesforce\",\"crm_owner\":3,\"team_id\":1} {\"correlation_id\":\"e7a521c5-fad4-48c2-b880-d572b5d237f4\",\"trace_id\":\"dba432b8-cb4b-4e76-8684-01d96f91e453\"}\n[2026-04-27 10:31:07] local.INFO: [SocialAccountService] Fetching token {\"socialAccountId\":1500,\"provider\":\"salesforce\"} {\"correlation_id\":\"e7a521c5-fad4-48c2-b880-d572b5d237f4\",\"trace_id\":\"dba432b8-cb4b-4e76-8684-01d96f91e453\"}\n[2026-04-27 10:31:07] local.INFO: [SocialAccountService] Token retrieved {\"socialAccountId\":1500,\"provider\":\"salesforce\"} {\"correlation_id\":\"e7a521c5-fad4-48c2-b880-d572b5d237f4\",\"trace_id\":\"dba432b8-cb4b-4e76-8684-01d96f91e453\"}\n[2026-04-27 10:31:07] local.INFO: [EncryptedTokenManager] Generating access token. {\"mode\":\"legacy\"} {\"correlation_id\":\"e7a521c5-fad4-48c2-b880-d572b5d237f4\",\"trace_id\":\"dba432b8-cb4b-4e76-8684-01d96f91e453\"}\n[2026-04-27 10:31:07] local.INFO: [CrmOwnerResolver] TeamMember found with active crm connection {\"crm_provider\":\"salesforce\",\"crm_owner\":143,\"team_id\":1} {\"correlation_id\":\"e7a521c5-fad4-48c2-b880-d572b5d237f4\",\"trace_id\":\"dba432b8-cb4b-4e76-8684-01d96f91e453\"}\n[2026-04-27 10:31:07] local.INFO: [SocialAccountService] Fetching token {\"socialAccountId\":1194,\"provider\":\"twilio-flex\"} {\"correlation_id\":\"e7a521c5-fad4-48c2-b880-d572b5d237f4\",\"trace_id\":\"dba432b8-cb4b-4e76-8684-01d96f91e453\"}\n[2026-04-27 10:31:07] local.INFO: [SocialAccountService] Token retrieved {\"socialAccountId\":1194,\"provider\":\"twilio-flex\"} {\"correlation_id\":\"e7a521c5-fad4-48c2-b880-d572b5d237f4\",\"trace_id\":\"dba432b8-cb4b-4e76-8684-01d96f91e453\"}\n[2026-04-27 10:31:07] local.INFO: [EncryptedTokenManager] Generating access token. {\"mode\":\"legacy\"} {\"correlation_id\":\"e7a521c5-fad4-48c2-b880-d572b5d237f4\",\"trace_id\":\"dba432b8-cb4b-4e76-8684-01d96f91e453\"}\n[2026-04-27 10:31:07] local.INFO: [SyncActivity] Start {\"import_id\":811739,\"provider\":\"twilio-flex\",\"provider_id\":317,\"team\":\"jiminny\",\"team_id\":1} {\"correlation_id\":\"e7a521c5-fad4-48c2-b880-d572b5d237f4\",\"trace_id\":\"dba432b8-cb4b-4e76-8684-01d96f91e453\"}\n[2026-04-27 10:31:07] local.NOTICE: [TwilioFlex] Calls import start {\"correlation_id\":\"e7a521c5-fad4-48c2-b880-d572b5d237f4\",\"trace_id\":\"dba432b8-cb4b-4e76-8684-01d96f91e453\"}\n[2026-04-27 10:31:08] local.ALERT: [SyncActivity] Failed {\"import_id\":811739,\"provider\":\"twilio-flex\",\"provider_id\":317,\"team\":\"jiminny\",\"team_id\":1,\"reason\":\"[HTTP 401] Unable to fetch page: Authenticate\",\"file\":\"/home/jiminny/vendor/twilio/sdk/src/Twilio/Page.php\",\"line\":60} {\"correlation_id\":\"e7a521c5-fad4-48c2-b880-d572b5d237f4\",\"trace_id\":\"dba432b8-cb4b-4e76-8684-01d96f91e453\"}\n[2026-04-27 10:31:08] local.INFO: [HubSpot Journal Polling] Getting offset from database {\"offset\":\"\",\"jiminny_team_id\":1} {\"correlation_id\":\"1f928e93-b17e-4518-a1b9-a8280d1d95e8\",\"trace_id\":\"ba203ed6-fe98-4fd7-aa62-8de9fb8ffeca\"}\n[2026-04-27 10:31:08] local.INFO: [HubSpot Journal API] Fetching latest journal entry {\"url\":\"https://api.hubapi.com/webhooks/v4/journal/latest\"} {\"correlation_id\":\"1f928e93-b17e-4518-a1b9-a8280d1d95e8\",\"trace_id\":\"ba203ed6-fe98-4fd7-aa62-8de9fb8ffeca\"}\n[2026-04-27 10:31:09] local.INFO: [SocialAccountService] Fetching token {\"socialAccountId\":1500,\"provider\":\"salesforce\"} {\"correlation_id\":\"73848601-79bb-4161-91ae-37acf4385d24\",\"trace_id\":\"dba432b8-cb4b-4e76-8684-01d96f91e453\"}\n[2026-04-27 10:31:09] local.INFO: [SocialAccountService] Token retrieved {\"socialAccountId\":1500,\"provider\":\"salesforce\"} {\"correlation_id\":\"73848601-79bb-4161-91ae-37acf4385d24\",\"trace_id\":\"dba432b8-cb4b-4e76-8684-01d96f91e453\"}\n[2026-04-27 10:31:09] local.INFO: [EncryptedTokenManager] Generating access token. {\"mode\":\"legacy\"} {\"correlation_id\":\"73848601-79bb-4161-91ae-37acf4385d24\",\"trace_id\":\"dba432b8-cb4b-4e76-8684-01d96f91e453\"}\n[2026-04-27 10:31:09] local.INFO: [CrmOwnerResolver] Integration owner matched as CRM Owner {\"crm_provider\":\"salesforce\",\"crm_owner\":143,\"team_id\":1} {\"correlation_id\":\"73848601-79bb-4161-91ae-37acf4385d24\",\"trace_id\":\"dba432b8-cb4b-4e76-8684-01d96f91e453\"}\n[2026-04-27 10:31:09] local.INFO: [SocialAccountService] Fetching token {\"socialAccountId\":1500,\"provider\":\"salesforce\"} {\"correlation_id\":\"73848601-79bb-4161-91ae-37acf4385d24\",\"trace_id\":\"dba432b8-cb4b-4e76-8684-01d96f91e453\"}\n[2026-04-27 10:31:09] local.INFO: [HubSpot Journal Polling] No data {\"correlation_id\":\"1f928e93-b17e-4518-a1b9-a8280d1d95e8\",\"trace_id\":\"ba203ed6-fe98-4fd7-aa62-8de9fb8ffeca\"}\n[2026-04-27 10:31:09] local.INFO: [SocialAccountService] Token retrieved {\"socialAccountId\":1500,\"provider\":\"salesforce\"} {\"correlation_id\":\"73848601-79bb-4161-91ae-37acf4385d24\",\"trace_id\":\"dba432b8-cb4b-4e76-8684-01d96f91e453\"}\n[2026-04-27 10:31:09] local.INFO: [EncryptedTokenManager] Generating access token. {\"mode\":\"legacy\"} {\"correlation_id\":\"73848601-79bb-4161-91ae-37acf4385d24\",\"trace_id\":\"dba432b8-cb4b-4e76-8684-01d96f91e453\"}\n[2026-04-27 10:31:09] local.INFO: [SyncActivity] Start {\"import_id\":811740,\"provider\":\"xant\",\"provider_id\":161,\"team\":\"jiminny\",\"team_id\":1} {\"correlation_id\":\"73848601-79bb-4161-91ae-37acf4385d24\",\"trace_id\":\"dba432b8-cb4b-4e76-8684-01d96f91e453\"}\n[2026-04-27 10:31:09] local.INFO: [Salesforce] Performing query {\"query\":\"\n SELECT Playbooks_Call_Date__c,Playbooks_Call_Recording__c,CreatedDate,TaskSubtype,CallType,CallDurationInSeconds,Id,OwnerId,WhoId,WhatId,Priority,ActivityDate,Subject,Description,Status,Type\n FROM Task\n WHERE IsDeleted = false\n AND LastModifiedDate >= :from\n AND LastModifiedDate <= :to\n ORDER BY LastModifiedDate ASC\n LIMIT :limit\",\"params\":{\"from\":\"2026-04-27T10:14:00Z\",\"to\":\"2026-04-27T10:30:00Z\",\"ownerId\":null,\"subType\":null,\"limit\":5000}} {\"correlation_id\":\"73848601-79bb-4161-91ae-37acf4385d24\",\"trace_id\":\"dba432b8-cb4b-4e76-8684-01d96f91e453\"}\n[2026-04-27 10:31:09] local.INFO: [Salesforce] Sending request {\"endpoint\":\"https://jiminny--stagingenv.sandbox.my.salesforce.com/services/data/v50.0/query/?q=%0A++++++++++++SELECT+Playbooks_Call_Date__c%2CPlaybooks_Call_Recording__c%2CCreatedDate%2CTaskSubtype%2CCallType%2CCallDurationInSeconds%2CId%2COwnerId%2CWhoId%2CWhatId%2CPriority%2CActivityDate%2CSubject%2CDescription%2CStatus%2CType%0A++++++++++++++FROM+Task%0A+++++++++++++WHERE+IsDeleted+%3D+false%0A+++++++++++++++AND+LastModifiedDate+%3E%3D+2026-04-27T10%3A14%3A00Z%0A+++++++++++++++AND+LastModifiedDate+%3C%3D+2026-04-27T10%3A30%3A00Z%0A++++++++++ORDER+BY+LastModifiedDate+ASC%0A+++++++++++++LIMIT+5000 GET\",\"team_id\":1} {\"correlation_id\":\"73848601-79bb-4161-91ae-37acf4385d24\",\"trace_id\":\"dba432b8-cb4b-4e76-8684-01d96f91e453\"}\n[2026-04-27 10:31:09] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage before starting command {\"command\":\"mailbox:batch:fail-stalled\",\"memoryBeforeCommandInMb\":60.0,\"memoryPeakBeforeCommandInMb\":99.727} {\"correlation_id\":\"0d30446d-c469-4ade-80dc-f57f997ea610\",\"trace_id\":\"61eea326-3f40-4a50-a5e6-bddd7fa25d27\"}\n[2026-04-27 10:31:09] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage for command {\"command\":\"mailbox:batch:fail-stalled\",\"memoryBeforeCommandInMb\":60.0,\"memoryAfterCommandInMB\":62.0,\"memoryPeakBeforeCommandInMb\":99.727,\"memoryPeakAfterCommandInMB\":99.727} {\"correlation_id\":\"0d30446d-c469-4ade-80dc-f57f997ea610\",\"trace_id\":\"61eea326-3f40-4a50-a5e6-bddd7fa25d27\"}\n[2026-04-27 10:31:09] local.INFO: [Xant (InsideSales)] No calls found. {\"correlation_id\":\"73848601-79bb-4161-91ae-37acf4385d24\",\"trace_id\":\"dba432b8-cb4b-4e76-8684-01d96f91e453\"}\n[2026-04-27 10:31:09] local.INFO: [SyncActivity] End {\"import_id\":811740,\"provider\":\"xant\",\"provider_id\":161,\"team\":\"jiminny\",\"team_id\":1} {\"correlation_id\":\"73848601-79bb-4161-91ae-37acf4385d24\",\"trace_id\":\"dba432b8-cb4b-4e76-8684-01d96f91e453\"}\n[2026-04-27 10:31:09] local.INFO: [SyncActivity] Memory usage {\"import_id\":811740,\"provider\":\"xant\",\"provider_id\":161,\"team\":\"jiminny\",\"team_id\":1,\"memory_usage\":25149472,\"memory_real_usage\":65011712,\"pid\":13616} {\"correlation_id\":\"73848601-79bb-4161-91ae-37acf4385d24\",\"trace_id\":\"dba432b8-cb4b-4e76-8684-01d96f91e453\"}\n[2026-04-27 10:31:09] local.INFO: [SocialAccountService] Fetching token {\"socialAccountId\":1500,\"provider\":\"salesforce\"} {\"correlation_id\":\"11c977fb-9603-4dde-a4d7-f821d47fcdc9\",\"trace_id\":\"dba432b8-cb4b-4e76-8684-01d96f91e453\"}\n[2026-04-27 10:31:09] local.INFO: [SocialAccountService] Token retrieved {\"socialAccountId\":1500,\"provider\":\"salesforce\"} {\"correlation_id\":\"11c977fb-9603-4dde-a4d7-f821d47fcdc9\",\"trace_id\":\"dba432b8-cb4b-4e76-8684-01d96f91e453\"}\n[2026-04-27 10:31:09] local.INFO: [EncryptedTokenManager] Generating access token. {\"mode\":\"legacy\"} {\"correlation_id\":\"11c977fb-9603-4dde-a4d7-f821d47fcdc9\",\"trace_id\":\"dba432b8-cb4b-4e76-8684-01d96f91e453\"}\n[2026-04-27 10:31:09] local.INFO: [CrmOwnerResolver] Integration owner matched as CRM Owner {\"crm_provider\":\"salesforce\",\"crm_owner\":143,\"team_id\":1} {\"correlation_id\":\"11c977fb-9603-4dde-a4d7-f821d47fcdc9\",\"trace_id\":\"dba432b8-cb4b-4e76-8684-01d96f91e453\"}\n[2026-04-27 10:31:09] local.INFO: [SocialAccountService] Fetching token {\"socialAccountId\":1500,\"provider\":\"salesforce\"} {\"correlation_id\":\"11c977fb-9603-4dde-a4d7-f821d47fcdc9\",\"trace_id\":\"dba432b8-cb4b-4e76-8684-01d96f91e453\"}\n[2026-04-27 10:31:09] local.INFO: [SocialAccountService] Token retrieved {\"socialAccountId\":1500,\"provider\":\"salesforce\"} {\"correlation_id\":\"11c977fb-9603-4dde-a4d7-f821d47fcdc9\",\"trace_id\":\"dba432b8-cb4b-4e76-8684-01d96f91e453\"}\n[2026-04-27 10:31:09] local.INFO: [EncryptedTokenManager] Generating access token. {\"mode\":\"legacy\"} {\"correlation_id\":\"11c977fb-9603-4dde-a4d7-f821d47fcdc9\",\"trace_id\":\"dba432b8-cb4b-4e76-8684-01d96f91e453\"}\n[2026-04-27 10:31:09] local.INFO: [SyncActivity] Start {\"import_id\":811741,\"provider\":\"apollo\",\"provider_id\":441,\"team\":\"jiminny\",\"team_id\":1} {\"correlation_id\":\"11c977fb-9603-4dde-a4d7-f821d47fcdc9\",\"trace_id\":\"dba432b8-cb4b-4e76-8684-01d96f91e453\"}\n[2026-04-27 10:31:09] local.INFO: [Salesforce] Performing query {\"query\":\"\n SELECT AccountId,CreatedDate,TaskSubtype,CallType,Id,OwnerId,WhoId,WhatId,Priority,ActivityDate,Subject,Description,Status,Type\n FROM Task\n WHERE IsDeleted = false\n AND LastModifiedDate >= :from\n AND LastModifiedDate <= :to\n ORDER BY LastModifiedDate ASC\n LIMIT :limit\",\"params\":{\"from\":\"2026-04-27T10:14:00Z\",\"to\":\"2026-04-27T10:30:00Z\",\"ownerId\":null,\"subType\":null,\"limit\":5000}} {\"correlation_id\":\"11c977fb-9603-4dde-a4d7-f821d47fcdc9\",\"trace_id\":\"dba432b8-cb4b-4e76-8684-01d96f91e453\"}\n[2026-04-27 10:31:09] local.INFO: [Salesforce] Sending request {\"endpoint\":\"https://jiminny--stagingenv.sandbox.my.salesforce.com/services/data/v50.0/query/?q=%0A++++++++++++SELECT+AccountId%2CCreatedDate%2CTaskSubtype%2CCallType%2CId%2COwnerId%2CWhoId%2CWhatId%2CPriority%2CActivityDate%2CSubject%2CDescription%2CStatus%2CType%0A++++++++++++++FROM+Task%0A+++++++++++++WHERE+IsDeleted+%3D+false%0A+++++++++++++++AND+LastModifiedDate+%3E%3D+2026-04-27T10%3A14%3A00Z%0A+++++++++++++++AND+LastModifiedDate+%3C%3D+2026-04-27T10%3A30%3A00Z%0A++++++++++ORDER+BY+LastModifiedDate+ASC%0A+++++++++++++LIMIT+5000 GET\",\"team_id\":1} {\"correlation_id\":\"11c977fb-9603-4dde-a4d7-f821d47fcdc9\",\"trace_id\":\"dba432b8-cb4b-4e76-8684-01d96f91e453\"}\n[2026-04-27 10:31:10] local.INFO: [Apollo] No calls found. {\"correlation_id\":\"11c977fb-9603-4dde-a4d7-f821d47fcdc9\",\"trace_id\":\"dba432b8-cb4b-4e76-8684-01d96f91e453\"}\n[2026-04-27 10:31:10] local.INFO: [SyncActivity] End {\"import_id\":811741,\"provider\":\"apollo\",\"provider_id\":441,\"team\":\"jiminny\",\"team_id\":1} {\"correlation_id\":\"11c977fb-9603-4dde-a4d7-f821d47fcdc9\",\"trace_id\":\"dba432b8-cb4b-4e76-8684-01d96f91e453\"}\n[2026-04-27 10:31:10] local.INFO: [SyncActivity] Memory usage {\"import_id\":811741,\"provider\":\"apollo\",\"provider_id\":441,\"team\":\"jiminny\",\"team_id\":1,\"memory_usage\":25527752,\"memory_real_usage\":65011712,\"pid\":13616} {\"correlation_id\":\"11c977fb-9603-4dde-a4d7-f821d47fcdc9\",\"trace_id\":\"dba432b8-cb4b-4e76-8684-01d96f91e453\"}\n[2026-04-27 10:31:10] local.INFO: [SocialAccountService] Fetching token {\"socialAccountId\":1500,\"provider\":\"salesforce\"} {\"correlation_id\":\"856f8092-8c5e-409d-8aa6-ffb8efd8bfa1\",\"trace_id\":\"dba432b8-cb4b-4e76-8684-01d96f91e453\"}\n[2026-04-27 10:31:10] local.INFO: [SocialAccountService] Token retrieved {\"socialAccountId\":1500,\"provider\":\"salesforce\"} {\"correlation_id\":\"856f8092-8c5e-409d-8aa6-ffb8efd8bfa1\",\"trace_id\":\"dba432b8-cb4b-4e76-8684-01d96f91e453\"}\n[2026-04-27 10:31:10] local.INFO: [EncryptedTokenManager] Generating access token. {\"mode\":\"legacy\"} {\"correlation_id\":\"856f8092-8c5e-409d-8aa6-ffb8efd8bfa1\",\"trace_id\":\"dba432b8-cb4b-4e76-8684-01d96f91e453\"}\n[2026-04-27 10:31:10] local.INFO: [CrmOwnerResolver] Integration owner matched as CRM Owner {\"crm_provider\":\"salesforce\",\"crm_owner\":143,\"team_id\":1} {\"correlation_id\":\"856f8092-8c5e-409d-8aa6-ffb8efd8bfa1\",\"trace_id\":\"dba432b8-cb4b-4e76-8684-01d96f91e453\"}\n[2026-04-27 10:31:10] local.INFO: [SocialAccountService] Fetching token {\"socialAccountId\":1500,\"provider\":\"salesforce\"} {\"correlation_id\":\"856f8092-8c5e-409d-8aa6-ffb8efd8bfa1\",\"trace_id\":\"dba432b8-cb4b-4e76-8684-01d96f91e453\"}\n[2026-04-27 10:31:10] local.INFO: [SocialAccountService] Token retrieved {\"socialAccountId\":1500,\"provider\":\"salesforce\"} {\"correlation_id\":\"856f8092-8c5e-409d-8aa6-ffb8efd8bfa1\",\"trace_id\":\"dba432b8-cb4b-4e76-8684-01d96f91e453\"}\n[2026-04-27 10:31:10] local.INFO: [EncryptedTokenManager] Generating access token. {\"mode\":\"legacy\"} {\"correlation_id\":\"856f8092-8c5e-409d-8aa6-ffb8efd8bfa1\",\"trace_id\":\"dba432b8-cb4b-4e76-8684-01d96f91e453\"}\n[2026-04-27 10:31:10] local.INFO: [SyncActivity] Start {\"import_id\":811742,\"provider\":\"groove\",\"provider_id\":228,\"team\":\"jiminny\",\"team_id\":1} {\"correlation_id\":\"856f8092-8c5e-409d-8aa6-ffb8efd8bfa1\",\"trace_id\":\"dba432b8-cb4b-4e76-8684-01d96f91e453\"}\n[2026-04-27 10:31:10] local.INFO: [Salesforce] Performing query {\"query\":\"\n SELECT call_recording_url__c,TaskSubtype,CreatedDate,CallType,CallDurationInSeconds,Id,OwnerId,WhoId,WhatId,Priority,ActivityDate,Subject,Description,Status,Type\n FROM Task\n WHERE IsDeleted = false\n AND LastModifiedDate >= :from\n AND LastModifiedDate <= :to\n ORDER BY LastModifiedDate ASC\n LIMIT :limit\",\"params\":{\"from\":\"2026-04-27T10:14:00Z\",\"to\":\"2026-04-27T10:30:00Z\",\"ownerId\":null,\"subType\":null,\"limit\":5000}} {\"correlation_id\":\"856f8092-8c5e-409d-8aa6-ffb8efd8bfa1\",\"trace_id\":\"dba432b8-cb4b-4e76-8684-01d96f91e453\"}\n[2026-04-27 10:31:10] local.INFO: [Salesforce] Sending request {\"endpoint\":\"https://jiminny--stagingenv.sandbox.my.salesforce.com/services/data/v50.0/query/?q=%0A++++++++++++SELECT+call_recording_url__c%2CTaskSubtype%2CCreatedDate%2CCallType%2CCallDurationInSeconds%2CId%2COwnerId%2CWhoId%2CWhatId%2CPriority%2CActivityDate%2CSubject%2CDescription%2CStatus%2CType%0A++++++++++++++FROM+Task%0A+++++++++++++WHERE+IsDeleted+%3D+false%0A+++++++++++++++AND+LastModifiedDate+%3E%3D+2026-04-27T10%3A14%3A00Z%0A+++++++++++++++AND+LastModifiedDate+%3C%3D+2026-04-27T10%3A30%3A00Z%0A++++++++++ORDER+BY+LastModifiedDate+ASC%0A+++++++++++++LIMIT+5000 GET\",\"team_id\":1} {\"correlation_id\":\"856f8092-8c5e-409d-8aa6-ffb8efd8bfa1\",\"trace_id\":\"dba432b8-cb4b-4e76-8684-01d96f91e453\"}\n[2026-04-27 10:31:10] local.ERROR: [Salesforce] Request exception [400] \nSELECT call_recording_url__c,TaskSubtype\n ^\nERROR at Row:1:Column:8\nNo such column 'call_recording_url__c' on entity 'Task'. If you are attempting to use a custom field, be sure to append the '__c' after the custom field name. Please reference your WSDL or the describe call for the appropriate names. {\"url\":\"https://jiminny--stagingenv.sandbox.my.salesforce.com/services/data/v50.0/query/?q=%0A++++++++++++SELECT+call_recording_url__c%2CTaskSubtype%2CCreatedDate%2CCallType%2CCallDurationInSeconds%2CId%2COwnerId%2CWhoId%2CWhatId%2CPriority%2CActivityDate%2CSubject%2CDescription%2CStatus%2CType%0A++++++++++++++FROM+Task%0A+++++++++++++WHERE+IsDeleted+%3D+false%0A+++++++++++++++AND+LastModifiedDate+%3E%3D+2026-04-27T10%3A14%3A00Z%0A+++++++++++++++AND+LastModifiedDate+%3C%3D+2026-04-27T10%3A30%3A00Z%0A++++++++++ORDER+BY+LastModifiedDate+ASC%0A+++++++++++++LIMIT+5000\",\"data\":{\"headers\":{\"Authorization\":\"Bearer 00D2g0000008hH4!AQEAQC41CLa_xP1bAbi9E2OUbAOpW..Wx961OtqTwJ3oWpOKs6yPdm47B4yKRBHbRmmJ.Lvd34sjr0gnAkV8fLb8IZWzIW93\"}},\"response\":{\"GuzzleHttp\\\\Psr7\\\\Stream\":\"[{\\\"message\\\":\\\"\\\\nSELECT call_recording_url__c,TaskSubtype\\\\n ^\\\\nERROR at Row:1:Column:8\\\\nNo such column 'call_recording_url__c' on entity 'Task'. If you are attempting to use a custom field, be sure to append the '__c' after the custom field name. Please reference your WSDL or the describe call for the appropriate names.\\\",\\\"errorCode\\\":\\\"INVALID_FIELD\\\"}]\"},\"fields\":[]} {\"correlation_id\":\"856f8092-8c5e-409d-8aa6-ffb8efd8bfa1\",\"trace_id\":\"dba432b8-cb4b-4e76-8684-01d96f91e453\"}\n[2026-04-27 10:31:10] local.ALERT: [SyncActivity] Failed {\"import_id\":811742,\"provider\":\"groove\",\"provider_id\":228,\"team\":\"jiminny\",\"team_id\":1,\"reason\":\"\nSELECT call_recording_url__c,TaskSubtype\n ^\nERROR at Row:1:Column:8\nNo such column 'call_recording_url__c' on entity 'Task'. If you are attempting to use a custom field, be sure to append the '__c' after the custom field name. Please reference your WSDL or the describe call for the appropriate names.\",\"file\":\"/home/jiminny/app/Services/Crm/Salesforce/Client.php\",\"line\":564} {\"correlation_id\":\"856f8092-8c5e-409d-8aa6-ffb8efd8bfa1\",\"trace_id\":\"dba432b8-cb4b-4e76-8684-01d96f91e453\"}\n[2026-04-27 10:31:10] local.INFO: [SocialAccountService] Fetching token {\"socialAccountId\":1500,\"provider\":\"salesforce\"} {\"correlation_id\":\"0ec6aef6-345c-4602-b6a1-646b95e481c0\",\"trace_id\":\"dba432b8-cb4b-4e76-8684-01d96f91e453\"}\n[2026-04-27 10:31:10] local.INFO: [SocialAccountService] Token retrieved {\"socialAccountId\":1500,\"provider\":\"salesforce\"} {\"correlation_id\":\"0ec6aef6-345c-4602-b6a1-646b95e481c0\",\"trace_id\":\"dba432b8-cb4b-4e76-8684-01d96f91e453\"}\n[2026-04-27 10:31:10] local.INFO: [EncryptedTokenManager] Generating access token. {\"mode\":\"legacy\"} {\"correlation_id\":\"0ec6aef6-345c-4602-b6a1-646b95e481c0\",\"trace_id\":\"dba432b8-cb4b-4e76-8684-01d96f91e453\"}\n[2026-04-27 10:31:10] local.INFO: [CrmOwnerResolver] Integration owner matched as CRM Owner {\"crm_provider\":\"salesforce\",\"crm_owner\":143,\"team_id\":1} {\"correlation_id\":\"0ec6aef6-345c-4602-b6a1-646b95e481c0\",\"trace_id\":\"dba432b8-cb4b-4e76-8684-01d96f91e453\"}\n[2026-04-27 10:31:10] local.INFO: [SocialAccountService] Fetching token {\"socialAccountId\":1500,\"provider\":\"salesforce\"} {\"correlation_id\":\"0ec6aef6-345c-4602-b6a1-646b95e481c0\",\"trace_id\":\"dba432b8-cb4b-4e76-8684-01d96f91e453\"}\n[2026-04-27 10:31:10] local.INFO: [SocialAccountService] Token retrieved {\"socialAccountId\":1500,\"provider\":\"salesforce\"} {\"correlation_id\":\"0ec6aef6-345c-4602-b6a1-646b95e481c0\",\"trace_id\":\"dba432b8-cb4b-4e76-8684-01d96f91e453\"}\n[2026-04-27 10:31:10] local.INFO: [EncryptedTokenManager] Generating access token. {\"mode\":\"legacy\"} {\"correlation_id\":\"0ec6aef6-345c-4602-b6a1-646b95e481c0\",\"trace_id\":\"dba432b8-cb4b-4e76-8684-01d96f91e453\"}\n[2026-04-27 10:31:10] local.INFO: [SyncActivity] Start {\"import_id\":811743,\"provider\":\"twilio-video\",\"provider_id\":243,\"team\":\"jiminny\",\"team_id\":1} {\"correlation_id\":\"0ec6aef6-345c-4602-b6a1-646b95e481c0\",\"trace_id\":\"dba432b8-cb4b-4e76-8684-01d96f91e453\"}\n[2026-04-27 10:31:10] local.INFO: [Salesforce] Performing query {\"query\":\"SELECT Id,OwnerId,WhoId,WhatId,Priority,ActivityDate,Subject,Description,Status,Type,twilio_call_sid__c,Lead_UUID__c,Opportunity__c\n FROM Task\n WHERE Type = 'Video'\n AND isClosed = true\n AND IsDeleted = false\n AND LastModifiedDate >= :from\n AND twilio_call_sid__c != NULL AND LastModifiedDate <= :to ORDER BY LastModifiedDate ASC\n LIMIT :limit\",\"params\":{\"from\":\"2026-04-27T10:14:00Z\",\"to\":\"2026-04-27T10:30:00Z\",\"ownerId\":null,\"subType\":null,\"limit\":5000}} {\"correlation_id\":\"0ec6aef6-345c-4602-b6a1-646b95e481c0\",\"trace_id\":\"dba432b8-cb4b-4e76-8684-01d96f91e453\"}\n[2026-04-27 10:31:10] local.INFO: [Salesforce] Sending request {\"endpoint\":\"https://jiminny--stagingenv.sandbox.my.salesforce.com/services/data/v50.0/query/?q=SELECT+Id%2COwnerId%2CWhoId%2CWhatId%2CPriority%2CActivityDate%2CSubject%2CDescription%2CStatus%2CType%2Ctwilio_call_sid__c%2CLead_UUID__c%2COpportunity__c%0A++++++++++++++FROM+Task%0A++++++++++++WHERE+Type+%3D+%27Video%27%0A++++++++++++++AND+isClosed+%3D+true%0A++++++++++++++AND+IsDeleted+%3D+false%0A++++++++++++++AND+LastModifiedDate+%3E%3D+2026-04-27T10%3A14%3A00Z%0A++++++++++++++AND+twilio_call_sid__c+%21%3D+NULL+AND+LastModifiedDate+%3C%3D+2026-04-27T10%3A30%3A00Z+ORDER+BY+LastModifiedDate+ASC%0A+++++++++++++LIMIT+5000 GET\",\"team_id\":1} {\"correlation_id\":\"0ec6aef6-345c-4602-b6a1-646b95e481c0\",\"trace_id\":\"dba432b8-cb4b-4e76-8684-01d96f91e453\"}\n[2026-04-27 10:31:11] local.INFO: [Twilio Video] No calls found. {\"correlation_id\":\"0ec6aef6-345c-4602-b6a1-646b95e481c0\",\"trace_id\":\"dba432b8-cb4b-4e76-8684-01d96f91e453\"}\n[2026-04-27 10:31:11] local.INFO: [SyncActivity] End {\"import_id\":811743,\"provider\":\"twilio-video\",\"provider_id\":243,\"team\":\"jiminny\",\"team_id\":1} {\"correlation_id\":\"0ec6aef6-345c-4602-b6a1-646b95e481c0\",\"trace_id\":\"dba432b8-cb4b-4e76-8684-01d96f91e453\"}\n[2026-04-27 10:31:11] local.INFO: [SyncActivity] Memory usage {\"import_id\":811743,\"provider\":\"twilio-video\",\"provider_id\":243,\"team\":\"jiminny\",\"team_id\":1,\"memory_usage\":25679680,\"memory_real_usage\":65011712,\"pid\":13616} {\"correlation_id\":\"0ec6aef6-345c-4602-b6a1-646b95e481c0\",\"trace_id\":\"dba432b8-cb4b-4e76-8684-01d96f91e453\"}\n[2026-04-27 10:31:11] local.INFO: [SocialAccountService] Fetching token {\"socialAccountId\":1499,\"provider\":\"hubspot\"} {\"correlation_id\":\"3b9d7e85-b8c2-4ae2-863e-c343c277afdc\",\"trace_id\":\"dba432b8-cb4b-4e76-8684-01d96f91e453\"}\n[2026-04-27 10:31:11] local.INFO: [SocialAccountService] Token retrieved {\"socialAccountId\":1499,\"provider\":\"hubspot\"} {\"correlation_id\":\"3b9d7e85-b8c2-4ae2-863e-c343c277afdc\",\"trace_id\":\"dba432b8-cb4b-4e76-8684-01d96f91e453\"}\n[2026-04-27 10:31:11] local.INFO: [EncryptedTokenManager] Generating access token. {\"mode\":\"legacy\"} {\"correlation_id\":\"3b9d7e85-b8c2-4ae2-863e-c343c277afdc\",\"trace_id\":\"dba432b8-cb4b-4e76-8684-01d96f91e453\"}\n[2026-04-27 10:31:11] local.INFO: [CrmOwnerResolver] Integration owner matched as CRM Owner {\"crm_provider\":\"hubspot\",\"crm_owner\":89,\"team_id\":2} {\"correlation_id\":\"3b9d7e85-b8c2-4ae2-863e-c343c277afdc\",\"trace_id\":\"dba432b8-cb4b-4e76-8684-01d96f91e453\"}\n[2026-04-27 10:31:11] local.INFO: [SocialAccountService] Fetching token {\"socialAccountId\":408,\"provider\":\"hubspot\"} {\"correlation_id\":\"3b9d7e85-b8c2-4ae2-863e-c343c277afdc\",\"trace_id\":\"dba432b8-cb4b-4e76-8684-01d96f91e453\"}\n[2026-04-27 10:31:11] local.INFO: [SocialAccountService] Token needs refreshing {\"socialAccountId\":408,\"provider\":\"hubspot\"} {\"correlation_id\":\"3b9d7e85-b8c2-4ae2-863e-c343c277afdc\",\"trace_id\":\"dba432b8-cb4b-4e76-8684-01d96f91e453\"}\n[2026-04-27 10:31:11] local.INFO: [EncryptedTokenManager] Generating access token. {\"mode\":\"legacy\"} {\"correlation_id\":\"3b9d7e85-b8c2-4ae2-863e-c343c277afdc\",\"trace_id\":\"dba432b8-cb4b-4e76-8684-01d96f91e453\"}\n[2026-04-27 10:31:11] local.INFO: [SocialAccountService] Refreshing token from provider {\"socialAccountId\":408,\"provider\":\"hubspot\",\"refreshToken\":\"de4e47eb985578f4218833e763e31059e88b562e87e10749b3389be2328f0aa7\",\"state\":\"connected\"} {\"correlation_id\":\"3b9d7e85-b8c2-4ae2-863e-c343c277afdc\",\"trace_id\":\"dba432b8-cb4b-4e76-8684-01d96f91e453\"}\n[2026-04-27 10:31:11] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage before starting command {\"command\":\"crm:bullhorn:ping\",\"memoryBeforeCommandInMb\":60.0,\"memoryPeakBeforeCommandInMb\":99.727} {\"correlation_id\":\"a2085168-092a-44b0-8dd2-c8845bc36f86\",\"trace_id\":\"9445425b-6aaf-4a23-9323-ecf85939f967\"}\n[2026-04-27 10:31:11] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage for command {\"command\":\"crm:bullhorn:ping\",\"memoryBeforeCommandInMb\":60.0,\"memoryAfterCommandInMB\":62.0,\"memoryPeakBeforeCommandInMb\":99.727,\"memoryPeakAfterCommandInMB\":99.727} {\"correlation_id\":\"a2085168-092a-44b0-8dd2-c8845bc36f86\",\"trace_id\":\"9445425b-6aaf-4a23-9323-ecf85939f967\"}\n[2026-04-27 10:31:12] local.INFO: [SocialAccountObserver] Saving model {\"correlation_id\":\"3b9d7e85-b8c2-4ae2-863e-c343c277afdc\",\"trace_id\":\"dba432b8-cb4b-4e76-8684-01d96f91e453\"}","role_description":"text entry area","is_enabled":true,"is_focused":true,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Sync Changes","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.008643617,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Hide This Notification","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.008643617,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Code changed:","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.042220745,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Hide","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.008643617,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"2","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.007978723,"height":0.0},"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"60","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.010305851,"height":0.0},"on_screen":false,"role_description":"text"},{"role":"AXButton","text":"Previous Highlighted Error","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.00731383,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Next Highlighted Error","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.006981383,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXTextArea","text":"<?php\n\ndeclare(strict_types=1);\n\nnamespace Jiminny\\Services\\Crm\\Hubspot;\n\nuse HubSpot\\Client\\Crm\\Deals\\ApiException as DealApiException;\nuse HubSpot\\Client\\Crm\\Contacts\\ApiException as ContactApiException;\nuse HubSpot\\Client\\Crm\\Companies\\ApiException as CompanyApiException;\nuse HubSpot\\Client\\Crm\\Contacts\\Model\\SimplePublicObjectWithAssociations as ContactsWithAssociations;\nuse HubSpot\\Client\\Crm\\Companies\\Model\\SimplePublicObjectWithAssociations as CompaniesWithAssociations;\nuse HubSpot\\Client\\Crm\\Deals\\Model\\SimplePublicObjectWithAssociations as DealWithAssociations;\nuse HubSpot\\Client\\Crm\\Objects\\Model\\SimplePublicObjectInput;\nuse HubSpot\\Client\\Crm\\Objects\\Model\\SimplePublicObjectWithAssociations as ObjectWithAssociations;\nuse HubSpot\\Client\\Crm\\Pipelines\\Model\\Error;\nuse HubSpot\\Client\\Crm\\Pipelines\\Model\\PipelineStage;\nuse HubSpot\\Client\\Crm\\Properties\\Model\\Property;\nuse HubSpot\\Discovery\\Discovery;\nuse Jiminny\\Component\\Utility\\Service\\ProviderRateLimiter;\nuse Jiminny\\Exceptions\\CrmException;\nuse Jiminny\\Exceptions\\RateLimitException;\nuse Jiminny\\Exceptions\\SocialAccountTokenInvalidException;\nuse Jiminny\\Jobs\\Crm\\NoteObject;\nuse Jiminny\\Models\\Crm\\Field;\nuse Jiminny\\Services\\Crm\\BaseClient;\nuse Jiminny\\Services\\Crm\\Hubspot\\DTO\\Response\\Owner;\nuse Jiminny\\Services\\SocialAccountService;\nuse SevenShores\\Hubspot\\Exceptions\\BadRequest;\nuse SevenShores\\Hubspot\\Exceptions\\HubspotException;\nuse SevenShores\\Hubspot\\Factory;\nuse SevenShores\\Hubspot\\Http\\Response;\nuse Jiminny\\Services\\Crm\\Hubspot\\Pagination\\HubspotPaginationService;\nuse Throwable;\n\n/**\n * @phpstan-type CrmFieldOption array{id:string, label:string, value?:string}\n */\nclass Client extends BaseClient implements HubspotClientInterface\n{\n public const string MIN_API_VERSION = '2';\n\n public const string BASE_URL = 'https://api.hubapi.com';\n\n public const int ASSOCIATIONS_BATCH_SIZE_LIMIT = 1000;\n\n private HubspotPaginationService $paginationService;\n private HubspotTokenManager $tokenManager;\n private ProviderRateLimiter $rateLimiter;\n\n public function __construct(\n SocialAccountService $socialAccountService,\n HubspotPaginationService $paginationService,\n HubspotTokenManager $tokenManager,\n ProviderRateLimiter $rateLimiter,\n ) {\n parent::__construct($socialAccountService);\n $this->paginationService = $paginationService;\n $this->tokenManager = $tokenManager;\n $this->rateLimiter = $rateLimiter;\n\n $this->setBaseUrl(self::BASE_URL);\n $this->setVersion(self::MIN_API_VERSION);\n }\n\n /**\n * Single entry point for every HubSpot API call. Enforces the per-portal\n * rate limit configured in the rate_limits table (morphed to the current\n * Configuration) and reacts to a real 429 from HubSpot by translating it\n * into a RateLimitException carrying Retry-After.\n *\n * Wrap any outbound HubSpot call (SDK or raw HTTP) like:\n *\n * $this->executeRequest(fn () => $this->getNewInstance()->crm()->...);\n *\n * @template T\n * @param callable(): T $apiCall\n * @return T\n *\n * @throws RateLimitException\n */\n private function executeRequest(callable $apiCall)\n {\n if (! $this->rateLimiter->canMakeRequest($this->config)) {\n $retryAfter = $this->rateLimiter->requestAvailableIn($this->config);\n\n $this->log->warning('[Hubspot] Rate limit exceeded, deferring request', [\n 'team_id' => $this->config->team_id,\n 'config_id' => $this->config->getId(),\n 'retry_after' => $retryAfter,\n ]);\n\n throw new RateLimitException(\n 'Hubspot rate limit reached for configuration ' . $this->config->getId(),\n $retryAfter,\n );\n }\n\n $this->rateLimiter->incrementRequestCount($this->config);\n\n try {\n return $apiCall();\n } catch (Throwable $e) {\n if ($this->isHubspotRateLimit($e)) {\n $retryAfter = $this->parseRetryAfter($e);\n\n $this->log->warning('[Hubspot] Received 429 from API', [\n 'team_id' => $this->config->team_id,\n 'config_id' => $this->config->getId(),\n 'retry_after' => $retryAfter,\n 'reason' => $e->getMessage(),\n ]);\n\n throw new RateLimitException('Hubspot returned 429', $retryAfter, $e);\n }\n\n throw $e;\n }\n }\n\n private function isHubspotRateLimit(Throwable $e): bool\n {\n return method_exists($e, 'getCode') && (int) $e->getCode() === 429;\n }\n\n private function parseRetryAfter(Throwable $e): int\n {\n if (method_exists($e, 'getResponseHeaders')) {\n $headers = $e->getResponseHeaders() ?: [];\n $value = $headers['Retry-After'] ?? $headers['retry-after'] ?? null;\n if (is_array($value)) {\n $value = $value[0] ?? null;\n }\n if (is_numeric($value)) {\n return (int) $value;\n }\n }\n\n return 10;\n }\n\n public function getMinimumApiVersion(): string\n {\n return self::MIN_API_VERSION;\n }\n\n public function getInstance(): Factory\n {\n return new Factory([\n 'key' => $this->accessToken,\n 'oauth2' => true,\n 'base_url' => $this->baseUrl,\n ]);\n }\n\n public function getNewInstance(): Discovery\n {\n return \\HubSpot\\Factory::createWithAccessToken($this->accessToken);\n }\n\n /**\n * Secondly and daily limits for Hubspot API\n *\n * Product Tier: Free & Starter | Professional & Enterprise | API add-on (any tier)\n * Burst: 100/10 seconds | 150/10 seconds | 200/10 seconds\n * Daily: 250,000 | 500,000 | 1,000,000\n *\n * Official documentation states: The search endpoints are rate limited to five requests per second.\n * Since with 5 RPS were still hitting secondly rate limits we lowered it to 4\n */\n public function getPaginatedData(array $payload, string $type, int $offset = 0): array\n {\n $total = 0;\n $lastId = null;\n $rows = [];\n foreach ($this->getPaginatedDataGenerator($payload, $type, $offset, $total, $lastId) as $row) {\n $rows[] = $row;\n }\n\n return ['results' => $rows, 'total' => $total, 'last_record' => $lastId];\n }\n\n /**\n * @throws HubspotException\n * @throws SocialAccountTokenInvalidException\n * @throws BadRequest\n */\n public function getPaginatedDataGenerator(\n array $payload,\n string $type,\n int $offset = 0,\n int &$total = 0,\n ?string &$lastRecordId = null\n ): \\Generator {\n return $this->paginationService->getPaginatedDataGenerator(\n $this,\n $payload,\n $type,\n $offset,\n $total,\n $lastRecordId\n );\n }\n\n /**\n * @throws DealApiException\n * @throws CrmException\n */\n public function getOpportunityById(string $crmId, array $fields): array\n {\n try {\n $deal = $this->executeRequest(fn () => $this->getNewInstance()->crm()->deals()->basicApi()->getById(\n $crmId,\n implode(',', $fields),\n 'companies,contacts'\n ));\n } catch (DealApiException $e) {\n $this->log->info('[Hubspot] Failed to fetch opportunity', [\n 'crm_id' => $crmId,\n 'reason' => $e->getMessage(),\n ]);\n\n throw $e;\n }\n\n if (! $deal instanceof DealWithAssociations) {\n throw new CrmException('Deal not found');\n }\n\n return [\n 'id' => $deal->getId(),\n 'properties' => $deal->getProperties(),\n 'associations' => $deal->getAssociations(),\n ];\n }\n\n /**\n * Generic batch read method for HubSpot objects\n *\n * @param string $objectType The object type ('deals', 'companies', 'contacts')\n * @param array<string> $crmIds Array of HubSpot object IDs (max 100)\n * @param array<string> $fields Array of property names to fetch\n *\n * @return array<string, array> Array keyed by CRM ID with object data\n */\n private function batchReadObjects(string $objectType, array $crmIds, array $fields): array\n {\n if (empty($crmIds)) {\n return [];\n }\n\n $this->validateBatchSize($objectType, $crmIds);\n $this->ensureValidToken();\n\n try {\n $batchConfig = $this->createBatchConfiguration($objectType);\n $batchReadRequest = $this->prepareBatchRequest($batchConfig, $crmIds, $fields);\n $response = $batchConfig['api']->read($batchReadRequest);\n\n $this->validateApiResponse($response, $objectType);\n\n $results = $this->processApiResults($response);\n $this->logBatchResults($objectType, $crmIds, $results);\n\n return $results;\n } catch (\\Throwable $e) {\n $this->handleBatchError($e, $objectType, $crmIds);\n }\n }\n\n private function validateBatchSize(string $objectType, array $crmIds): void\n {\n if (count($crmIds) > 100) {\n throw new \\InvalidArgumentException(\"Batch size cannot exceed 100 {$objectType}\");\n }\n }\n\n private function createBatchConfiguration(string $objectType): array\n {\n $configurations = [\n 'deals' => [\n 'batchReadRequest' => new \\HubSpot\\Client\\Crm\\Deals\\Model\\BatchReadInputSimplePublicObjectId(),\n 'inputClass' => \\HubSpot\\Client\\Crm\\Deals\\Model\\SimplePublicObjectId::class,\n 'api' => $this->getNewInstance()->crm()->deals()->batchApi(),\n ],\n 'companies' => [\n 'batchReadRequest' => new \\HubSpot\\Client\\Crm\\Companies\\Model\\BatchReadInputSimplePublicObjectId(),\n 'inputClass' => \\HubSpot\\Client\\Crm\\Companies\\Model\\SimplePublicObjectId::class,\n 'api' => $this->getNewInstance()->crm()->companies()->batchApi(),\n ],\n 'contacts' => [\n 'batchReadRequest' => new \\HubSpot\\Client\\Crm\\Contacts\\Model\\BatchReadInputSimplePublicObjectId(),\n 'inputClass' => \\HubSpot\\Client\\Crm\\Contacts\\Model\\SimplePublicObjectId::class,\n 'api' => $this->getNewInstance()->crm()->contacts()->batchApi(),\n ],\n ];\n\n if (! isset($configurations[$objectType])) {\n throw new \\InvalidArgumentException(\"Unsupported object type: {$objectType}\");\n }\n\n return $configurations[$objectType];\n }\n\n private function prepareBatchRequest(array $batchConfig, array $crmIds, array $fields): object\n {\n $batchReadRequest = $batchConfig['batchReadRequest'];\n $inputClass = $batchConfig['inputClass'];\n\n $inputs = array_map(function ($crmId) use ($inputClass) {\n $input = new $inputClass();\n $input->setId($crmId);\n\n return $input;\n }, $crmIds);\n\n $batchReadRequest->setInputs($inputs);\n $batchReadRequest->setProperties($fields);\n\n return $batchReadRequest;\n }\n\n private function validateApiResponse($response, string $objectType): void\n {\n if (! $response) {\n throw new CrmException(\"HubSpot API returned null response for {$objectType} batch read\");\n }\n }\n\n private function processApiResults($response): array\n {\n $results = [];\n $responseResults = $response->getResults();\n\n if ($responseResults) {\n foreach ($responseResults as $object) {\n if ($object && $object->getId()) {\n $results[$object->getId()] = [\n 'id' => $object->getId(),\n 'properties' => $object->getProperties() ?: [],\n ];\n }\n }\n }\n\n return $results;\n }\n\n private function logBatchResults(string $objectType, array $crmIds, array $results): void\n {\n $this->log->info(\"[HubSpot] Batch fetched {$objectType}\", [\n 'requested_count' => count($crmIds),\n 'returned_count' => count($results),\n 'crm_ids' => $crmIds,\n ]);\n }\n\n private function handleBatchError(\\Throwable $e, string $objectType, array $crmIds): void\n {\n $errorMessage = $e->getMessage() ?: 'Unknown error';\n $errorTrace = $e->getTraceAsString() ?: 'No trace available';\n\n $this->log->error(\"[HubSpot] Failed to batch fetch {$objectType}\", [\n 'crm_ids' => $crmIds,\n 'error' => $errorMessage,\n 'trace' => $errorTrace,\n ]);\n\n throw new CrmException(\"Failed to batch fetch {$objectType}: \" . $errorMessage);\n }\n\n /**\n * Batch read multiple opportunities by their CRM IDs\n *\n * @param array<string> $crmIds Array of HubSpot deal IDs (max 100)\n * @param array<string> $fields Array of property names to fetch\n *\n * @return array<string, array> Array keyed by CRM ID with opportunity data\n */\n public function getOpportunitiesByIds(array $crmIds, array $fields): array\n {\n return $this->batchReadObjects('deals', $crmIds, $fields);\n }\n\n /**\n * Batch read multiple companies by their CRM IDs\n *\n * @param array<string> $crmIds Array of HubSpot company IDs (max 100)\n * @param array<string> $fields Array of property names to fetch\n *\n * @return array<string, array> Array keyed by CRM ID with company data\n */\n public function getCompaniesByIds(array $crmIds, array $fields): array\n {\n return $this->batchReadObjects('companies', $crmIds, $fields);\n }\n\n /**\n * Batch read multiple contacts by their CRM IDs\n *\n * @param array<string> $crmIds Array of HubSpot contact IDs (max 100)\n * @param array<string> $fields Array of property names to fetch\n *\n * @return array<string, array> Array keyed by CRM ID with contact data\n */\n public function getContactsByIds(array $crmIds, array $fields): array\n {\n return $this->batchReadObjects('contacts', $crmIds, $fields);\n }\n\n /**\n * @throws CompanyApiException\n * @throws CrmException\n */\n public function getAccountById(string $crmId, array $fields): array\n {\n try {\n $company = $this->getNewInstance()->crm()->companies()->basicApi()->getById(\n $crmId,\n implode(',', $fields),\n );\n } catch (CompanyApiException $e) {\n $this->log->info('[Hubspot] Failed to fetch account', [\n 'crm_id' => $crmId,\n 'reason' => $e->getMessage(),\n ]);\n\n throw $e;\n }\n\n if (! $company instanceof CompaniesWithAssociations) {\n throw new CrmException('Account not found');\n }\n\n return [\n 'id' => $company->getId(),\n 'properties' => $company->getProperties(),\n ];\n }\n\n /**\n * @throws ContactApiException\n * @throws CrmException\n */\n public function getContactById(string $crmId, array $fields): array\n {\n try {\n $contact = $this->getNewInstance()->crm()->contacts()->basicApi()->getById(\n $crmId,\n implode(',', $fields)\n );\n } catch (ContactApiException $e) {\n $this->log->info('[Hubspot] Failed to fetch contact', [\n 'crm_id' => $crmId,\n 'reason' => $e->getMessage(),\n ]);\n\n throw $e;\n }\n\n if (! $contact instanceof ContactsWithAssociations) {\n throw new CrmException('Contact not found');\n }\n\n return [\n 'id' => $contact->getId(),\n 'properties' => $contact->getProperties(),\n ];\n }\n\n /**\n * This is email search request that Hubspot offers as GET (more generous quota)\n */\n public function getContactByEmail(string $email, array $fields = []): array\n {\n try {\n $contact = $this->getNewInstance()->crm()->contacts()->basicApi()->getById(\n $email,\n implode(',', $fields),\n null,\n false,\n 'email'\n );\n\n return [\n 'id' => $contact->getId(),\n 'properties' => $contact->getProperties(),\n ];\n } catch (ContactApiException $e) {\n $this->log->info('[Hubspot] Failed to fetch contact', [\n 'email' => $email,\n 'reason' => $e->getMessage(),\n ]);\n\n return [];\n }\n }\n\n /**\n * @throws CrmException\n */\n public function fetchProperty(string $objectType, string $propertyId): Property\n {\n $result = $this->getNewInstance()->crm()->properties()->coreApi()->getByName($objectType, $propertyId);\n\n if (! $result instanceof Property) {\n $this->log->error('[Hubspot] Failed to fetch property', [\n 'object_type' => $objectType,\n 'property_id' => $propertyId,\n 'reason' => $result->getMessage(),\n ]);\n\n throw new CrmException('Failed to fetch property');\n }\n\n return $result;\n }\n\n /**\n * @return array<CrmFieldOption>\n */\n public function fetchPropertyOptions(string $objectType, string $propertyId): array\n {\n /** @var array<CrmFieldOption> */\n return $this->fetchProperty($objectType, $propertyId)->getOptions();\n }\n\n /**\n * @return array<array{id:string, label:string, deleted:bool}>\n */\n public function fetchCallDispositions(): array\n {\n /** @var Response $response */\n $response = $this->getInstance()->engagements()->getCallDispositions();\n\n /**\n * @var array<array{\n * id:string,\n * label:string,\n * deleted: bool\n * }>\n */\n return $response->toArray();\n }\n\n /**\n * @return array<CrmFieldOption>\n */\n public function fetchOpportunityPipelineStages(): array\n {\n $stages = [];\n $apiResponse = $this->getNewInstance()->crm()->pipelines()->pipelinesApi()->getAll('deals');\n\n if ($apiResponse instanceof Error) {\n $this->log->error('[Hubspot] Failed to fetch opportunity pipelines', [\n 'reason' => $apiResponse->getMessage(),\n ]);\n\n return [];\n }\n\n foreach ($apiResponse->getResults() as $pipeline) {\n $pipelineStages = array_map(\n static function (PipelineStage $stage) {\n return [\n 'id' => $stage->getId(),\n 'label' => $stage->getLabel(),\n ];\n },\n $pipeline->getStages()\n );\n\n $stages = array_merge($stages, $pipelineStages);\n }\n\n return $stages;\n }\n\n public function fetchOpportunityPipelines(): array\n {\n $pipelines = [];\n\n try {\n $apiResponse = $this->makeRequest('/crm/v3/pipelines/deals');\n } catch (\\Exception $e) {\n $this->log->info('[Hubspot] Failed to fetch opportunity pipelines', [\n 'reason' => $e->getMessage(),\n ]);\n\n return [];\n }\n\n $response = $apiResponse->toArray();\n\n foreach ($response['results'] as $pipeline) {\n $pipelines[] = [\n 'id' => $pipeline['id'],\n 'label' => $pipeline['label'],\n ];\n }\n\n return $pipelines;\n }\n\n /**\n * @return array<CrmFieldOption>\n */\n public function fetchMeetingOutcomeFieldOptions(Field $field): array\n {\n return $field->getCrmProviderId() === 'meetingOutcome'\n ? $this->fetchMeetingOutcomeTypes()\n : $this->fetchCallActivityTypes();\n }\n\n public function fetchMeetingOutcomeTypes(): array\n {\n return $this->extractMeetingTypeOptions(\n 'https://api.hubapi.com/crm/v3/properties/meeting/hs_meeting_outcome'\n );\n }\n\n public function fetchCallActivityTypes(): array\n {\n return $this->extractMeetingTypeOptions(\n 'https://api.hubapi.com/crm/v3/properties/call/hs_activity_type'\n );\n }\n\n private function extractMeetingTypeOptions(string $endpoint): array\n {\n /** @var Response $response */\n $response = $this->getInstance()\n ->getClient()\n ->request('GET', $endpoint);\n\n /**\n * @var array<array{\n * value: string,\n * label: string,\n * displayOrder: int\n * }> $optionData\n */\n $optionData = $response->toArray()['options'] ?? [];\n\n $options = [];\n foreach ($optionData as $item) {\n $options[] = [\n 'id' => $item['value'],\n 'value' => $item['value'],\n 'label' => $item['label'],\n 'display_order' => $item['displayOrder'],\n ];\n }\n\n return $options;\n }\n\n /**\n * @return array<CrmFieldOption>\n */\n public function fetchDispositionFieldOptions(): array\n {\n $options = [];\n\n $dispositions = $this->fetchCallDispositions();\n\n foreach ($dispositions as $disposition) {\n if ($disposition['deleted'] !== false) {\n continue;\n }\n\n $option['value'] = $disposition['id'];\n $option['id'] = $disposition['id'];\n $option['label'] = $disposition['label'];\n\n $options[] = $option;\n }\n\n return $options;\n }\n\n /**\n * @return array<CrmFieldOption>\n */\n public function fetchOpportunityFieldOptions(Field $field): array\n {\n if ($field->isStageField()) {\n return $this->fetchOpportunityPipelineStages();\n }\n\n if ($field->isPipelineField()) {\n return $this->fetchOpportunityPipelines();\n }\n\n return $this->fetchPropertyOptions('deals', $field->getCrmProviderId());\n }\n\n /**\n * @throws BadRequest\n * @throws HubspotException\n */\n public function makeRequest(string $endpoint, $method = 'GET', $payload = [], ?string $queryString = null)\n {\n $endpoint = self::BASE_URL . $endpoint;\n\n if ($method === 'GET') {\n $response = $this->getInstance()->getClient()?->request(\n method: $method,\n endpoint: $endpoint,\n query_string: $queryString\n );\n } else {\n $response = $this->getInstance()->getClient()->request($method, $endpoint, [\n 'json' => ($payload),\n ]);\n }\n\n $max = $response->getHeaderLine('X-HubSpot-RateLimit-Max'); // \"110\"\n $remaining = $response->getHeaderLine('X-HubSpot-RateLimit-Remaining'); // \"109\"\n $interval = $response->getHeaderLine('X-HubSpot-RateLimit-Interval-Milliseconds'); // \"10000\"\n $body = json_decode((string) $response->getBody(), true);\n\n \\Illuminate\\Support\\Facades\\Log::channel('custom_channel')->info('$max ' . PHP_EOL . print_r($max, true));\n \\Illuminate\\Support\\Facades\\Log::channel('custom_channel')->info('$remaining ' . PHP_EOL . print_r($remaining, true));\n \\Illuminate\\Support\\Facades\\Log::channel('custom_channel')->info('$interval ' . PHP_EOL . print_r($interval, true));\n \\Illuminate\\Support\\Facades\\Log::channel('custom_channel')->info('$body ' . PHP_EOL . print_r($body, true));\n\n return $response;\n }\n\n /**\n * @throws BadRequest\n * @throws HubspotException\n */\n public function createMeeting(array $payload): Response\n {\n $endpoint = '/crm/v3/objects/meetings';\n\n return $this->makeRequest($endpoint, 'POST', $payload);\n }\n\n /**\n * @throws BadRequest\n * @throws HubspotException\n */\n public function updateMeeting(string $meetingId, array $payload): Response\n {\n $endpoint = '/crm/v3/objects/meetings/' . $meetingId;\n\n return $this->makeRequest($endpoint, 'PATCH', $payload);\n }\n\n /**\n * @throws \\Exception\n */\n public function createNote(\n string $body,\n string $ownerId,\n int $timestamp,\n string $objectId,\n NoteObject $noteObject\n ): ?string {\n try {\n $noteInput = new SimplePublicObjectInput([\n 'properties' => [\n 'hs_note_body' => $body,\n 'hubspot_owner_id' => $ownerId,\n 'hs_timestamp' => $timestamp,\n ],\n ]);\n\n // Create note\n $note = $this->getNewInstance()->crm()->objects()->basicApi()->create('note', $noteInput);\n\n $this->getNewInstance()->crm()->objects()->associationsApi()->create(\n 'note',\n $note->getId(),\n $this->getNoteObject($noteObject),\n $objectId,\n $this->getNoteAssociationType($noteObject),\n );\n\n return $note->getId();\n } catch (\\Exception $e) {\n $this->log->error('[Hubspot] Failed to create note', [\n 'objectId' => $objectId,\n 'noteObject' => $noteObject->getObjectType(),\n 'reason' => $e->getMessage(),\n ]);\n\n \\Sentry::captureException($e);\n }\n\n return null;\n }\n\n public function updateEngagement(string $objectId, array $engagement, array $metadata): void\n {\n $this->getInstance()->engagements()->update($objectId, $engagement, $metadata);\n }\n\n public function getEngagementData(string $engagementId): array\n {\n $engagement = $this->getInstance()->engagements()->get($engagementId);\n\n return $engagement->toArray();\n }\n\n public function createEngagement(array $engagement, array $associations, array $metadata): Response\n {\n return $this->getInstance()\n ->engagements()\n ->create($engagement, $associations, $metadata);\n }\n\n public function isUnauthorizedException(\\Exception $e): bool\n {\n // Check for specific HubSpot API exception types first\n if ($e instanceof BadRequest) {\n // BadRequest can contain 401 status codes\n return $e->getCode() === 401;\n }\n\n // Check for HTTP client exceptions with status codes\n if ($e instanceof \\GuzzleHttp\\Exception\\RequestException && $e->hasResponse()) {\n $response = $e->getResponse();\n if ($response !== null) {\n return $response->getStatusCode() === 401;\n }\n }\n\n // Check for Guzzle HTTP exceptions\n if ($e instanceof \\GuzzleHttp\\Exception\\ClientException) {\n return $e->getCode() === 401;\n }\n\n // Fallback to string matching as last resort, but be more specific\n $message = strtolower($e->getMessage());\n\n return str_contains($message, '401 unauthorized') ||\n str_contains($message, 'http 401') ||\n str_contains($message, 'status code 401') ||\n (preg_match('/\\b401\\b/', $message) && str_contains($message, 'unauthorized'));\n }\n\n /**\n * Validates and refreshes the access token if needed before API requests.\n * This ensures long-running processes don't fail due to token expiration.\n *\n * @throws SocialAccountTokenInvalidException\n */\n public function ensureValidToken(): void\n {\n if ($this->oauthAccount === null) {\n return;\n }\n\n $newToken = $this->tokenManager->ensureValidToken($this->oauthAccount);\n if ($newToken !== null) {\n $this->accessToken = $newToken;\n }\n }\n\n public function getConfig()\n {\n return $this->config;\n }\n\n // returns only active (archived=false)\n public function getOwners(): array\n {\n return $this->getNewInstance()->crm()->owners()->getAll();\n }\n\n /**\n * @param bool $archived\n *\n * @return array<Owner>|[]\n */\n public function getOwnersArchived(bool $archived = true): array\n {\n $endpoint = '/crm/v3/owners';\n $queryParams = [\n 'archived' => $archived ? 'true' : 'false',\n ];\n $queryString = http_build_query($queryParams);\n\n $owners = [];\n\n try {\n $response = $this->makeRequest(endpoint: $endpoint, queryString: $queryString);\n $responseData = $response?->toArray();\n\n foreach ($responseData['results'] as $result) {\n try {\n $owners[] = Owner::create($result);\n } catch (Throwable $e) {\n $this->log->error('[HubSpot] Failed to process owner data', [\n 'result' => $result,\n 'error' => $e->getMessage(),\n ]);\n\n continue;\n }\n }\n } catch (Throwable $e) {\n $this->log->error('HubSpot] Failed to fetch owners', [\n 'archived' => $archived,\n 'error' => $e->getMessage(),\n ]);\n\n return [];\n }\n\n return $owners;\n }\n\n public function getMeeting(string $engagementId): ObjectWithAssociations\n {\n return $this->getNewInstance()->crm()->objects()->basicApi()\n ->getById('meeting', $engagementId, null, 'contact,company,deal');\n }\n\n public function deleteEngagement(string $engagementId): void\n {\n $this->getInstance()->engagements()->delete((int) $engagementId);\n }\n\n public function getAssociationsData(array $ids, string $fromObject, string $toObject): array\n {\n $associationData = [];\n $idChunks = array_chunk($ids, self::ASSOCIATIONS_BATCH_SIZE_LIMIT);\n\n foreach ($idChunks as $idChunk) {\n try {\n $batchInput = new \\HubSpot\\Client\\Crm\\Associations\\Model\\BatchInputPublicObjectId();\n $batchInput->setInputs(array_map(function ($id) {\n $publicObjectId = new \\HubSpot\\Client\\Crm\\Associations\\Model\\PublicObjectId();\n $publicObjectId->setId($id);\n\n return $publicObjectId;\n }, $idChunk));\n\n $associatedObjectsData = $this\n ->getNewInstance()\n ->crm()\n ->associations()\n ->batchApi()\n ->read($fromObject, $toObject, $batchInput);\n\n if ($associatedObjectsData instanceof \\HubSpot\\Client\\Crm\\Associations\\Model\\BatchResponsePublicAssociationMulti) {\n foreach ($associatedObjectsData->getResults() as $association) {\n $from = $association->getFrom()->getId();\n $toAssociations = $association->getTo();\n\n if (! empty($toAssociations)) {\n $associationData[$from] = array_map(function ($item) {\n return $item->getId();\n }, $toAssociations);\n }\n }\n }\n } catch (\\Exception $e) {\n $this->log->error('[Hubspot] Failed to fetch associations', [\n 'from_object' => $fromObject,\n 'to_object' => $toObject,\n 'reason' => $e->getMessage(),\n ]);\n }\n }\n\n return $associationData;\n }\n\n /**\n * @throws \\Exception\n */\n private function getNoteAssociationType(NoteObject $noteObject): string\n {\n return match($noteObject) {\n NoteObject::Opportunity => 'note_to_deal',\n NoteObject::Lead, NoteObject::Contact => 'note_to_contact', // or 'note_to_lead' if your portal supports it\n NoteObject::Account => 'note_to_company',\n NoteObject::Call, NoteObject::Event => throw new \\Exception('Not supported'),\n };\n }\n\n /**\n * @throws \\Exception\n */\n private function getNoteObject(NoteObject $noteObject): string\n {\n return match($noteObject) {\n NoteObject::Opportunity => 'deal',\n NoteObject::Lead, NoteObject::Contact => 'contact',\n NoteObject::Account => 'company',\n NoteObject::Call, NoteObject::Event => throw new \\Exception('Not supported'),\n };\n }\n\n public function addAssociations(string $objectType, string $associationType, array $payload): Response\n {\n $endpoint = \"/crm/v4/associations/$objectType/$associationType/batch/create\";\n\n return $this->makeRequest($endpoint, 'POST', $payload);\n }\n\n public function removeAssociations(string $objectType, string $associationType, array $payload): Response\n {\n $endpoint = \"/crm/v4/associations/$objectType/$associationType/batch/archive\";\n\n return $this->makeRequest($endpoint, 'POST', $payload);\n }\n}","depth":4,"on_screen":true,"value":"<?php\n\ndeclare(strict_types=1);\n\nnamespace Jiminny\\Services\\Crm\\Hubspot;\n\nuse HubSpot\\Client\\Crm\\Deals\\ApiException as DealApiException;\nuse HubSpot\\Client\\Crm\\Contacts\\ApiException as ContactApiException;\nuse HubSpot\\Client\\Crm\\Companies\\ApiException as CompanyApiException;\nuse HubSpot\\Client\\Crm\\Contacts\\Model\\SimplePublicObjectWithAssociations as ContactsWithAssociations;\nuse HubSpot\\Client\\Crm\\Companies\\Model\\SimplePublicObjectWithAssociations as CompaniesWithAssociations;\nuse HubSpot\\Client\\Crm\\Deals\\Model\\SimplePublicObjectWithAssociations as DealWithAssociations;\nuse HubSpot\\Client\\Crm\\Objects\\Model\\SimplePublicObjectInput;\nuse HubSpot\\Client\\Crm\\Objects\\Model\\SimplePublicObjectWithAssociations as ObjectWithAssociations;\nuse HubSpot\\Client\\Crm\\Pipelines\\Model\\Error;\nuse HubSpot\\Client\\Crm\\Pipelines\\Model\\PipelineStage;\nuse HubSpot\\Client\\Crm\\Properties\\Model\\Property;\nuse HubSpot\\Discovery\\Discovery;\nuse Jiminny\\Component\\Utility\\Service\\ProviderRateLimiter;\nuse Jiminny\\Exceptions\\CrmException;\nuse Jiminny\\Exceptions\\RateLimitException;\nuse Jiminny\\Exceptions\\SocialAccountTokenInvalidException;\nuse Jiminny\\Jobs\\Crm\\NoteObject;\nuse Jiminny\\Models\\Crm\\Field;\nuse Jiminny\\Services\\Crm\\BaseClient;\nuse Jiminny\\Services\\Crm\\Hubspot\\DTO\\Response\\Owner;\nuse Jiminny\\Services\\SocialAccountService;\nuse SevenShores\\Hubspot\\Exceptions\\BadRequest;\nuse SevenShores\\Hubspot\\Exceptions\\HubspotException;\nuse SevenShores\\Hubspot\\Factory;\nuse SevenShores\\Hubspot\\Http\\Response;\nuse Jiminny\\Services\\Crm\\Hubspot\\Pagination\\HubspotPaginationService;\nuse Throwable;\n\n/**\n * @phpstan-type CrmFieldOption array{id:string, label:string, value?:string}\n */\nclass Client extends BaseClient implements HubspotClientInterface\n{\n public const string MIN_API_VERSION = '2';\n\n public const string BASE_URL = 'https://api.hubapi.com';\n\n public const int ASSOCIATIONS_BATCH_SIZE_LIMIT = 1000;\n\n private HubspotPaginationService $paginationService;\n private HubspotTokenManager $tokenManager;\n private ProviderRateLimiter $rateLimiter;\n\n public function __construct(\n SocialAccountService $socialAccountService,\n HubspotPaginationService $paginationService,\n HubspotTokenManager $tokenManager,\n ProviderRateLimiter $rateLimiter,\n ) {\n parent::__construct($socialAccountService);\n $this->paginationService = $paginationService;\n $this->tokenManager = $tokenManager;\n $this->rateLimiter = $rateLimiter;\n\n $this->setBaseUrl(self::BASE_URL);\n $this->setVersion(self::MIN_API_VERSION);\n }\n\n /**\n * Single entry point for every HubSpot API call. Enforces the per-portal\n * rate limit configured in the rate_limits table (morphed to the current\n * Configuration) and reacts to a real 429 from HubSpot by translating it\n * into a RateLimitException carrying Retry-After.\n *\n * Wrap any outbound HubSpot call (SDK or raw HTTP) like:\n *\n * $this->executeRequest(fn () => $this->getNewInstance()->crm()->...);\n *\n * @template T\n * @param callable(): T $apiCall\n * @return T\n *\n * @throws RateLimitException\n */\n private function executeRequest(callable $apiCall)\n {\n if (! $this->rateLimiter->canMakeRequest($this->config)) {\n $retryAfter = $this->rateLimiter->requestAvailableIn($this->config);\n\n $this->log->warning('[Hubspot] Rate limit exceeded, deferring request', [\n 'team_id' => $this->config->team_id,\n 'config_id' => $this->config->getId(),\n 'retry_after' => $retryAfter,\n ]);\n\n throw new RateLimitException(\n 'Hubspot rate limit reached for configuration ' . $this->config->getId(),\n $retryAfter,\n );\n }\n\n $this->rateLimiter->incrementRequestCount($this->config);\n\n try {\n return $apiCall();\n } catch (Throwable $e) {\n if ($this->isHubspotRateLimit($e)) {\n $retryAfter = $this->parseRetryAfter($e);\n\n $this->log->warning('[Hubspot] Received 429 from API', [\n 'team_id' => $this->config->team_id,\n 'config_id' => $this->config->getId(),\n 'retry_after' => $retryAfter,\n 'reason' => $e->getMessage(),\n ]);\n\n throw new RateLimitException('Hubspot returned 429', $retryAfter, $e);\n }\n\n throw $e;\n }\n }\n\n private function isHubspotRateLimit(Throwable $e): bool\n {\n return method_exists($e, 'getCode') && (int) $e->getCode() === 429;\n }\n\n private function parseRetryAfter(Throwable $e): int\n {\n if (method_exists($e, 'getResponseHeaders')) {\n $headers = $e->getResponseHeaders() ?: [];\n $value = $headers['Retry-After'] ?? $headers['retry-after'] ?? null;\n if (is_array($value)) {\n $value = $value[0] ?? null;\n }\n if (is_numeric($value)) {\n return (int) $value;\n }\n }\n\n return 10;\n }\n\n public function getMinimumApiVersion(): string\n {\n return self::MIN_API_VERSION;\n }\n\n public function getInstance(): Factory\n {\n return new Factory([\n 'key' => $this->accessToken,\n 'oauth2' => true,\n 'base_url' => $this->baseUrl,\n ]);\n }\n\n public function getNewInstance(): Discovery\n {\n return \\HubSpot\\Factory::createWithAccessToken($this->accessToken);\n }\n\n /**\n * Secondly and daily limits for Hubspot API\n *\n * Product Tier: Free & Starter | Professional & Enterprise | API add-on (any tier)\n * Burst: 100/10 seconds | 150/10 seconds | 200/10 seconds\n * Daily: 250,000 | 500,000 | 1,000,000\n *\n * Official documentation states: The search endpoints are rate limited to five requests per second.\n * Since with 5 RPS were still hitting secondly rate limits we lowered it to 4\n */\n public function getPaginatedData(array $payload, string $type, int $offset = 0): array\n {\n $total = 0;\n $lastId = null;\n $rows = [];\n foreach ($this->getPaginatedDataGenerator($payload, $type, $offset, $total, $lastId) as $row) {\n $rows[] = $row;\n }\n\n return ['results' => $rows, 'total' => $total, 'last_record' => $lastId];\n }\n\n /**\n * @throws HubspotException\n * @throws SocialAccountTokenInvalidException\n * @throws BadRequest\n */\n public function getPaginatedDataGenerator(\n array $payload,\n string $type,\n int $offset = 0,\n int &$total = 0,\n ?string &$lastRecordId = null\n ): \\Generator {\n return $this->paginationService->getPaginatedDataGenerator(\n $this,\n $payload,\n $type,\n $offset,\n $total,\n $lastRecordId\n );\n }\n\n /**\n * @throws DealApiException\n * @throws CrmException\n */\n public function getOpportunityById(string $crmId, array $fields): array\n {\n try {\n $deal = $this->executeRequest(fn () => $this->getNewInstance()->crm()->deals()->basicApi()->getById(\n $crmId,\n implode(',', $fields),\n 'companies,contacts'\n ));\n } catch (DealApiException $e) {\n $this->log->info('[Hubspot] Failed to fetch opportunity', [\n 'crm_id' => $crmId,\n 'reason' => $e->getMessage(),\n ]);\n\n throw $e;\n }\n\n if (! $deal instanceof DealWithAssociations) {\n throw new CrmException('Deal not found');\n }\n\n return [\n 'id' => $deal->getId(),\n 'properties' => $deal->getProperties(),\n 'associations' => $deal->getAssociations(),\n ];\n }\n\n /**\n * Generic batch read method for HubSpot objects\n *\n * @param string $objectType The object type ('deals', 'companies', 'contacts')\n * @param array<string> $crmIds Array of HubSpot object IDs (max 100)\n * @param array<string> $fields Array of property names to fetch\n *\n * @return array<string, array> Array keyed by CRM ID with object data\n */\n private function batchReadObjects(string $objectType, array $crmIds, array $fields): array\n {\n if (empty($crmIds)) {\n return [];\n }\n\n $this->validateBatchSize($objectType, $crmIds);\n $this->ensureValidToken();\n\n try {\n $batchConfig = $this->createBatchConfiguration($objectType);\n $batchReadRequest = $this->prepareBatchRequest($batchConfig, $crmIds, $fields);\n $response = $batchConfig['api']->read($batchReadRequest);\n\n $this->validateApiResponse($response, $objectType);\n\n $results = $this->processApiResults($response);\n $this->logBatchResults($objectType, $crmIds, $results);\n\n return $results;\n } catch (\\Throwable $e) {\n $this->handleBatchError($e, $objectType, $crmIds);\n }\n }\n\n private function validateBatchSize(string $objectType, array $crmIds): void\n {\n if (count($crmIds) > 100) {\n throw new \\InvalidArgumentException(\"Batch size cannot exceed 100 {$objectType}\");\n }\n }\n\n private function createBatchConfiguration(string $objectType): array\n {\n $configurations = [\n 'deals' => [\n 'batchReadRequest' => new \\HubSpot\\Client\\Crm\\Deals\\Model\\BatchReadInputSimplePublicObjectId(),\n 'inputClass' => \\HubSpot\\Client\\Crm\\Deals\\Model\\SimplePublicObjectId::class,\n 'api' => $this->getNewInstance()->crm()->deals()->batchApi(),\n ],\n 'companies' => [\n 'batchReadRequest' => new \\HubSpot\\Client\\Crm\\Companies\\Model\\BatchReadInputSimplePublicObjectId(),\n 'inputClass' => \\HubSpot\\Client\\Crm\\Companies\\Model\\SimplePublicObjectId::class,\n 'api' => $this->getNewInstance()->crm()->companies()->batchApi(),\n ],\n 'contacts' => [\n 'batchReadRequest' => new \\HubSpot\\Client\\Crm\\Contacts\\Model\\BatchReadInputSimplePublicObjectId(),\n 'inputClass' => \\HubSpot\\Client\\Crm\\Contacts\\Model\\SimplePublicObjectId::class,\n 'api' => $this->getNewInstance()->crm()->contacts()->batchApi(),\n ],\n ];\n\n if (! isset($configurations[$objectType])) {\n throw new \\InvalidArgumentException(\"Unsupported object type: {$objectType}\");\n }\n\n return $configurations[$objectType];\n }\n\n private function prepareBatchRequest(array $batchConfig, array $crmIds, array $fields): object\n {\n $batchReadRequest = $batchConfig['batchReadRequest'];\n $inputClass = $batchConfig['inputClass'];\n\n $inputs = array_map(function ($crmId) use ($inputClass) {\n $input = new $inputClass();\n $input->setId($crmId);\n\n return $input;\n }, $crmIds);\n\n $batchReadRequest->setInputs($inputs);\n $batchReadRequest->setProperties($fields);\n\n return $batchReadRequest;\n }\n\n private function validateApiResponse($response, string $objectType): void\n {\n if (! $response) {\n throw new CrmException(\"HubSpot API returned null response for {$objectType} batch read\");\n }\n }\n\n private function processApiResults($response): array\n {\n $results = [];\n $responseResults = $response->getResults();\n\n if ($responseResults) {\n foreach ($responseResults as $object) {\n if ($object && $object->getId()) {\n $results[$object->getId()] = [\n 'id' => $object->getId(),\n 'properties' => $object->getProperties() ?: [],\n ];\n }\n }\n }\n\n return $results;\n }\n\n private function logBatchResults(string $objectType, array $crmIds, array $results): void\n {\n $this->log->info(\"[HubSpot] Batch fetched {$objectType}\", [\n 'requested_count' => count($crmIds),\n 'returned_count' => count($results),\n 'crm_ids' => $crmIds,\n ]);\n }\n\n private function handleBatchError(\\Throwable $e, string $objectType, array $crmIds): void\n {\n $errorMessage = $e->getMessage() ?: 'Unknown error';\n $errorTrace = $e->getTraceAsString() ?: 'No trace available';\n\n $this->log->error(\"[HubSpot] Failed to batch fetch {$objectType}\", [\n 'crm_ids' => $crmIds,\n 'error' => $errorMessage,\n 'trace' => $errorTrace,\n ]);\n\n throw new CrmException(\"Failed to batch fetch {$objectType}: \" . $errorMessage);\n }\n\n /**\n * Batch read multiple opportunities by their CRM IDs\n *\n * @param array<string> $crmIds Array of HubSpot deal IDs (max 100)\n * @param array<string> $fields Array of property names to fetch\n *\n * @return array<string, array> Array keyed by CRM ID with opportunity data\n */\n public function getOpportunitiesByIds(array $crmIds, array $fields): array\n {\n return $this->batchReadObjects('deals', $crmIds, $fields);\n }\n\n /**\n * Batch read multiple companies by their CRM IDs\n *\n * @param array<string> $crmIds Array of HubSpot company IDs (max 100)\n * @param array<string> $fields Array of property names to fetch\n *\n * @return array<string, array> Array keyed by CRM ID with company data\n */\n public function getCompaniesByIds(array $crmIds, array $fields): array\n {\n return $this->batchReadObjects('companies', $crmIds, $fields);\n }\n\n /**\n * Batch read multiple contacts by their CRM IDs\n *\n * @param array<string> $crmIds Array of HubSpot contact IDs (max 100)\n * @param array<string> $fields Array of property names to fetch\n *\n * @return array<string, array> Array keyed by CRM ID with contact data\n */\n public function getContactsByIds(array $crmIds, array $fields): array\n {\n return $this->batchReadObjects('contacts', $crmIds, $fields);\n }\n\n /**\n * @throws CompanyApiException\n * @throws CrmException\n */\n public function getAccountById(string $crmId, array $fields): array\n {\n try {\n $company = $this->getNewInstance()->crm()->companies()->basicApi()->getById(\n $crmId,\n implode(',', $fields),\n );\n } catch (CompanyApiException $e) {\n $this->log->info('[Hubspot] Failed to fetch account', [\n 'crm_id' => $crmId,\n 'reason' => $e->getMessage(),\n ]);\n\n throw $e;\n }\n\n if (! $company instanceof CompaniesWithAssociations) {\n throw new CrmException('Account not found');\n }\n\n return [\n 'id' => $company->getId(),\n 'properties' => $company->getProperties(),\n ];\n }\n\n /**\n * @throws ContactApiException\n * @throws CrmException\n */\n public function getContactById(string $crmId, array $fields): array\n {\n try {\n $contact = $this->getNewInstance()->crm()->contacts()->basicApi()->getById(\n $crmId,\n implode(',', $fields)\n );\n } catch (ContactApiException $e) {\n $this->log->info('[Hubspot] Failed to fetch contact', [\n 'crm_id' => $crmId,\n 'reason' => $e->getMessage(),\n ]);\n\n throw $e;\n }\n\n if (! $contact instanceof ContactsWithAssociations) {\n throw new CrmException('Contact not found');\n }\n\n return [\n 'id' => $contact->getId(),\n 'properties' => $contact->getProperties(),\n ];\n }\n\n /**\n * This is email search request that Hubspot offers as GET (more generous quota)\n */\n public function getContactByEmail(string $email, array $fields = []): array\n {\n try {\n $contact = $this->getNewInstance()->crm()->contacts()->basicApi()->getById(\n $email,\n implode(',', $fields),\n null,\n false,\n 'email'\n );\n\n return [\n 'id' => $contact->getId(),\n 'properties' => $contact->getProperties(),\n ];\n } catch (ContactApiException $e) {\n $this->log->info('[Hubspot] Failed to fetch contact', [\n 'email' => $email,\n 'reason' => $e->getMessage(),\n ]);\n\n return [];\n }\n }\n\n /**\n * @throws CrmException\n */\n public function fetchProperty(string $objectType, string $propertyId): Property\n {\n $result = $this->getNewInstance()->crm()->properties()->coreApi()->getByName($objectType, $propertyId);\n\n if (! $result instanceof Property) {\n $this->log->error('[Hubspot] Failed to fetch property', [\n 'object_type' => $objectType,\n 'property_id' => $propertyId,\n 'reason' => $result->getMessage(),\n ]);\n\n throw new CrmException('Failed to fetch property');\n }\n\n return $result;\n }\n\n /**\n * @return array<CrmFieldOption>\n */\n public function fetchPropertyOptions(string $objectType, string $propertyId): array\n {\n /** @var array<CrmFieldOption> */\n return $this->fetchProperty($objectType, $propertyId)->getOptions();\n }\n\n /**\n * @return array<array{id:string, label:string, deleted:bool}>\n */\n public function fetchCallDispositions(): array\n {\n /** @var Response $response */\n $response = $this->getInstance()->engagements()->getCallDispositions();\n\n /**\n * @var array<array{\n * id:string,\n * label:string,\n * deleted: bool\n * }>\n */\n return $response->toArray();\n }\n\n /**\n * @return array<CrmFieldOption>\n */\n public function fetchOpportunityPipelineStages(): array\n {\n $stages = [];\n $apiResponse = $this->getNewInstance()->crm()->pipelines()->pipelinesApi()->getAll('deals');\n\n if ($apiResponse instanceof Error) {\n $this->log->error('[Hubspot] Failed to fetch opportunity pipelines', [\n 'reason' => $apiResponse->getMessage(),\n ]);\n\n return [];\n }\n\n foreach ($apiResponse->getResults() as $pipeline) {\n $pipelineStages = array_map(\n static function (PipelineStage $stage) {\n return [\n 'id' => $stage->getId(),\n 'label' => $stage->getLabel(),\n ];\n },\n $pipeline->getStages()\n );\n\n $stages = array_merge($stages, $pipelineStages);\n }\n\n return $stages;\n }\n\n public function fetchOpportunityPipelines(): array\n {\n $pipelines = [];\n\n try {\n $apiResponse = $this->makeRequest('/crm/v3/pipelines/deals');\n } catch (\\Exception $e) {\n $this->log->info('[Hubspot] Failed to fetch opportunity pipelines', [\n 'reason' => $e->getMessage(),\n ]);\n\n return [];\n }\n\n $response = $apiResponse->toArray();\n\n foreach ($response['results'] as $pipeline) {\n $pipelines[] = [\n 'id' => $pipeline['id'],\n 'label' => $pipeline['label'],\n ];\n }\n\n return $pipelines;\n }\n\n /**\n * @return array<CrmFieldOption>\n */\n public function fetchMeetingOutcomeFieldOptions(Field $field): array\n {\n return $field->getCrmProviderId() === 'meetingOutcome'\n ? $this->fetchMeetingOutcomeTypes()\n : $this->fetchCallActivityTypes();\n }\n\n public function fetchMeetingOutcomeTypes(): array\n {\n return $this->extractMeetingTypeOptions(\n 'https://api.hubapi.com/crm/v3/properties/meeting/hs_meeting_outcome'\n );\n }\n\n public function fetchCallActivityTypes(): array\n {\n return $this->extractMeetingTypeOptions(\n 'https://api.hubapi.com/crm/v3/properties/call/hs_activity_type'\n );\n }\n\n private function extractMeetingTypeOptions(string $endpoint): array\n {\n /** @var Response $response */\n $response = $this->getInstance()\n ->getClient()\n ->request('GET', $endpoint);\n\n /**\n * @var array<array{\n * value: string,\n * label: string,\n * displayOrder: int\n * }> $optionData\n */\n $optionData = $response->toArray()['options'] ?? [];\n\n $options = [];\n foreach ($optionData as $item) {\n $options[] = [\n 'id' => $item['value'],\n 'value' => $item['value'],\n 'label' => $item['label'],\n 'display_order' => $item['displayOrder'],\n ];\n }\n\n return $options;\n }\n\n /**\n * @return array<CrmFieldOption>\n */\n public function fetchDispositionFieldOptions(): array\n {\n $options = [];\n\n $dispositions = $this->fetchCallDispositions();\n\n foreach ($dispositions as $disposition) {\n if ($disposition['deleted'] !== false) {\n continue;\n }\n\n $option['value'] = $disposition['id'];\n $option['id'] = $disposition['id'];\n $option['label'] = $disposition['label'];\n\n $options[] = $option;\n }\n\n return $options;\n }\n\n /**\n * @return array<CrmFieldOption>\n */\n public function fetchOpportunityFieldOptions(Field $field): array\n {\n if ($field->isStageField()) {\n return $this->fetchOpportunityPipelineStages();\n }\n\n if ($field->isPipelineField()) {\n return $this->fetchOpportunityPipelines();\n }\n\n return $this->fetchPropertyOptions('deals', $field->getCrmProviderId());\n }\n\n /**\n * @throws BadRequest\n * @throws HubspotException\n */\n public function makeRequest(string $endpoint, $method = 'GET', $payload = [], ?string $queryString = null)\n {\n $endpoint = self::BASE_URL . $endpoint;\n\n if ($method === 'GET') {\n $response = $this->getInstance()->getClient()?->request(\n method: $method,\n endpoint: $endpoint,\n query_string: $queryString\n );\n } else {\n $response = $this->getInstance()->getClient()->request($method, $endpoint, [\n 'json' => ($payload),\n ]);\n }\n\n $max = $response->getHeaderLine('X-HubSpot-RateLimit-Max'); // \"110\"\n $remaining = $response->getHeaderLine('X-HubSpot-RateLimit-Remaining'); // \"109\"\n $interval = $response->getHeaderLine('X-HubSpot-RateLimit-Interval-Milliseconds'); // \"10000\"\n $body = json_decode((string) $response->getBody(), true);\n\n \\Illuminate\\Support\\Facades\\Log::channel('custom_channel')->info('$max ' . PHP_EOL . print_r($max, true));\n \\Illuminate\\Support\\Facades\\Log::channel('custom_channel')->info('$remaining ' . PHP_EOL . print_r($remaining, true));\n \\Illuminate\\Support\\Facades\\Log::channel('custom_channel')->info('$interval ' . PHP_EOL . print_r($interval, true));\n \\Illuminate\\Support\\Facades\\Log::channel('custom_channel')->info('$body ' . PHP_EOL . print_r($body, true));\n\n return $response;\n }\n\n /**\n * @throws BadRequest\n * @throws HubspotException\n */\n public function createMeeting(array $payload): Response\n {\n $endpoint = '/crm/v3/objects/meetings';\n\n return $this->makeRequest($endpoint, 'POST', $payload);\n }\n\n /**\n * @throws BadRequest\n * @throws HubspotException\n */\n public function updateMeeting(string $meetingId, array $payload): Response\n {\n $endpoint = '/crm/v3/objects/meetings/' . $meetingId;\n\n return $this->makeRequest($endpoint, 'PATCH', $payload);\n }\n\n /**\n * @throws \\Exception\n */\n public function createNote(\n string $body,\n string $ownerId,\n int $timestamp,\n string $objectId,\n NoteObject $noteObject\n ): ?string {\n try {\n $noteInput = new SimplePublicObjectInput([\n 'properties' => [\n 'hs_note_body' => $body,\n 'hubspot_owner_id' => $ownerId,\n 'hs_timestamp' => $timestamp,\n ],\n ]);\n\n // Create note\n $note = $this->getNewInstance()->crm()->objects()->basicApi()->create('note', $noteInput);\n\n $this->getNewInstance()->crm()->objects()->associationsApi()->create(\n 'note',\n $note->getId(),\n $this->getNoteObject($noteObject),\n $objectId,\n $this->getNoteAssociationType($noteObject),\n );\n\n return $note->getId();\n } catch (\\Exception $e) {\n $this->log->error('[Hubspot] Failed to create note', [\n 'objectId' => $objectId,\n 'noteObject' => $noteObject->getObjectType(),\n 'reason' => $e->getMessage(),\n ]);\n\n \\Sentry::captureException($e);\n }\n\n return null;\n }\n\n public function updateEngagement(string $objectId, array $engagement, array $metadata): void\n {\n $this->getInstance()->engagements()->update($objectId, $engagement, $metadata);\n }\n\n public function getEngagementData(string $engagementId): array\n {\n $engagement = $this->getInstance()->engagements()->get($engagementId);\n\n return $engagement->toArray();\n }\n\n public function createEngagement(array $engagement, array $associations, array $metadata): Response\n {\n return $this->getInstance()\n ->engagements()\n ->create($engagement, $associations, $metadata);\n }\n\n public function isUnauthorizedException(\\Exception $e): bool\n {\n // Check for specific HubSpot API exception types first\n if ($e instanceof BadRequest) {\n // BadRequest can contain 401 status codes\n return $e->getCode() === 401;\n }\n\n // Check for HTTP client exceptions with status codes\n if ($e instanceof \\GuzzleHttp\\Exception\\RequestException && $e->hasResponse()) {\n $response = $e->getResponse();\n if ($response !== null) {\n return $response->getStatusCode() === 401;\n }\n }\n\n // Check for Guzzle HTTP exceptions\n if ($e instanceof \\GuzzleHttp\\Exception\\ClientException) {\n return $e->getCode() === 401;\n }\n\n // Fallback to string matching as last resort, but be more specific\n $message = strtolower($e->getMessage());\n\n return str_contains($message, '401 unauthorized') ||\n str_contains($message, 'http 401') ||\n str_contains($message, 'status code 401') ||\n (preg_match('/\\b401\\b/', $message) && str_contains($message, 'unauthorized'));\n }\n\n /**\n * Validates and refreshes the access token if needed before API requests.\n * This ensures long-running processes don't fail due to token expiration.\n *\n * @throws SocialAccountTokenInvalidException\n */\n public function ensureValidToken(): void\n {\n if ($this->oauthAccount === null) {\n return;\n }\n\n $newToken = $this->tokenManager->ensureValidToken($this->oauthAccount);\n if ($newToken !== null) {\n $this->accessToken = $newToken;\n }\n }\n\n public function getConfig()\n {\n return $this->config;\n }\n\n // returns only active (archived=false)\n public function getOwners(): array\n {\n return $this->getNewInstance()->crm()->owners()->getAll();\n }\n\n /**\n * @param bool $archived\n *\n * @return array<Owner>|[]\n */\n public function getOwnersArchived(bool $archived = true): array\n {\n $endpoint = '/crm/v3/owners';\n $queryParams = [\n 'archived' => $archived ? 'true' : 'false',\n ];\n $queryString = http_build_query($queryParams);\n\n $owners = [];\n\n try {\n $response = $this->makeRequest(endpoint: $endpoint, queryString: $queryString);\n $responseData = $response?->toArray();\n\n foreach ($responseData['results'] as $result) {\n try {\n $owners[] = Owner::create($result);\n } catch (Throwable $e) {\n $this->log->error('[HubSpot] Failed to process owner data', [\n 'result' => $result,\n 'error' => $e->getMessage(),\n ]);\n\n continue;\n }\n }\n } catch (Throwable $e) {\n $this->log->error('HubSpot] Failed to fetch owners', [\n 'archived' => $archived,\n 'error' => $e->getMessage(),\n ]);\n\n return [];\n }\n\n return $owners;\n }\n\n public function getMeeting(string $engagementId): ObjectWithAssociations\n {\n return $this->getNewInstance()->crm()->objects()->basicApi()\n ->getById('meeting', $engagementId, null, 'contact,company,deal');\n }\n\n public function deleteEngagement(string $engagementId): void\n {\n $this->getInstance()->engagements()->delete((int) $engagementId);\n }\n\n public function getAssociationsData(array $ids, string $fromObject, string $toObject): array\n {\n $associationData = [];\n $idChunks = array_chunk($ids, self::ASSOCIATIONS_BATCH_SIZE_LIMIT);\n\n foreach ($idChunks as $idChunk) {\n try {\n $batchInput = new \\HubSpot\\Client\\Crm\\Associations\\Model\\BatchInputPublicObjectId();\n $batchInput->setInputs(array_map(function ($id) {\n $publicObjectId = new \\HubSpot\\Client\\Crm\\Associations\\Model\\PublicObjectId();\n $publicObjectId->setId($id);\n\n return $publicObjectId;\n }, $idChunk));\n\n $associatedObjectsData = $this\n ->getNewInstance()\n ->crm()\n ->associations()\n ->batchApi()\n ->read($fromObject, $toObject, $batchInput);\n\n if ($associatedObjectsData instanceof \\HubSpot\\Client\\Crm\\Associations\\Model\\BatchResponsePublicAssociationMulti) {\n foreach ($associatedObjectsData->getResults() as $association) {\n $from = $association->getFrom()->getId();\n $toAssociations = $association->getTo();\n\n if (! empty($toAssociations)) {\n $associationData[$from] = array_map(function ($item) {\n return $item->getId();\n }, $toAssociations);\n }\n }\n }\n } catch (\\Exception $e) {\n $this->log->error('[Hubspot] Failed to fetch associations', [\n 'from_object' => $fromObject,\n 'to_object' => $toObject,\n 'reason' => $e->getMessage(),\n ]);\n }\n }\n\n return $associationData;\n }\n\n /**\n * @throws \\Exception\n */\n private function getNoteAssociationType(NoteObject $noteObject): string\n {\n return match($noteObject) {\n NoteObject::Opportunity => 'note_to_deal',\n NoteObject::Lead, NoteObject::Contact => 'note_to_contact', // or 'note_to_lead' if your portal supports it\n NoteObject::Account => 'note_to_company',\n NoteObject::Call, NoteObject::Event => throw new \\Exception('Not supported'),\n };\n }\n\n /**\n * @throws \\Exception\n */\n private function getNoteObject(NoteObject $noteObject): string\n {\n return match($noteObject) {\n NoteObject::Opportunity => 'deal',\n NoteObject::Lead, NoteObject::Contact => 'contact',\n NoteObject::Account => 'company',\n NoteObject::Call, NoteObject::Event => throw new \\Exception('Not supported'),\n };\n }\n\n public function addAssociations(string $objectType, string $associationType, array $payload): Response\n {\n $endpoint = \"/crm/v4/associations/$objectType/$associationType/batch/create\";\n\n return $this->makeRequest($endpoint, 'POST', $payload);\n }\n\n public function removeAssociations(string $objectType, string $associationType, array $payload): Response\n {\n $endpoint = \"/crm/v4/associations/$objectType/$associationType/batch/archive\";\n\n return $this->makeRequest($endpoint, 'POST', $payload);\n }\n}","role_description":"text entry area","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"Project","depth":3,"on_screen":false,"role_description":"text"},{"role":"AXButton","text":"Project","depth":3,"bounds":{"left":0.011968086,"top":0.047885075,"width":0.024268618,"height":0.024740623},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"New File or Directory…","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.008643617,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Expand Selected","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.008643617,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Collapse All","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.008643617,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Options","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.008643617,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Hide","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.008643617,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"app ~/jiminny/app, folder","depth":6,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":".circleci, folder","depth":7,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":".cursor, folder","depth":7,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":".github","depth":7,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":".sonarlint, folder","depth":7,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":".vscode, folder","depth":7,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":".windsurf, folder","depth":7,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"app, sources root","depth":7,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Actions, folder","depth":8,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Component, folder","depth":8,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Acl, folder","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"ActionItems, folder","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Activity, folder","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"ActivityAnalytics, folder","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"ActivitySearch, folder","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"AiActivityType, folder","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"AiAutomation, folder","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"AiCallScoring, folder","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"AskAnything, folder","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Dtos, folder","depth":10,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Events, folder","depth":10,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"AskAnythingPromptService.php","depth":10,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"HistoryService.php","depth":10,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"AskJiminnyAi, folder","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"AWS, folder","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"BillingManagement, folder","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Cache, folder","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"CoachingFeedback, folder","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Country, folder","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"CustomerApi, folder","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Database, folder","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Datadog, folder","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"DateTime, folder","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"DealInsights, folder","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"DealRisks, folder","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"ElasticSearch","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Eloquent","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Encoding","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Encryption","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"ES","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Faker","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"FeatureFlags","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"FFMpeg","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"FileSystem","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Gecko","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Gong","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"GuzzleHttp","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"KeyPoints","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Kiosk","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"LanguageDetection","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"LiveFeed","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Locks","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Math","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"MediaPipeline","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"MeetingBot, folder","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"MobileSettings, folder","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Model, folder","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Notification, folder","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Nudge, folder","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"ParagraphBreaker, folder","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"ParticipantSpeech, folder","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"PartitionedCookie, folder","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"PlaybackPage, folder","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Playlist, folder","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Prophet, folder","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"ProphetAi, folder","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"ProsperWorks, folder","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Queue, folder","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Job, folder","depth":10,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"RateLimitAware.php, abstract class","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"RateLimitAwareWrapper.php, class","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"BotsQueueConstants.php","depth":10,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Constants.php","depth":10,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"ProcessingQueueConstants.php","depth":10,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Router, folder","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Saml2, folder","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"SCIM, folder","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Seeder, folder","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Sentry, folder","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Serializer, folder","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Settings, folder","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Sidekick, folder","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Slack, folder","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"TeamInsights, folder","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"TimeMemoryMapper, folder","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Transcription, folder","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"TranscriptionSummary, folder","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Twilio, folder","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Uploader, folder","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"UrlGenerator, folder","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Utility, folder","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Exceptions, folder","depth":10,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Service, folder","depth":10,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"BaseRateLimiter.php, class","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"EfficientJsonParser.php, class","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"ProviderRateLimiter.php, class","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"RateLimiterInstance.php, class","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Uuid, folder","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Waveform, folder","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Webhooks, folder","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Workflow, folder","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Configuration, folder","depth":8,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Console, folder","depth":8,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Commands, folder","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Activities, folder","depth":10,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Analytics, folder","depth":10,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Calendars, folder","depth":10,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Crm, folder","depth":10,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Hubspot, folder","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"IntegrationApp, folder","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Traits, folder","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"AddLayoutEntities.php, class","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"AutologDelayedCommand.php, class","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"BullhornCommandAbstract.php, abstract class","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"BullhornPingCommand.php, class","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"BullhornSearchCommand.php, class","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"BullhornSessionCommand.php, class","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"CheckActivityLoggableCommand.php, final class","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"CleanDuplicateFieldDataCommand.php, class","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"FullSyncOpportunityCommand.php, class","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"LogActivitiesCommand.php, final class","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"ManageSyncStrategyCommand.php, class","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"MatchCrmObjectsCommand.php, class","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"MatchOpportunityActivitiesCommand.php, class","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"MigrateProvider.php, class","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"ProcessHubspotObjectsSyncBatches.php, class","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"PurgeDeletedOpportunitiesCommand.php, class","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"ResetGovernorLimits.php, class","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"SendNotLogged.php, class","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"SetupActivityTypeForFollowUp.php, final class","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"SetupCloseCrm.php, class","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"SetupCopperCrm.php, class","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"SetupCrmCommand.php, abstract class","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"SetupLayouts.php, class","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"SyncAccount.php, class","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"SyncContact.php, class","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"SyncFieldMetadata.php, class","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"SyncHubspotActiveDeals.php, class","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"SyncHubspotObjects.php, class","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"SyncLead.php, class","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"SyncObjects.php, class","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"SyncOpportunitiesMissingFieldDataCommand.php, class","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"SyncOpportunity.php, class","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"SyncProfileMetadata.php, class","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"SyncTeamMetadata.php, class","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"UpdateOpportunitySpecifications.php, class","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"DealInsights, folder","depth":10,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Dev, folder","depth":10,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"AddRateLimitCommand.php, class","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"FixHubSpotTokens.php, class","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"FixMissMatchedCrmActivitiesCommand.php, final class","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"ImportCallsCommand.php, class","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"MonitorSocialAccountsState.php, class","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Dialers, folder","depth":10,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"DTOs, folder","depth":10,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Elasticsearch, folder","depth":10,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"EngagementStats, folder","depth":10,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"GeckoExport, folder","depth":10,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Livestream, folder","depth":10,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Mailboxes, folder","depth":10,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Migrate, folder","depth":10,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"PlaybackThemes, folder","depth":10,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Playbooks, folder","depth":10,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Playlists, folder","depth":10,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Postmark, folder","depth":10,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"ProphetAi, folder","depth":10,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Reports, folder","depth":10,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"AutomatedReportsCommand.php, class","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"AutomatedReportsRetentionPolicyCommand.php, class","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"AutomatedReportsSendCommand.php, class","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"CreateMockAskJiminnyReportResultCommand.php, class","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"DeleteReportCommand.php, class","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"GenerateMarketingReport.php, class","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Team.php, class","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Usage.php, class","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Slack, folder","depth":10,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Teams, folder","depth":10,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Tracks, folder","depth":10,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Transcription, folder","depth":10,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Twilio, folder","depth":10,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Users, folder","depth":10,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Vocabulary, folder","depth":10,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Zoom, folder","depth":10,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Command.php","depth":10,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"CreateDatabaseUsers.php","depth":10,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"DatabaseTableCount.php","depth":10,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"DeleteOldAiCrmNotesCommand.php","depth":10,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"DeleteS3LeftoversCommand.php","depth":10,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"DevPostmanCommand.php","depth":10,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"DiarizeViaAiParticipantIdentificationCommand.php","depth":10,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"EncryptTokensCommand.php","depth":10,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"EngagementStatsRegenerateCommand.php","depth":10,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"FeatureFlagsHelper.php","depth":10,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"FixCrossTenantIssues.php","depth":10,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"FlushRolesPermissionsCache.php","depth":10,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"GenerateInternalWebhookToken.php","depth":10,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"GroupSetDefaultLanguageCommand.php","depth":10,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"HelperTruncateCoachingTables.php","depth":10,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"HubspotJournalPollingCommand.php","depth":10,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"HubspotWebhookServiceCommand.php","depth":10,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"ImportRecording.php","depth":10,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"ImportUsersFromCsvFile.php","depth":10,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"IterateUsersCommand.php","depth":10,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"JiminnyCacheClearCommand.php","depth":10,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"JiminnyDebugCommand.php","depth":10,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"JiminnySetEncryptedTokenManagerModeCommand.php","depth":10,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"JiminnyTokenInfoCommand.php","depth":10,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"MakeSlackLiveCoachingChatNotesOn.php","depth":10,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"ManageScimForTeam.php","depth":10,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"MarkBranchForEnvironmentPipelineCommand.php","depth":10,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"MuteOrganizerChannel.php, class","depth":10,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"PhpApm.php, class","depth":10,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"PropagateCoachingFeedbackCreatedAtToSectionFeedbacks.php, class","depth":10,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"PurgeConferences.php, class","depth":10,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"PurgeSoftDeletedOpportunitiesCommand.php, class","depth":10,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"PurgeSyncBatchesCommand.php, class","depth":10,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"RecalculateDealRisksCommand.php, class","depth":10,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"RemoveDeleteMarkersCommand.php","depth":10,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"RemoveExpiredNudgesCommand.php","depth":10,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"RemoveUnusedParticipantSpeechesCommand.php","depth":10,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"ResetElasticSearch.php","depth":10,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"RestoreActivityCrmProviderIdCommand.php","depth":10,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"RestoreActivityTypeCommand.php","depth":10,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"RunAiCallScoringForUntypedActivitiesCommand.php","depth":10,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"SeedActivities.php","depth":10,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"SendNudgeExpirationWarningsCommand.php","depth":10,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"SyncActivity.php","depth":10,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"TrackImported.php","depth":10,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"WhichWorkerIsWorkingOnWhichJob.php","depth":10,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Scheduling, folder","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Kernel.php","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Contracts, folder","depth":8,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Acl, folder","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"ActivitySearch, folder","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"AiAutomation, folder","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Crm, folder","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"DateTime, folder","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"ES, folder","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Http, folder","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Requests, folder","depth":10,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"ApiResponse.php","depth":10,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"RateLimited.php","depth":10,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"RateLimitInterface.php","depth":10,"on_screen":false,"role_description":"text"}]...
|
2832706491500642659
|
-8151256403349000395
|
visual_change
|
accessibility
|
NULL
|
This view is read-only
text/html
text/html
text/ht This view is read-only
text/html
text/html
text/html
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
[2026-04-27 10:26:25] local.INFO: Jiminny\Console\Commands\Command::run Memory usage before starting command {"command":"mailbox:sync","memoryBeforeCommandInMb":60.0,"memoryPeakBeforeCommandInMb":99.727} {"correlation_id":"7b1ea2fb-8f36-4897-9970-91b4abba2cc7","trace_id":"265be7c0-bf8f-4037-a0d2-57df3ef1d7e1"}
[2026-04-27 10:26:25] local.INFO: [EmailSchedule] STARTING Inbox Sync {"host":"docker_lamp_1"} {"correlation_id":"7b1ea2fb-8f36-4897-9970-91b4abba2cc7","trace_id":"265be7c0-bf8f-4037-a0d2-57df3ef1d7e1"}
[2026-04-27 10:26:25] local.INFO: [EmailSchedule] FINISHED Inbox Sync {"host":"docker_lamp_1","events":2} {"correlation_id":"7b1ea2fb-8f36-4897-9970-91b4abba2cc7","trace_id":"265be7c0-bf8f-4037-a0d2-57df3ef1d7e1"}
[2026-04-27 10:26:25] local.INFO: Jiminny\Console\Commands\Command::run Memory usage for command {"command":"mailbox:sync","memoryBeforeCommandInMb":60.0,"memoryAfterCommandInMB":62.0,"memoryPeakBeforeCommandInMb":99.727,"memoryPeakAfterCommandInMB":99.727} {"correlation_id":"7b1ea2fb-8f36-4897-9970-91b4abba2cc7","trace_id":"265be7c0-bf8f-4037-a0d2-57df3ef1d7e1"}
[2026-04-27 10:26:26] local.INFO: [automated-reports] Started {"correlation_id":"d3f79034-397b-4936-9305-bdd99b23f2dd","trace_id":"aa0f8702-73eb-4947-a765-2e89244599c3"}
[2026-04-27 10:26:26] local.INFO: [automated-reports] Checking conditions {"isMonday":true,"isWeekend":false,"isFirstDayOfMonth":false,"currentMonth":4,"isQuarterlyMonth":true} {"correlation_id":"d3f79034-397b-4936-9305-bdd99b23f2dd","trace_id":"aa0f8702-73eb-4947-a765-2e89244599c3"}
[2026-04-27 10:26:26] local.INFO: [automated-reports] Processing daily reports {"correlation_id":"d3f79034-397b-4936-9305-bdd99b23f2dd","trace_id":"aa0f8702-73eb-4947-a765-2e89244599c3"}
[2026-04-27 10:26:26] local.INFO: [automated-reports] Found 1 daily reports to process {"correlation_id":"d3f79034-397b-4936-9305-bdd99b23f2dd","trace_id":"aa0f8702-73eb-4947-a765-2e89244599c3"}
[2026-04-27 10:26:26] local.INFO: [automated-reports] Dispatching Generate Report job for report {"reportUuid":"4f6ca2b5-1993-48aa-99ad-b66f19f15d43","teamId":1,"frequency":"weekly","type":"ask_jiminny"} {"correlation_id":"d3f79034-397b-4936-9305-bdd99b23f2dd","trace_id":"aa0f8702-73eb-4947-a765-2e89244599c3"}
[2026-04-27 10:26:26] local.INFO: [automated-reports] Processing weekly reports {"correlation_id":"d3f79034-397b-4936-9305-bdd99b23f2dd","trace_id":"aa0f8702-73eb-4947-a765-2e89244599c3"}
[2026-04-27 10:26:26] local.INFO: [automated-reports] Found 1 weekly reports to process {"correlation_id":"d3f79034-397b-4936-9305-bdd99b23f2dd","trace_id":"aa0f8702-73eb-4947-a765-2e89244599c3"}
[2026-04-27 10:26:26] local.INFO: [automated-reports] Dispatching Generate Report job for report {"reportUuid":"4f6ca2b5-1993-48aa-99ad-b66f19f15d43","teamId":1,"frequency":"weekly","type":"ask_jiminny"} {"correlation_id":"d3f79034-397b-4936-9305-bdd99b23f2dd","trace_id":"aa0f8702-73eb-4947-a765-2e89244599c3"}
[2026-04-27 10:26:26] local.INFO: [automated-reports] Completed {"correlation_id":"d3f79034-397b-4936-9305-bdd99b23f2dd","trace_id":"aa0f8702-73eb-4947-a765-2e89244599c3"}
[2026-04-27 10:26:26] local.INFO: [HubSpot Journal Polling] Getting offset from database {"offset":"","jiminny_team_id":1} {"correlation_id":"b453f0f4-0d2c-491c-a935-14c60265ec18","trace_id":"a50202d6-5601-46b4-a02e-cf5ac8d061fa"}
[2026-04-27 10:26:26] local.INFO: [HubSpot Journal API] Fetching latest journal entry {"url":"[URL_WITH_CREDENTIALS] {"correlation_id":"d3e3eb3d-5c83-4d6e-9592-aaa38c1843a4","trace_id":"265be7c0-bf8f-4037-a0d2-57df3ef1d7e1"}
[2026-04-27 10:26:28] local.INFO: [Gmail] imported 7 emails via full sync workflow for inbox 212 {"correlation_id":"d3e3eb3d-5c83-4d6e-9592-aaa38c1843a4","trace_id":"265be7c0-bf8f-4037-a0d2-57df3ef1d7e1"}
[2026-04-27 10:26:28] local.INFO: [Gmail] seeding inbox 212 with last message time : 2026-04-27 10:24:57 {"correlation_id":"d3e3eb3d-5c83-4d6e-9592-aaa38c1843a4","trace_id":"265be7c0-bf8f-4037-a0d2-57df3ef1d7e1"}
[2026-04-27 10:26:28] local.INFO: [Sync Mailbox] Sync complete {"inbox_id":212} {"correlation_id":"d3e3eb3d-5c83-4d6e-9592-aaa38c1843a4","trace_id":"265be7c0-bf8f-4037-a0d2-57df3ef1d7e1"}
[2026-04-27 10:27:04] local.INFO: Jiminny\Console\Commands\Command::run Memory usage before starting command {"command":"meeting-bot:schedule-bot","memoryBeforeCommandInMb":60.0,"memoryPeakBeforeCommandInMb":99.727} {"correlation_id":"f55fbf77-7e5f-4c87-accd-011697ca0fa3","trace_id":"9bd1c431-26de-4e31-83d8-3129ed9b3b79"}
[2026-04-27 10:27:04] local.INFO: [ScheduleBotCommand] Number of activities to be captured: 0 {"correlation_id":"f55fbf77-7e5f-4c87-accd-011697ca0fa3","trace_id":"9bd1c431-26de-4e31-83d8-3129ed9b3b79"}
[2026-04-27 10:27:04] local.INFO: Jiminny\Console\Commands\Command::run Memory usage for command {"command":"meeting-bot:schedule-bot","memoryBeforeCommandInMb":60.0,"memoryAfterCommandInMB":62.0,"memoryPeakBeforeCommandInMb":99.727,"memoryPeakAfterCommandInMB":99.727} {"correlation_id":"f55fbf77-7e5f-4c87-accd-011697ca0fa3","trace_id":"9bd1c431-26de-4e31-83d8-3129ed9b3b79"}
[2026-04-27 10:27:05] local.INFO: Jiminny\Console\Commands\Command::run Memory usage before starting command {"command":"dialers:monitor-activities","memoryBeforeCommandInMb":60.0,"memoryPeakBeforeCommandInMb":99.727} {"correlation_id":"da241053-5f3b-4d02-9504-4ef2987cdbd7","trace_id":"364d3fc7-0f11-40c6-8697-d18eff2c4cb6"}
[2026-04-27 10:27:05] local.INFO: Jiminny\Console\Commands\Command::run Memory usage for command {"command":"dialers:monitor-activities","memoryBeforeCommandInMb":60.0,"memoryAfterCommandInMB":62.0,"memoryPeakBeforeCommandInMb":99.727,"memoryPeakAfterCommandInMB":99.727} {"correlation_id":"da241053-5f3b-4d02-9504-4ef2987cdbd7","trace_id":"364d3fc7-0f11-40c6-8697-d18eff2c4cb6"}
[2026-04-27 10:27:06] local.NOTICE: Monitoring start {"correlation_id":"ad0d871a-a1fd-4725-b2f7-ac8f0fc1857e","trace_id":"cb504e84-d924-43a0-82d5-faf1840134ba"}
[2026-04-27 10:27:06] local.NOTICE: Monitoring end {"correlation_id":"ad0d871a-a1fd-4725-b2f7-ac8f0fc1857e","trace_id":"cb504e84-d924-43a0-82d5-faf1840134ba"}
[2026-04-27 10:27:08] local.INFO: Jiminny\Console\Commands\Command::run Memory usage before starting command {"command":"mailbox:skip-lists:refresh","memoryBeforeCommandInMb":60.0,"memoryPeakBeforeCommandInMb":99.727} {"correlation_id":"b8652f0d-1cee-44ae-a0ea-3d3137a882c1","trace_id":"7fe19940-0813-4199-a2a8-958de09bd0ee"}
[2026-04-27 10:27:08] local.INFO: Jiminny\Console\Commands\Command::run Memory usage for command {"command":"mailbox:skip-lists:refresh","memoryBeforeCommandInMb":60.0,"memoryAfterCommandInMB":62.0,"memoryPeakBeforeCommandInMb":99.727,"memoryPeakAfterCommandInMB":99.727} {"correlation_id":"b8652f0d-1cee-44ae-a0ea-3d3137a882c1","trace_id":"7fe19940-0813-4199-a2a8-958de09bd0ee"}
[2026-04-27 10:27:09] local.INFO: Jiminny\Console\Commands\Command::run Memory usage before starting command {"command":"mailbox:batch:process","memoryBeforeCommandInMb":60.0,"memoryPeakBeforeCommandInMb":99.727} {"correlation_id":"06190462-0f47-403f-8e95-ad5d05bff1fe","trace_id":"4aa6bed6-05a1-4661-a9c9-edadc61d84ac"}
[2026-04-27 10:27:09] local.INFO: [EmailSchedule] STARTING batch process {"host":"docker_lamp_1"} {"correlation_id":"06190462-0f47-403f-8e95-ad5d05bff1fe","trace_id":"4aa6bed6-05a1-4661-a9c9-edadc61d84ac"}
[2026-04-27 10:27:09] local.INFO: [EmailSchedule] FINISHED batch process {"host":"docker_lamp_1","processed":0} {"correlation_id":"06190462-0f47-403f-8e95-ad5d05bff1fe","trace_id":"4aa6bed6-05a1-4661-a9c9-edadc61d84ac"}
[2026-04-27 10:27:09] local.INFO: Jiminny\Console\Commands\Command::run Memory usage for command {"command":"mailbox:batch:process","memoryBeforeCommandInMb":60.0,"memoryAfterCommandInMB":62.0,"memoryPeakBeforeCommandInMb":99.727,"memoryPeakAfterCommandInMB":99.727} {"correlation_id":"06190462-0f47-403f-8e95-ad5d05bff1fe","trace_id":"4aa6bed6-05a1-4661-a9c9-edadc61d84ac"}
[2026-04-27 10:27:11] local.INFO: Jiminny\Console\Commands\Command::run Memory usage before starting command {"command":"mailbox:batch:create","memoryBeforeCommandInMb":60.0,"memoryPeakBeforeCommandInMb":99.727} {"correlation_id":"b561c5d5-e6e3-4091-9517-b0458190a360","trace_id":"bf9679fb-b785-40cb-b814-04a1dc385a1c"}
[2026-04-27 10:27:11] local.INFO: [EmailSchedule] STARTING batch create {"host":"docker_lamp_1"} {"correlation_id":"b561c5d5-e6e3-4091-9517-b0458190a360","trace_id":"bf9679fb-b785-40cb-b814-04a1dc385a1c"}
[2026-04-27 10:27:11] local.INFO: [EmailSchedule] FINISHED batch create {"host":"docker_lamp_1"} {"correlation_id":"b561c5d5-e6e3-4091-9517-b0458190a360","trace_id":"bf9679fb-b785-40cb-b814-04a1dc385a1c"}
[2026-04-27 10:27:11] local.INFO: Jiminny\Console\Commands\Command::run Memory usage for command {"command":"mailbox:batch:create","memoryBeforeCommandInMb":60.0,"memoryAfterCommandInMB":62.0,"memoryPeakBeforeCommandInMb":99.727,"memoryPeakAfterCommandInMB":99.727} {"correlation_id":"b561c5d5-e6e3-4091-9517-b0458190a360","trace_id":"bf9679fb-b785-40cb-b814-04a1dc385a1c"}
[2026-04-27 10:27:14] local.INFO: [Jiminny\Jobs\Mailbox\CreateBatches] processed 2 inboxes and created 1 batches {"userId":null,"batchSize":30,"maxBatches":1000} {"correlation_id":"98416296-43c2-4834-927e-4a0e86dc99cf","trace_id":"bf9679fb-b785-40cb-b814-04a1dc385a1c"}
[2026-04-27 10:28:04] local.INFO: Jiminny\Console\Commands\Command::run Memory usage before starting command {"command":"meeting-bot:schedule-bot","memoryBeforeCommandInMb":60.0,"memoryPeakBeforeCommandInMb":99.727} {"correlation_id":"5852bcf1-af92-4d24-8a2d-08a9d4a5cfac","trace_id":"25af5e8c-a148-493e-ac9f-4d53f1960177"}
[2026-04-27 10:28:04] local.INFO: [ScheduleBotCommand] Number of activities to be captured: 0 {"correlation_id":"5852bcf1-af92-4d24-8a2d-08a9d4a5cfac","trace_id":"25af5e8c-a148-493e-ac9f-4d53f1960177"}
[2026-04-27 10:28:04] local.INFO: Jiminny\Console\Commands\Command::run Memory usage for command {"command":"meeting-bot:schedule-bot","memoryBeforeCommandInMb":60.0,"memoryAfterCommandInMB":62.0,"memoryPeakBeforeCommandInMb":99.727,"memoryPeakAfterCommandInMB":99.727} {"correlation_id":"5852bcf1-af92-4d24-8a2d-08a9d4a5cfac","trace_id":"25af5e8c-a148-493e-ac9f-4d53f1960177"}
[2026-04-27 10:28:06] local.INFO: Jiminny\Console\Commands\Command::run Memory usage before starting command {"command":"dialers:monitor-activities","memoryBeforeCommandInMb":60.0,"memoryPeakBeforeCommandInMb":99.727} {"correlation_id":"cc1cff1f-71b4-4bb1-800f-66e27c0f2b3a","trace_id":"c03103a3-9ab8-480b-9753-7c7c6488f246"}
[2026-04-27 10:28:06] local.INFO: Jiminny\Console\Commands\Command::run Memory usage for command {"command":"dialers:monitor-activities","memoryBeforeCommandInMb":60.0,"memoryAfterCommandInMB":62.0,"memoryPeakBeforeCommandInMb":99.727,"memoryPeakAfterCommandInMB":99.727} {"correlation_id":"cc1cff1f-71b4-4bb1-800f-66e27c0f2b3a","trace_id":"c03103a3-9ab8-480b-9753-7c7c6488f246"}
[2026-04-27 10:28:09] local.ERROR: Target class [Jiminny\Repositories\AjReportsRepository] does not exist. {"exception":"[object] (Illuminate\\Contracts\\Container\\BindingResolutionException(code: 0): Target class [Jiminny\\Repositories\\AjReportsRepository] does not exist. at /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php:1124)
[stacktrace]
#0 /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php(933): Illuminate\\Container\\Container->build('Jiminny\\\\Reposit...')
#1 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Application.php(1078): Illuminate\\Container\\Container->resolve('Jiminny\\\\Reposit...', Array, true)
#2 /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php(864): Illuminate\\Foundation\\Application->resolve('Jiminny\\\\Reposit...', Array)
#3 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Application.php(1058): Illuminate\\Container\\Container->make('Jiminny\\\\Reposit...', Array)
#4 /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php(1336): Illuminate\\Foundation\\Application->make('Jiminny\\\\Reposit...')
#5 /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php(1237): Illuminate\\Container\\Container->resolveClass(Object(ReflectionParameter))
#6 /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php(1162): Illuminate\\Container\\Container->resolveDependencies(Array)
#7 /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php(933): Illuminate\\Container\\Container->build('Jiminny\\\\Http\\\\Co...')
#8 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Application.php(1078): Illuminate\\Container\\Container->resolve('Jiminny\\\\Http\\\\Co...', Array, true)
#9 /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php(864): Illuminate\\Foundation\\Application->resolve('Jiminny\\\\Http\\\\Co...', Array)
#10 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Application.php(1058): Illuminate\\Container\\Container->make('Jiminny\\\\Http\\\\Co...', Array)
#11 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Route.php(286): Illuminate\\Foundation\\Application->make('Jiminny\\\\Http\\\\Co...')
#12 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Route.php(1133): Illuminate\\Routing\\Route->getController()
#13 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Route.php(1062): Illuminate\\Routing\\Route->controllerMiddleware()
#14 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Router.php(834): Illuminate\\Routing\\Route->gatherMiddleware()
#15 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Router.php(816): Illuminate\\Routing\\Router->gatherRouteMiddleware(Object(Illuminate\\Routing\\Route))
#16 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Router.php(800): Illuminate\\Routing\\Router->runRouteWithinStack(Object(Illuminate\\Routing\\Route), Object(Illuminate\\Http\\Request))
#17 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Router.php(764): Illuminate\\Routing\\Router->runRoute(Object(Illuminate\\Http\\Request), Object(Illuminate\\Routing\\Route))
#18 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Router.php(753): Illuminate\\Routing\\Router->dispatchToRoute(Object(Illuminate\\Http\\Request))
#19 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Http/Kernel.php(200): Illuminate\\Routing\\Router->dispatch(Object(Illuminate\\Http\\Request))
#20 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(180): Illuminate\\Foundation\\Http\\Kernel->Illuminate\\Foundation\\Http\\{closure}(Object(Illuminate\\Http\\Request))
#21 /home/jiminny/vendor/barryvdh/laravel-debugbar/src/Middleware/InjectDebugbar.php(59): Illuminate\\Pipeline\\Pipeline->Illuminate\\Pipeline\\{closure}(Object(Illuminate\\Http\\Request))
#22 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(219): Barryvdh\\Debugbar\\Middleware\\InjectDebugbar->handle(Object(Illuminate\\Http\\Request), Object(Closure))
#23 /home/jiminny/vendor/laravel/framework/src/Illuminate/Http/Middleware/HandleCors.php(74): Illuminate\\Pipeline\\Pipeline->Illuminate\\Pipeline\\{closure}(Object(Illuminate\\Http\\Request))
#24 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(219): Illuminate\\Http\\Middleware\\HandleCors->handle(Object(Illuminate\\Http\\Request), Object(Closure))
#25 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Http/Middleware/TransformsRequest.php(21): Illuminate\\Pipeline\\Pipeline->Illuminate\\Pipeline\\{closure}(Object(Illuminate\\Http\\Request))
#26 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Http/Middleware/TrimStrings.php(51): Illuminate\\Foundation\\Http\\Middleware\\TransformsRequest->handle(Object(Illuminate\\Http\\Request), Object(Closure))
#27 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(219): Illuminate\\Foundation\\Http\\Middleware\\TrimStrings->handle(Object(Illuminate\\Http\\Request), Object(Closure))
#28 /home/jiminny/vendor/bepsvpt/secure-headers/src/SecureHeadersMiddleware.php(18): Illuminate\\Pipeline\\Pipeline->Illuminate\\Pipeline\\{closure}(Object(Illuminate\\Http\\Request))
#29 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(219): Bepsvpt\\SecureHeaders\\SecureHeadersMiddleware->handle(Object(Illuminate\\Http\\Request), Object(Closure))
#30 /home/jiminny/app/Http/Middleware/SentryContext.php(60): Illuminate\\Pipeline\\Pipeline->Illuminate\\Pipeline\\{closure}(Object(Illuminate\\Http\\Request))
#31 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(219): Jiminny\\Http\\Middleware\\SentryContext->handle(Object(Illuminate\\Http\\Request), Object(Closure))
#32 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Http/Middleware/PreventRequestsDuringMaintenance.php(109): Illuminate\\Pipeline\\Pipeline->Illuminate\\Pipeline\\{closure}(Object(Illuminate\\Http\\Request))
#33 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(219): Illuminate\\Foundation\\Http\\Middleware\\PreventRequestsDuringMaintenance->handle(Object(Illuminate\\Http\\Request), Object(Closure))
#34 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Http/Middleware/InvokeDeferredCallbacks.php(22): Illuminate\\Pipeline\\Pipeline->Illuminate\\Pipeline\\{closure}(Object(Illuminate\\Http\\Request))
#35 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(219): Illuminate\\Foundation\\Http\\Middleware\\InvokeDeferredCallbacks->handle(Object(Illuminate\\Http\\Request), Object(Closure))
#36 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(137): Illuminate\\Pipeline\\Pipeline->Illuminate\\Pipeline\\{closure}(Object(Illuminate\\Http\\Request))
#37 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Http/Kernel.php(175): Illuminate\\Pipeline\\Pipeline->then(Object(Closure))
#38 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Http/Kernel.php(144): Illuminate\\Foundation\\Http\\Kernel->sendRequestThroughRouter(Object(Illuminate\\Http\\Request))
#39 /home/jiminny/public/index.php(51): Illuminate\\Foundation\\Http\\Kernel->handle(Object(Illuminate\\Http\\Request))
#40 {main}
[previous exception] [object] (ReflectionException(code: -1): Class \"Jiminny\\Repositories\\AjReportsRepository\" does not exist at /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php:1122)
[stacktrace]
#0 /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php(1122): ReflectionClass->__construct('Jiminny\\\\Reposit...')
#1 /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php(933): Illuminate\\Container\\Container->build('Jiminny\\\\Reposit...')
#2 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Application.php(1078): Illuminate\\Container\\Container->resolve('Jiminny\\\\Reposit...', Array, true)
#3 /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php(864): Illuminate\\Foundation\\Application->resolve('Jiminny\\\\Reposit...', Array)
#4 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Application.php(1058): Illuminate\\Container\\Container->make('Jiminny\\\\Reposit...', Array)
#5 /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php(1336): Illuminate\\Foundation\\Application->make('Jiminny\\\\Reposit...')
#6 /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php(1237): Illuminate\\Container\\Container->resolveClass(Object(ReflectionParameter))
#7 /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php(1162): Illuminate\\Container\\Container->resolveDependencies(Array)
#8 /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php(933): Illuminate\\Container\\Container->build('Jiminny\\\\Http\\\\Co...')
#9 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Application.php(1078): Illuminate\\Container\\Container->resolve('Jiminny\\\\Http\\\\Co...', Array, true)
#10 /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php(864): Illuminate\\Foundation\\Application->resolve('Jiminny\\\\Http\\\\Co...', Array)
#11 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Application.php(1058): Illuminate\\Container\\Container->make('Jiminny\\\\Http\\\\Co...', Array)
#12 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Route.php(286): Illuminate\\Foundation\\Application->make('Jiminny\\\\Http\\\\Co...')
#13 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Route.php(1133): Illuminate\\Routing\\Route->getController()
#14 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Route.php(1062): Illuminate\\Routing\\Route->controllerMiddleware()
#15 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Router.php(834): Illuminate\\Routing\\Route->gatherMiddleware()
#16 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Router.php(816): Illuminate\\Routing\\Router->gatherRouteMiddleware(Object(Illuminate\\Routing\\Route))
#17 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Router.php(800): Illuminate\\Routing\\Router->runRouteWithinStack(Object(Illuminate\\Routing\\Route), Object(Illuminate\\Http\\Request))
#18 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Router.php(764): Illuminate\\Routing\\Router->runRoute(Object(Illuminate\\Http\\Request), Object(Illuminate\\Routing\\Route))
#19 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Router.php(753): Illuminate\\Routing\\Router->dispatchToRoute(Object(Illuminate\\Http\\Request))
#20 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Http/Kernel.php(200): Illuminate\\Routing\\Router->dispatch(Object(Illuminate\\Http\\Request))
#21 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(180): Illuminate\\Foundation\\Http\\Kernel->Illuminate\\Foundation\\Http\\{closure}(Object(Illuminate\\Http\\Request))
#22 /home/jiminny/vendor/barryvdh/laravel-debugbar/src/Middleware/InjectDebugbar.php(59): Illuminate\\Pipeline\\Pipeline->Illuminate\\Pipeline\\{closure}(Object(Illuminate\\Http\\Request))
#23 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(219): Barryvdh\\Debugbar\\Middleware\\InjectDebugbar->handle(Object(Illuminate\\Http\\Request), Object(Closure))
#24 /home/jiminny/vendor/laravel/framework/src/Illuminate/Http/Middleware/HandleCors.php(74): Illuminate\\Pipeline\\Pipeline->Illuminate\\Pipeline\\{closure}(Object(Illuminate\\Http\\Request))
#25 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(219): Illuminate\\Http\\Middleware\\HandleCors->handle(Object(Illuminate\\Http\\Request), Object(Closure))
#26 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Http/Middleware/TransformsRequest.php(21): Illuminate\\Pipeline\\Pipeline->Illuminate\\Pipeline\\{closure}(Object(Illuminate\\Http\\Request))
#27 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Http/Middleware/TrimStrings.php(51): Illuminate\\Foundation\\Http\\Middleware\\TransformsRequest->handle(Object(Illuminate\\Http\\Request), Object(Closure))
#28 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(219): Illuminate\\Foundation\\Http\\Middleware\\TrimStrings->handle(Object(Illuminate\\Http\\Request), Object(Closure))
#29 /home/jiminny/vendor/bepsvpt/secure-headers/src/SecureHeadersMiddleware.php(18): Illuminate\\Pipeline\\Pipeline->Illuminate\\Pipeline\\{closure}(Object(Illuminate\\Http\\Request))
#30 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(219): Bepsvpt\\SecureHeaders\\SecureHeadersMiddleware->handle(Object(Illuminate\\Http\\Request), Object(Closure))
#31 /home/jiminny/app/Http/Middleware/SentryContext.php(60): Illuminate\\Pipeline\\Pipeline->Illuminate\\Pipeline\\{closure}(Object(Illuminate\\Http\\Request))
#32 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(219): Jiminny\\Http\\Middleware\\SentryContext->handle(Object(Illuminate\\Http\\Request), Object(Closure))
#33 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Http/Middleware/PreventRequestsDuringMaintenance.php(109): Illuminate\\Pipeline\\Pipeline->Illuminate\\Pipeline\\{closure}(Object(Illuminate\\Http\\Request))
#34 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(219): Illuminate\\Foundation\\Http\\Middleware\\PreventRequestsDuringMaintenance->handle(Object(Illuminate\\Http\\Request), Object(Closure))
#35 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Http/Middleware/InvokeDeferredCallbacks.php(22): Illuminate\\Pipeline\\Pipeline->Illuminate\\Pipeline\\{closure}(Object(Illuminate\\Http\\Request))
#36 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(219): Illuminate\\Foundation\\Http\\Middleware\\InvokeDeferredCallbacks->handle(Object(Illuminate\\Http\\Request), Object(Closure))
#37 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(137): Illuminate\\Pipeline\\Pipeline->Illuminate\\Pipeline\\{closure}(Object(Illuminate\\Http\\Request))
#38 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Http/Kernel.php(175): Illuminate\\Pipeline\\Pipeline->then(Object(Closure))
#39 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Http/Kernel.php(144): Illuminate\\Foundation\\Http\\Kernel->sendRequestThroughRouter(Object(Illuminate\\Http\\Request))
#40 /home/jiminny/public/index.php(51): Illuminate\\Foundation\\Http\\Kernel->handle(Object(Illuminate\\Http\\Request))
#41 {main}
"} {"correlation_id":"dfad8817-1dde-4622-8320-f5fc1616a8fe","trace_id":"d21232b2-60fb-4ca0-838c-ba42e61593f9"}
[2026-04-27 10:28:09] local.NOTICE: Monitoring start {"correlation_id":"93744c67-4444-41ff-aaad-6aa9cafe294e","trace_id":"80e396b0-b1ac-4ec8-838c-00021f98072c"}
[2026-04-27 10:28:09] local.NOTICE: Monitoring end {"correlation_id":"93744c67-4444-41ff-aaad-6aa9cafe294e","trace_id":"80e396b0-b1ac-4ec8-838c-00021f98072c"}
[2026-04-27 10:28:10] local.ERROR: Target class [Jiminny\Repositories\AjReportsRepository] does not exist. {"exception":"[object] (Illuminate\\Contracts\\Container\\BindingResolutionException(code: 0): Target class [Jiminny\\Repositories\\AjReportsRepository] does not exist. at /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php:1124)
[stacktrace]
#0 /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php(933): Illuminate\\Container\\Container->build('Jiminny\\\\Reposit...')
#1 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Application.php(1078): Illuminate\\Container\\Container->resolve('Jiminny\\\\Reposit...', Array, true)
#2 /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php(864): Illuminate\\Foundation\\Application->resolve('Jiminny\\\\Reposit...', Array)
#3 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Application.php(1058): Illuminate\\Container\\Container->make('Jiminny\\\\Reposit...', Array)
#4 /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php(1336): Illuminate\\Foundation\\Application->make('Jiminny\\\\Reposit...')
#5 /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php(1237): Illuminate\\Container\\Container->resolveClass(Object(ReflectionParameter))
#6 /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php(1162): Illuminate\\Container\\Container->resolveDependencies(Array)
#7 /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php(933): Illuminate\\Container\\Container->build('Jiminny\\\\Http\\\\Co...')
#8 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Application.php(1078): Illuminate\\Container\\Container->resolve('Jiminny\\\\Http\\\\Co...', Array, true)
#9 /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php(864): Illuminate\\Foundation\\Application->resolve('Jiminny\\\\Http\\\\Co...', Array)
#10 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Application.php(1058): Illuminate\\Container\\Container->make('Jiminny\\\\Http\\\\Co...', Array)
#11 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Route.php(286): Illuminate\\Foundation\\Application->make('Jiminny\\\\Http\\\\Co...')
#12 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Route.php(1133): Illuminate\\Routing\\Route->getController()
#13 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Route.php(1062): Illuminate\\Routing\\Route->controllerMiddleware()
#14 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Router.php(834): Illuminate\\Routing\\Route->gatherMiddleware()
#15 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Router.php(816): Illuminate\\Routing\\Router->gatherRouteMiddleware(Object(Illuminate\\Routing\\Route))
#16 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Router.php(800): Illuminate\\Routing\\Router->runRouteWithinStack(Object(Illuminate\\Routing\\Route), Object(Illuminate\\Http\\Request))
#17 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Router.php(764): Illuminate\\Routing\\Router->runRoute(Object(Illuminate\\Http\\Request), Object(Illuminate\\Routing\\Route))
#18 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Router.php(753): Illuminate\\Routing\\Router->dispatchToRoute(Object(Illuminate\\Http\\Request))
#19 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Http/Kernel.php(200): Illuminate\\Routing\\Router->dispatch(Object(Illuminate\\Http\\Request))
#20 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(180): Illuminate\\Foundation\\Http\\Kernel->Illuminate\\Foundation\\Http\\{closure}(Object(Illuminate\\Http\\Request))
#21 /home/jiminny/vendor/barryvdh/laravel-debugbar/src/Middleware/InjectDebugbar.php(59): Illuminate\\Pipeline\\Pipeline->Illuminate\\Pipeline\\{closure}(Object(Illuminate\\Http\\Request))
#22 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(219): Barryvdh\\Debugbar\\Middleware\\InjectDebugbar->handle(Object(Illuminate\\Http\\Request), Object(Closure))
#23 /home/jiminny/vendor/laravel/framework/src/Illuminate/Http/Middleware/HandleCors.php(74): Illuminate\\Pipeline\\Pipeline->Illuminate\\Pipeline\\{closure}(Object(Illuminate\\Http\\Request))
#24 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(219): Illuminate\\Http\\Middleware\\HandleCors->handle(Object(Illuminate\\Http\\Request), Object(Closure))
#25 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Http/Middleware/TransformsRequest.php(21): Illuminate\\Pipeline\\Pipeline->Illuminate\\Pipeline\\{closure}(Object(Illuminate\\Http\\Request))
#26 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Http/Middleware/TrimStrings.php(51): Illuminate\\Foundation\\Http\\Middleware\\TransformsRequest->handle(Object(Illuminate\\Http\\Request), Object(Closure))
#27 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(219): Illuminate\\Foundation\\Http\\Middleware\\TrimStrings->handle(Object(Illuminate\\Http\\Request), Object(Closure))
#28 /home/jiminny/vendor/bepsvpt/secure-headers/src/SecureHeadersMiddleware.php(18): Illuminate\\Pipeline\\Pipeline->Illuminate\\Pipeline\\{closure}(Object(Illuminate\\Http\\Request))
#29 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(219): Bepsvpt\\SecureHeaders\\SecureHeadersMiddleware->handle(Object(Illuminate\\Http\\Request), Object(Closure))
#30 /home/jiminny/app/Http/Middleware/SentryContext.php(60): Illuminate\\Pipeline\\Pipeline->Illuminate\\Pipeline\\{closure}(Object(Illuminate\\Http\\Request))
#31 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(219): Jiminny\\Http\\Middleware\\SentryContext->handle(Object(Illuminate\\Http\\Request), Object(Closure))
#32 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Http/Middleware/PreventRequestsDuringMaintenance.php(109): Illuminate\\Pipeline\\Pipeline->Illuminate\\Pipeline\\{closure}(Object(Illuminate\\Http\\Request))
#33 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(219): Illuminate\\Foundation\\Http\\Middleware\\PreventRequestsDuringMaintenance->handle(Object(Illuminate\\Http\\Request), Object(Closure))
#34 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Http/Middleware/InvokeDeferredCallbacks.php(22): Illuminate\\Pipeline\\Pipeline->Illuminate\\Pipeline\\{closure}(Object(Illuminate\\Http\\Request))
#35 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(219): Illuminate\\Foundation\\Http\\Middleware\\InvokeDeferredCallbacks->handle(Object(Illuminate\\Http\\Request), Object(Closure))
#36 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(137): Illuminate\\Pipeline\\Pipeline->Illuminate\\Pipeline\\{closure}(Object(Illuminate\\Http\\Request))
#37 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Http/Kernel.php(175): Illuminate\\Pipeline\\Pipeline->then(Object(Closure))
#38 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Http/Kernel.php(144): Illuminate\\Foundation\\Http\\Kernel->sendRequestThroughRouter(Object(Illuminate\\Http\\Request))
#39 /home/jiminny/public/index.php(51): Illuminate\\Foundation\\Http\\Kernel->handle(Object(Illuminate\\Http\\Request))
#40 {main}
[previous exception] [object] (ReflectionException(code: -1): Class \"Jiminny\\Repositories\\AjReportsRepository\" does not exist at /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php:1122)
[stacktrace]
#0 /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php(1122): ReflectionClass->__construct('Jiminny\\\\Reposit...')
#1 /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php(933): Illuminate\\Container\\Container->build('Jiminny\\\\Reposit...')
#2 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Application.php(1078): Illuminate\\Container\\Container->resolve('Jiminny\\\\Reposit...', Array, true)
#3 /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php(864): Illuminate\\Foundation\\Application->resolve('Jiminny\\\\Reposit...', Array)
#4 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Application.php(1058): Illuminate\\Container\\Container->make('Jiminny\\\\Reposit...', Array)
#5 /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php(1336): Illuminate\\Foundation\\Application->make('Jiminny\\\\Reposit...')
#6 /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php(1237): Illuminate\\Container\\Container->resolveClass(Object(ReflectionParameter))
#7 /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php(1162): Illuminate\\Container\\Container->resolveDependencies(Array)
#8 /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php(933): Illuminate\\Container\\Container->build('Jiminny\\\\Http\\\\Co...')
#9 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Application.php(1078): Illuminate\\Container\\Container->resolve('Jiminny\\\\Http\\\\Co...', Array, true)
#10 /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php(864): Illuminate\\Foundation\\Application->resolve('Jiminny\\\\Http\\\\Co...', Array)
#11 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Application.php(1058): Illuminate\\Container\\Container->make('Jiminny\\\\Http\\\\Co...', Array)
#12 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Route.php(286): Illuminate\\Foundation\\Application->make('Jiminny\\\\Http\\\\Co...')
#13 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Route.php(1133): Illuminate\\Routing\\Route->getController()
#14 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Route.php(1062): Illuminate\\Routing\\Route->controllerMiddleware()
#15 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Router.php(834): Illuminate\\Routing\\Route->gatherMiddleware()
#16 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Router.php(816): Illuminate\\Routing\\Router->gatherRouteMiddleware(Object(Illuminate\\Routing\\Route))
#17 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Router.php(800): Illuminate\\Routing\\Router->runRouteWithinStack(Object(Illuminate\\Routing\\Route), Object(Illuminate\\Http\\Request))
#18 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Router.php(764): Illuminate\\Routing\\Router->runRoute(Object(Illuminate\\Http\\Request), Object(Illuminate\\Routing\\Route))
#19 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Router.php(753): Illuminate\\Routing\\Router->dispatchToRoute(Object(Illuminate\\Http\\Request))
#20 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Http/Kernel.php(200): Illuminate\\Routing\\Router->dispatch(Object(Illuminate\\Http\\Request))
#21 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(180): Illuminate\\Foundation\\Http\\Kernel->Illuminate\\Foundation\\Http\\{closure}(Object(Illuminate\\Http\\Request))
#22 /home/jiminny/vendor/barryvdh/laravel-debugbar/src/Middleware/InjectDebugbar.php(59): Illuminate\\Pipeline\\Pipeline->Illuminate\\Pipeline\\{closure}(Object(Illuminate\\Http\\Request))
#23 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(219): Barryvdh\\Debugbar\\Middleware\\InjectDebugbar->handle(Object(Illuminate\\Http\\Request), Object(Closure))
#24 /home/jiminny/vendor/laravel/framework/src/Illuminate/Http/Middleware/HandleCors.php(74): Illuminate\\Pipeline\\Pipeline->Illuminate\\Pipeline\\{closure}(Object(Illuminate\\Http\\Request))
#25 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(219): Illuminate\\Http\\Middleware\\HandleCors->handle(Object(Illuminate\\Http\\Request), Object(Closure))
#26 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Http/Middleware/TransformsRequest.php(21): Illuminate\\Pipeline\\Pipeline->Illuminate\\Pipeline\\{closure}(Object(Illuminate\\Http\\Request))
#27 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Http/Middleware/TrimStrings.php(51): Illuminate\\Foundation\\Http\\Middleware\\TransformsRequest->handle(Object(Illuminate\\Http\\Request), Object(Closure))
#28 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(219): Illuminate\\Foundation\\Http\\Middleware\\TrimStrings->handle(Object(Illuminate\\Http\\Request), Object(Closure))
#29 /home/jiminny/vendor/bepsvpt/secure-headers/src/SecureHeadersMiddleware.php(18): Illuminate\\Pipeline\\Pipeline->Illuminate\\Pipeline\\{closure}(Object(Illuminate\\Http\\Request))
#30 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(219): Bepsvpt\\SecureHeaders\\SecureHeadersMiddleware->handle(Object(Illuminate\\Http\\Request), Object(Closure))
#31 /home/jiminny/app/Http/Middleware/SentryContext.php(60): Illuminate\\Pipeline\\Pipeline->Illuminate\\Pipeline\\{closure}(Object(Illuminate\\Http\\Request))
#32 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(219): Jiminny\\Http\\Middleware\\SentryContext->handle(Object(Illuminate\\Http\\Request), Object(Closure))
#33 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Http/Middleware/PreventRequestsDuringMaintenance.php(109): Illuminate\\Pipeline\\Pipeline->Illuminate\\Pipeline\\{closure}(Object(Illuminate\\Http\\Request))
#34 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(219): Illuminate\\Foundation\\Http\\Middleware\\PreventRequestsDuringMaintenance->handle(Object(Illuminate\\Http\\Request), Object(Closure))
#35 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Http/Middleware/InvokeDeferredCallbacks.php(22): Illuminate\\Pipeline\\Pipeline->Illuminate\\Pipeline\\{closure}(Object(Illuminate\\Http\\Request))
#36 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(219): Illuminate\\Foundation\\Http\\Middleware\\InvokeDeferredCallbacks->handle(Object(Illuminate\\Http\\Request), Object(Closure))
#37 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(137): Illuminate\\Pipeline\\Pipeline->Illuminate\\Pipeline\\{closure}(Object(Illuminate\\Http\\Request))
#38 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Http/Kernel.php(175): Illuminate\\Pipeline\\Pipeline->then(Object(Closure))
#39 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Http/Kernel.php(144): Illuminate\\Foundation\\Http\\Kernel->sendRequestThroughRouter(Object(Illuminate\\Http\\Request))
#40 /home/jiminny/public/index.php(51): Illuminate\\Foundation\\Http\\Kernel->handle(Object(Illuminate\\Http\\Request))
#41 {main}
"} {"correlation_id":"de768781-5c05-418c-baf3-79f77a8c8694","trace_id":"fec74062-3f6d-4cfc-968a-1bb285496db3"}
[2026-04-27 10:28:12] local.INFO: Jiminny\Console\Commands\Command::run Memory usage before starting command {"command":"mailbox:skip-lists:refresh","memoryBeforeCommandInMb":60.0,"memoryPeakBeforeCommandInMb":99.727} {"correlation_id":"907d15ed-8c84-41a1-a473-590eebb55dbc","trace_id":"ec7bd353-516e-491d-89dd-de44136c1672"}
[2026-04-27 10:28:12] local.INFO: Jiminny\Console\Commands\Command::run Memory usage for command {"command":"mailbox:skip-lists:refresh","memoryBeforeCommandInMb":60.0,"memoryAfterCommandInMB":62.0,"memoryPeakBeforeCommandInMb":99.727,"memoryPeakAfterCommandInMB":99.727} {"correlation_id":"907d15ed-8c84-41a1-a473-590eebb55dbc","trace_id":"ec7bd353-516e-491d-89dd-de44136c1672"}
[2026-04-27 10:28:13] local.ERROR: Target class [Jiminny\Repositories\AjReportsRepository] does not exist. {"exception":"[object] (Illuminate\\Contracts\\Container\\BindingResolutionException(code: 0): Target class [Jiminny\\Repositories\\AjReportsRepository] does not exist. at /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php:1124)
[stacktrace]
#0 /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php(933): Illuminate\\Container\\Container->build('Jiminny\\\\Reposit...')
#1 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Application.php(1078): Illuminate\\Container\\Container->resolve('Jiminny\\\\Reposit...', Array, true)
#2 /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php(864): Illuminate\\Foundation\\Application->resolve('Jiminny\\\\Reposi...
|
NULL
|
NULL
|
NULL
|
NULL
|
|
2702
|
112
|
24
|
2026-05-07T11:36:17.252573+00:00
|
/Users/lukas/.screenpipe/data/data/2026-05-07/1778 /Users/lukas/.screenpipe/data/data/2026-05-07/1778153777252_m2.jpg...
|
PhpStorm
|
faVsco.js – laravel.log
|
True
|
NULL
|
monitor_2
|
NULL
|
NULL
|
NULL
|
NULL
|
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
[2026-04-27 10:26:25] local.INFO: Jiminny\Console\Commands\Command::run Memory usage before starting command {"command":"mailbox:sync","memoryBeforeCommandInMb":60.0,"memoryPeakBeforeCommandInMb":99.727} {"correlation_id":"7b1ea2fb-8f36-4897-9970-91b4abba2cc7","trace_id":"265be7c0-bf8f-4037-a0d2-57df3ef1d7e1"}
[2026-04-27 10:26:25] local.INFO: [EmailSchedule] STARTING Inbox Sync {"host":"docker_lamp_1"} {"correlation_id":"7b1ea2fb-8f36-4897-9970-91b4abba2cc7","trace_id":"265be7c0-bf8f-4037-a0d2-57df3ef1d7e1"}
[2026-04-27 10:26:25] local.INFO: [EmailSchedule] FINISHED Inbox Sync {"host":"docker_lamp_1","events":2} {"correlation_id":"7b1ea2fb-8f36-4897-9970-91b4abba2cc7","trace_id":"265be7c0-bf8f-4037-a0d2-57df3ef1d7e1"}
[2026-04-27 10:26:25] local.INFO: Jiminny\Console\Commands\Command::run Memory usage for command {"command":"mailbox:sync","memoryBeforeCommandInMb":60.0,"memoryAfterCommandInMB":62.0,"memoryPeakBeforeCommandInMb":99.727,"memoryPeakAfterCommandInMB":99.727} {"correlation_id":"7b1ea2fb-8f36-4897-9970-91b4abba2cc7","trace_id":"265be7c0-bf8f-4037-a0d2-57df3ef1d7e1"}
[2026-04-27 10:26:26] local.INFO: [automated-reports] Started {"correlation_id":"d3f79034-397b-4936-9305-bdd99b23f2dd","trace_id":"aa0f8702-73eb-4947-a765-2e89244599c3"}
[2026-04-27 10:26:26] local.INFO: [automated-reports] Checking conditions {"isMonday":true,"isWeekend":false,"isFirstDayOfMonth":false,"currentMonth":4,"isQuarterlyMonth":true} {"correlation_id":"d3f79034-397b-4936-9305-bdd99b23f2dd","trace_id":"aa0f8702-73eb-4947-a765-2e89244599c3"}
[2026-04-27 10:26:26] local.INFO: [automated-reports] Processing daily reports {"correlation_id":"d3f79034-397b-4936-9305-bdd99b23f2dd","trace_id":"aa0f8702-73eb-4947-a765-2e89244599c3"}
[2026-04-27 10:26:26] local.INFO: [automated-reports] Found 1 daily reports to process {"correlation_id":"d3f79034-397b-4936-9305-bdd99b23f2dd","trace_id":"aa0f8702-73eb-4947-a765-2e89244599c3"}
[2026-04-27 10:26:26] local.INFO: [automated-reports] Dispatching Generate Report job for report {"reportUuid":"4f6ca2b5-1993-48aa-99ad-b66f19f15d43","teamId":1,"frequency":"weekly","type":"ask_jiminny"} {"correlation_id":"d3f79034-397b-4936-9305-bdd99b23f2dd","trace_id":"aa0f8702-73eb-4947-a765-2e89244599c3"}
[2026-04-27 10:26:26] local.INFO: [automated-reports] Processing weekly reports {"correlation_id":"d3f79034-397b-4936-9305-bdd99b23f2dd","trace_id":"aa0f8702-73eb-4947-a765-2e89244599c3"}
[2026-04-27 10:26:26] local.INFO: [automated-reports] Found 1 weekly reports to process {"correlation_id":"d3f79034-397b-4936-9305-bdd99b23f2dd","trace_id":"aa0f8702-73eb-4947-a765-2e89244599c3"}
[2026-04-27 10:26:26] local.INFO: [automated-reports] Dispatching Generate Report job for report {"reportUuid":"4f6ca2b5-1993-48aa-99ad-b66f19f15d43","teamId":1,"frequency":"weekly","type":"ask_jiminny"} {"correlation_id":"d3f79034-397b-4936-9305-bdd99b23f2dd","trace_id":"aa0f8702-73eb-4947-a765-2e89244599c3"}
[2026-04-27 10:26:26] local.INFO: [automated-reports] Completed {"correlation_id":"d3f79034-397b-4936-9305-bdd99b23f2dd","trace_id":"aa0f8702-73eb-4947-a765-2e89244599c3"}
[2026-04-27 10:26:26] local.INFO: [HubSpot Journal Polling] Getting offset from database {"offset":"","jiminny_team_id":1} {"correlation_id":"b453f0f4-0d2c-491c-a935-14c60265ec18","trace_id":"a50202d6-5601-46b4-a02e-cf5ac8d061fa"}
[2026-04-27 10:26:26] local.INFO: [HubSpot Journal API] Fetching latest journal entry {"url":"[URL_WITH_CREDENTIALS] {"correlation_id":"d3e3eb3d-5c83-4d6e-9592-aaa38c1843a4","trace_id":"265be7c0-bf8f-4037-a0d2-57df3ef1d7e1"}
[2026-04-27 10:26:28] local.INFO: [Gmail] imported 7 emails via full sync workflow for inbox 212 {"correlation_id":"d3e3eb3d-5c83-4d6e-9592-aaa38c1843a4","trace_id":"265be7c0-bf8f-4037-a0d2-57df3ef1d7e1"}
[2026-04-27 10:26:28] local.INFO: [Gmail] seeding inbox 212 with last message time : 2026-04-27 10:24:57 {"correlation_id":"d3e3eb3d-5c83-4d6e-9592-aaa38c1843a4","trace_id":"265be7c0-bf8f-4037-a0d2-57df3ef1d7e1"}
[2026-04-27 10:26:28] local.INFO: [Sync Mailbox] Sync complete {"inbox_id":212} {"correlation_id":"d3e3eb3d-5c83-4d6e-9592-aaa38c1843a4","trace_id":"265be7c0-bf8f-4037-a0d2-57df3ef1d7e1"}
[2026-04-27 10:27:04] local.INFO: Jiminny\Console\Commands\Command::run Memory usage before starting command {"command":"meeting-bot:schedule-bot","memoryBeforeCommandInMb":60.0,"memoryPeakBeforeCommandInMb":99.727} {"correlation_id":"f55fbf77-7e5f-4c87-accd-011697ca0fa3","trace_id":"9bd1c431-26de-4e31-83d8-3129ed9b3b79"}
[2026-04-27 10:27:04] local.INFO: [ScheduleBotCommand] Number of activities to be captured: 0 {"correlation_id":"f55fbf77-7e5f-4c87-accd-011697ca0fa3","trace_id":"9bd1c431-26de-4e31-83d8-3129ed9b3b79"}
[2026-04-27 10:27:04] local.INFO: Jiminny\Console\Commands\Command::run Memory usage for command {"command":"meeting-bot:schedule-bot","memoryBeforeCommandInMb":60.0,"memoryAfterCommandInMB":62.0,"memoryPeakBeforeCommandInMb":99.727,"memoryPeakAfterCommandInMB":99.727} {"correlation_id":"f55fbf77-7e5f-4c87-accd-011697ca0fa3","trace_id":"9bd1c431-26de-4e31-83d8-3129ed9b3b79"}
[2026-04-27 10:27:05] local.INFO: Jiminny\Console\Commands\Command::run Memory usage before starting command {"command":"dialers:monitor-activities","memoryBeforeCommandInMb":60.0,"memoryPeakBeforeCommandInMb":99.727} {"correlation_id":"da241053-5f3b-4d02-9504-4ef2987cdbd7","trace_id":"364d3fc7-0f11-40c6-8697-d18eff2c4cb6"}
[2026-04-27 10:27:05] local.INFO: Jiminny\Console\Commands\Command::run Memory usage for command {"command":"dialers:monitor-activities","memoryBeforeCommandInMb":60.0,"memoryAfterCommandInMB":62.0,"memoryPeakBeforeCommandInMb":99.727,"memoryPeakAfterCommandInMB":99.727} {"correlation_id":"da241053-5f3b-4d02-9504-4ef2987cdbd7","trace_id":"364d3fc7-0f11-40c6-8697-d18eff2c4cb6"}
[2026-04-27 10:27:06] local.NOTICE: Monitoring start {"correlation_id":"ad0d871a-a1fd-4725-b2f7-ac8f0fc1857e","trace_id":"cb504e84-d924-43a0-82d5-faf1840134ba"}
[2026-04-27 10:27:06] local.NOTICE: Monitoring end {"correlation_id":"ad0d871a-a1fd-4725-b2f7-ac8f0fc1857e","trace_id":"cb504e84-d924-43a0-82d5-faf1840134ba"}
[2026-04-27 10:27:08] local.INFO: Jiminny\Console\Commands\Command::run Memory usage before starting command {"command":"mailbox:skip-lists:refresh","memoryBeforeCommandInMb":60.0,"memoryPeakBeforeCommandInMb":99.727} {"correlation_id":"b8652f0d-1cee-44ae-a0ea-3d3137a882c1","trace_id":"7fe19940-0813-4199-a2a8-958de09bd0ee"}
[2026-04-27 10:27:08] local.INFO: Jiminny\Console\Commands\Command::run Memory usage for command {"command":"mailbox:skip-lists:refresh","memoryBeforeCommandInMb":60.0,"memoryAfterCommandInMB":62.0,"memoryPeakBeforeCommandInMb":99.727,"memoryPeakAfterCommandInMB":99.727} {"correlation_id":"b8652f0d-1cee-44ae-a0ea-3d3137a882c1","trace_id":"7fe19940-0813-4199-a2a8-958de09bd0ee"}
[2026-04-27 10:27:09] local.INFO: Jiminny\Console\Commands\Command::run Memory usage before starting command {"command":"mailbox:batch:process","memoryBeforeCommandInMb":60.0,"memoryPeakBeforeCommandInMb":99.727} {"correlation_id":"06190462-0f47-403f-8e95-ad5d05bff1fe","trace_id":"4aa6bed6-05a1-4661-a9c9-edadc61d84ac"}
[2026-04-27 10:27:09] local.INFO: [EmailSchedule] STARTING batch process {"host":"docker_lamp_1"} {"correlation_id":"06190462-0f47-403f-8e95-ad5d05bff1fe","trace_id":"4aa6bed6-05a1-4661-a9c9-edadc61d84ac"}
[2026-04-27 10:27:09] local.INFO: [EmailSchedule] FINISHED batch process {"host":"docker_lamp_1","processed":0} {"correlation_id":"06190462-0f47-403f-8e95-ad5d05bff1fe","trace_id":"4aa6bed6-05a1-4661-a9c9-edadc61d84ac"}
[2026-04-27 10:27:09] local.INFO: Jiminny\Console\Commands\Command::run Memory usage for command {"command":"mailbox:batch:process","memoryBeforeCommandInMb":60.0,"memoryAfterCommandInMB":62.0,"memoryPeakBeforeCommandInMb":99.727,"memoryPeakAfterCommandInMB":99.727} {"correlation_id":"06190462-0f47-403f-8e95-ad5d05bff1fe","trace_id":"4aa6bed6-05a1-4661-a9c9-edadc61d84ac"}
[2026-04-27 10:27:11] local.INFO: Jiminny\Console\Commands\Command::run Memory usage before starting command {"command":"mailbox:batch:create","memoryBeforeCommandInMb":60.0,"memoryPeakBeforeCommandInMb":99.727} {"correlation_id":"b561c5d5-e6e3-4091-9517-b0458190a360","trace_id":"bf9679fb-b785-40cb-b814-04a1dc385a1c"}
[2026-04-27 10:27:11] local.INFO: [EmailSchedule] STARTING batch create {"host":"docker_lamp_1"} {"correlation_id":"b561c5d5-e6e3-4091-9517-b0458190a360","trace_id":"bf9679fb-b785-40cb-b814-04a1dc385a1c"}
[2026-04-27 10:27:11] local.INFO: [EmailSchedule] FINISHED batch create {"host":"docker_lamp_1"} {"correlation_id":"b561c5d5-e6e3-4091-9517-b0458190a360","trace_id":"bf9679fb-b785-40cb-b814-04a1dc385a1c"}
[2026-04-27 10:27:11] local.INFO: Jiminny\Console\Commands\Command::run Memory usage for command {"command":"mailbox:batch:create","memoryBeforeCommandInMb":60.0,"memoryAfterCommandInMB":62.0,"memoryPeakBeforeCommandInMb":99.727,"memoryPeakAfterCommandInMB":99.727} {"correlation_id":"b561c5d5-e6e3-4091-9517-b0458190a360","trace_id":"bf9679fb-b785-40cb-b814-04a1dc385a1c"}
[2026-04-27 10:27:14] local.INFO: [Jiminny\Jobs\Mailbox\CreateBatches] processed 2 inboxes and created 1 batches {"userId":null,"batchSize":30,"maxBatches":1000} {"correlation_id":"98416296-43c2-4834-927e-4a0e86dc99cf","trace_id":"bf9679fb-b785-40cb-b814-04a1dc385a1c"}
[2026-04-27 10:28:04] local.INFO: Jiminny\Console\Commands\Command::run Memory usage before starting command {"command":"meeting-bot:schedule-bot","memoryBeforeCommandInMb":60.0,"memoryPeakBeforeCommandInMb":99.727} {"correlation_id":"5852bcf1-af92-4d24-8a2d-08a9d4a5cfac","trace_id":"25af5e8c-a148-493e-ac9f-4d53f1960177"}
[2026-04-27 10:28:04] local.INFO: [ScheduleBotCommand] Number of activities to be captured: 0 {"correlation_id":"5852bcf1-af92-4d24-8a2d-08a9d4a5cfac","trace_id":"25af5e8c-a148-493e-ac9f-4d53f1960177"}
[2026-04-27 10:28:04] local.INFO: Jiminny\Console\Commands\Command::run Memory usage for command {"command":"meeting-bot:schedule-bot","memoryBeforeCommandInMb":60.0,"memoryAfterCommandInMB":62.0,"memoryPeakBeforeCommandInMb":99.727,"memoryPeakAfterCommandInMB":99.727} {"correlation_id":"5852bcf1-af92-4d24-8a2d-08a9d4a5cfac","trace_id":"25af5e8c-a148-493e-ac9f-4d53f1960177"}
[2026-04-27 10:28:06] local.INFO: Jiminny\Console\Commands\Command::run Memory usage before starting command {"command":"dialers:monitor-activities","memoryBeforeCommandInMb":60.0,"memoryPeakBeforeCommandInMb":99.727} {"correlation_id":"cc1cff1f-71b4-4bb1-800f-66e27c0f2b3a","trace_id":"c03103a3-9ab8-480b-9753-7c7c6488f246"}
[2026-04-27 10:28:06] local.INFO: Jiminny\Console\Commands\Command::run Memory usage for command {"command":"dialers:monitor-activities","memoryBeforeCommandInMb":60.0,"memoryAfterCommandInMB":62.0,"memoryPeakBeforeCommandInMb":99.727,"memoryPeakAfterCommandInMB":99.727} {"correlation_id":"cc1cff1f-71b4-4bb1-800f-66e27c0f2b3a","trace_id":"c03103a3-9ab8-480b-9753-7c7c6488f246"}
[2026-04-27 10:28:09] local.ERROR: Target class [Jiminny\Repositories\AjReportsRepository] does not exist. {"exception":"[object] (Illuminate\\Contracts\\Container\\BindingResolutionException(code: 0): Target class [Jiminny\\Repositories\\AjReportsRepository] does not exist. at /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php:1124)
[stacktrace]
#0 /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php(933): Illuminate\\Container\\Container->build('Jiminny\\\\Reposit...')
#1 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Application.php(1078): Illuminate\\Container\\Container->resolve('Jiminny\\\\Reposit...', Array, true)
#2 /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php(864): Illuminate\\Foundation\\Application->resolve('Jiminny\\\\Reposit...', Array)
#3 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Application.php(1058): Illuminate\\Container\\Container->make('Jiminny\\\\Reposit...', Array)
#4 /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php(1336): Illuminate\\Foundation\\Application->make('Jiminny\\\\Reposit...')
#5 /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php(1237): Illuminate\\Container\\Container->resolveClass(Object(ReflectionParameter))
#6 /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php(1162): Illuminate\\Container\\Container->resolveDependencies(Array)
#7 /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php(933): Illuminate\\Container\\Container->build('Jiminny\\\\Http\\\\Co...')
#8 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Application.php(1078): Illuminate\\Container\\Container->resolve('Jiminny\\\\Http\\\\Co...', Array, true)
#9 /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php(864): Illuminate\\Foundation\\Application->resolve('Jiminny\\\\Http\\\\Co...', Array)
#10 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Application.php(1058): Illuminate\\Container\\Container->make('Jiminny\\\\Http\\\\Co...', Array)
#11 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Route.php(286): Illuminate\\Foundation\\Application->make('Jiminny\\\\Http\\\\Co...')
#12 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Route.php(1133): Illuminate\\Routing\\Route->getController()
#13 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Route.php(1062): Illuminate\\Routing\\Route->controllerMiddleware()
#14 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Router.php(834): Illuminate\\Routing\\Route->gatherMiddleware()
#15 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Router.php(816): Illuminate\\Routing\\Router->gatherRouteMiddleware(Object(Illuminate\\Routing\\Route))
#16 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Router.php(800): Illuminate\\Routing\\Router->runRouteWithinStack(Object(Illuminate\\Routing\\Route), Object(Illuminate\\Http\\Request))
#17 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Router.php(764): Illuminate\\Routing\\Router->runRoute(Object(Illuminate\\Http\\Request), Object(Illuminate\\Routing\\Route))
#18 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Router.php(753): Illuminate\\Routing\\Router->dispatchToRoute(Object(Illuminate\\Http\\Request))
#19 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Http/Kernel.php(200): Illuminate\\Routing\\Router->dispatch(Object(Illuminate\\Http\\Request))
#20 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(180): Illuminate\\Foundation\\Http\\Kernel->Illuminate\\Foundation\\Http\\{closure}(Object(Illuminate\\Http\\Request))
#21 /home/jiminny/vendor/barryvdh/laravel-debugbar/src/Middleware/InjectDebugbar.php(59): Illuminate\\Pipeline\\Pipeline->Illuminate\\Pipeline\\{closure}(Object(Illuminate\\Http\\Request))
#22 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(219): Barryvdh\\Debugbar\\Middleware\\InjectDebugbar->handle(Object(Illuminate\\Http\\Request), Object(Closure))
#23 /home/jiminny/vendor/laravel/framework/src/Illuminate/Http/Middleware/HandleCors.php(74): Illuminate\\Pipeline\\Pipeline->Illuminate\\Pipeline\\{closure}(Object(Illuminate\\Http\\Request))
#24 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(219): Illuminate\\Http\\Middleware\\HandleCors->handle(Object(Illuminate\\Http\\Request), Object(Closure))
#25 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Http/Middleware/TransformsRequest.php(21): Illuminate\\Pipeline\\Pipeline->Illuminate\\Pipeline\\{closure}(Object(Illuminate\\Http\\Request))
#26 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Http/Middleware/TrimStrings.php(51): Illuminate\\Foundation\\Http\\Middleware\\TransformsRequest->handle(Object(Illuminate\\Http\\Request), Object(Closure))
#27 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(219): Illuminate\\Foundation\\Http\\Middleware\\TrimStrings->handle(Object(Illuminate\\Http\\Request), Object(Closure))
#28 /home/jiminny/vendor/bepsvpt/secure-headers/src/SecureHeadersMiddleware.php(18): Illuminate\\Pipeline\\Pipeline->Illuminate\\Pipeline\\{closure}(Object(Illuminate\\Http\\Request))
#29 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(219): Bepsvpt\\SecureHeaders\\SecureHeadersMiddleware->handle(Object(Illuminate\\Http\\Request), Object(Closure))
#30 /home/jiminny/app/Http/Middleware/SentryContext.php(60): Illuminate\\Pipeline\\Pipeline->Illuminate\\Pipeline\\{closure}(Object(Illuminate\\Http\\Request))
#31 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(219): Jiminny\\Http\\Middleware\\SentryContext->handle(Object(Illuminate\\Http\\Request), Object(Closure))
#32 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Http/Middleware/PreventRequestsDuringMaintenance.php(109): Illuminate\\Pipeline\\Pipeline->Illuminate\\Pipeline\\{closure}(Object(Illuminate\\Http\\Request))
#33 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(219): Illuminate\\Foundation\\Http\\Middleware\\PreventRequestsDuringMaintenance->handle(Object(Illuminate\\Http\\Request), Object(Closure))
#34 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Http/Middleware/InvokeDeferredCallbacks.php(22): Illuminate\\Pipeline\\Pipeline->Illuminate\\Pipeline\\{closure}(Object(Illuminate\\Http\\Request))
#35 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(219): Illuminate\\Foundation\\Http\\Middleware\\InvokeDeferredCallbacks->handle(Object(Illuminate\\Http\\Request), Object(Closure))
#36 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(137): Illuminate\\Pipeline\\Pipeline->Illuminate\\Pipeline\\{closure}(Object(Illuminate\\Http\\Request))
#37 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Http/Kernel.php(175): Illuminate\\Pipeline\\Pipeline->then(Object(Closure))
#38 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Http/Kernel.php(144): Illuminate\\Foundation\\Http\\Kernel->sendRequestThroughRouter(Object(Illuminate\\Http\\Request))
#39 /home/jiminny/public/index.php(51): Illuminate\\Foundation\\Http\\Kernel->handle(Object(Illuminate\\Http\\Request))
#40 {main}
[previous exception] [object] (ReflectionException(code: -1): Class \"Jiminny\\Repositories\\AjReportsRepository\" does not exist at /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php:1122)
[stacktrace]
#0 /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php(1122): ReflectionClass->__construct('Jiminny\\\\Reposit...')
#1 /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php(933): Illuminate\\Container\\Container->build('Jiminny\\\\Reposit...')
#2 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Application.php(1078): Illuminate\\Container\\Container->resolve('Jiminny\\\\Reposit...', Array, true)
#3 /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php(864): Illuminate\\Foundation\\Application->resolve('Jiminny\\\\Reposit...', Array)
#4 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Application.php(1058): Illuminate\\Container\\Container->make('Jiminny\\\\Reposit...', Array)
#5 /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php(1336): Illuminate\\Foundation\\Application->make('Jiminny\\\\Reposit...')
#6 /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php(1237): Illuminate\\Container\\Container->resolveClass(Object(ReflectionParameter))
#7 /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php(1162): Illuminate\\Container\\Container->resolveDependencies(Array)
#8 /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php(933): Illuminate\\Container\\Container->build('Jiminny\\\\Http\\\\Co...')
#9 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Application.php(1078): Illuminate\\Container\\Container->resolve('Jiminny\\\\Http\\\\Co...', Array, true)
#10 /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php(864): Illuminate\\Foundation\\Application->resolve('Jiminny\\\\Http\\\\Co...', Array)
#11 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Application.php(1058): Illuminate\\Container\\Container->make('Jiminny\\\\Http\\\\Co...', Array)
#12 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Route.php(286): Illuminate\\Foundation\\Application->make('Jiminny\\\\Http\\\\Co...')
#13 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Route.php(1133): Illuminate\\Routing\\Route->getController()
#14 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Route.php(1062): Illuminate\\Routing\\Route->controllerMiddleware()
#15 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Router.php(834): Illuminate\\Routing\\Route->gatherMiddleware()
#16 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Router.php(816): Illuminate\\Routing\\Router->gatherRouteMiddleware(Object(Illuminate\\Routing\\Route))
#17 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Router.php(800): Illuminate\\Routing\\Router->runRouteWithinStack(Object(Illuminate\\Routing\\Route), Object(Illuminate\\Http\\Request))
#18 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Router.php(764): Illuminate\\Routing\\Router->runRoute(Object(Illuminate\\Http\\Request), Object(Illuminate\\Routing\\Route))
#19 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Router.php(753): Illuminate\\Routing\\Router->dispatchToRoute(Object(Illuminate\\Http\\Request))
#20 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Http/Kernel.php(200): Illuminate\\Routing\\Router->dispatch(Object(Illuminate\\Http\\Request))
#21 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(180): Illuminate\\Foundation\\Http\\Kernel->Illuminate\\Foundation\\Http\\{closure}(Object(Illuminate\\Http\\Request))
#22 /home/jiminny/vendor/barryvdh/laravel-debugbar/src/Middleware/InjectDebugbar.php(59): Illuminate\\Pipeline\\Pipeline->Illuminate\\Pipeline\\{closure}(Object(Illuminate\\Http\\Request))
#23 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(219): Barryvdh\\Debugbar\\Middleware\\InjectDebugbar->handle(Object(Illuminate\\Http\\Request), Object(Closure))
#24 /home/jiminny/vendor/laravel/framework/src/Illuminate/Http/Middleware/HandleCors.php(74): Illuminate\\Pipeline\\Pipeline->Illuminate\\Pipeline\\{closure}(Object(Illuminate\\Http\\Request))
#25 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(219): Illuminate\\Http\\Middleware\\HandleCors->handle(Object(Illuminate\\Http\\Request), Object(Closure))
#26 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Http/Middleware/TransformsRequest.php(21): Illuminate\\Pipeline\\Pipeline->Illuminate\\Pipeline\\{closure}(Object(Illuminate\\Http\\Request))
#27 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Http/Middleware/TrimStrings.php(51): Illuminate\\Foundation\\Http\\Middleware\\TransformsRequest->handle(Object(Illuminate\\Http\\Request), Object(Closure))
#28 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(219): Illuminate\\Foundation\\Http\\Middleware\\TrimStrings->handle(Object(Illuminate\\Http\\Request), Object(Closure))
#29 /home/jiminny/vendor/bepsvpt/secure-headers/src/SecureHeadersMiddleware.php(18): Illuminate\\Pipeline\\Pipeline->Illuminate\\Pipeline\\{closure}(Object(Illuminate\\Http\\Request))
#30 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(219): Bepsvpt\\SecureHeaders\\SecureHeadersMiddleware->handle(Object(Illuminate\\Http\\Request), Object(Closure))
#31 /home/jiminny/app/Http/Middleware/SentryContext.php(60): Illuminate\\Pipeline\\Pipeline->Illuminate\\Pipeline\\{closure}(Object(Illuminate\\Http\\Request))
#32 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(219): Jiminny\\Http\\Middleware\\SentryContext->handle(Object(Illuminate\\Http\\Request), Object(Closure))
#33 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Http/Middleware/PreventRequestsDuringMaintenance.php(109): Illuminate\\Pipeline\\Pipeline->Illuminate\\Pipeline\\{closure}(Object(Illuminate\\Http\\Request))
#34 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(219): Illuminate\\Foundation\\Http\\Middleware\\PreventRequestsDuringMaintenance->handle(Object(Illuminate\\Http\\Request), Object(Closure))
#35 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Http/Middleware/InvokeDeferredCallbacks.php(22): Illuminate\\Pipeline\\Pipeline->Illuminate\\Pipeline\\{closure}(Object(Illuminate\\Http\\Request))
#36 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(219): Illuminate\\Foundation\\Http\\Middleware\\InvokeDeferredCallbacks->handle(Object(Illuminate\\Http\\Request), Object(Closure))
#37 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(137): Illuminate\\Pipeline\\Pipeline->Illuminate\\Pipeline\\{closure}(Object(Illuminate\\Http\\Request))
#38 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Http/Kernel.php(175): Illuminate\\Pipeline\\Pipeline->then(Object(Closure))
#39 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Http/Kernel.php(144): Illuminate\\Foundation\\Http\\Kernel->sendRequestThroughRouter(Object(Illuminate\\Http\\Request))
#40 /home/jiminny/public/index.php(51): Illuminate\\Foundation\\Http\\Kernel->handle(Object(Illuminate\\Http\\Request))
#41 {main}
"} {"correlation_id":"dfad8817-1dde-4622-8320-f5fc1616a8fe","trace_id":"d21232b2-60fb-4ca0-838c-ba42e61593f9"}
[2026-04-27 10:28:09] local.NOTICE: Monitoring start {"correlation_id":"93744c67-4444-41ff-aaad-6aa9cafe294e","trace_id":"80e396b0-b1ac-4ec8-838c-00021f98072c"}
[2026-04-27 10:28:09] local.NOTICE: Monitoring end {"correlation_id":"93744c67-4444-41ff-aaad-6aa9cafe294e","trace_id":"80e396b0-b1ac-4ec8-838c-00021f98072c"}
[2026-04-27 10:28:10] local.ERROR: Target class [Jiminny\Repositories\AjReportsRepository] does not exist. {"exception":"[object] (Illuminate\\Contracts\\Container\\BindingResolutionException(code: 0): Target class [Jiminny\\Repositories\\AjReportsRepository] does not exist. at /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php:1124)
[stacktrace]
#0 /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php(933): Illuminate\\Container\\Container->build('Jiminny\\\\Reposit...')
#1 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Application.php(1078): Illuminate\\Container\\Container->resolve('Jiminny\\\\Reposit...', Array, true)
#2 /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php(864): Illuminate\\Foundation\\Application->resolve('Jiminny\\\\Reposit...', Array)
#3 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Application.php(1058): Illuminate\\Container\\Container->make('Jiminny\\\\Reposit...', Array)
#4 /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php(1336): Illuminate\\Foundation\\Application->make('Jiminny\\\\Reposit...')
#5 /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php(1237): Illuminate\\Container\\Container->resolveClass(Object(ReflectionParameter))
#6 /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php(1162): Illuminate\\Container\\Container->resolveDependencies(Array)
#7 /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php(933): Illuminate\\Container\\Container->build('Jiminny\\\\Http\\\\Co...')
#8 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Application.php(1078): Illuminate\\Container\\Container->resolve('Jiminny\\\\Http\\\\Co...', Array, true)
#9 /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php(864): Illuminate\\Foundation\\Application->resolve('Jiminny\\\\Http\\\\Co...', Array)
#10 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Application.php(1058): Illuminate\\Container\\Container->make('Jiminny\\\\Http\\\\Co...', Array)
#11 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Route.php(286): Illuminate\\Foundation\\Application->make('Jiminny\\\\Http\\\\Co...')
#12 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Route.php(1133): Illuminate\\Routing\\Route->getController()
#13 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Route.php(1062): Illuminate\\Routing\\Route->controllerMiddleware()
#14 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Router.php(834): Illuminate\\Routing\\Route->gatherMiddleware()
#15 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Router.php(816): Illuminate\\Routing\\Router->gatherRouteMiddleware(Object(Illuminate\\Routing\\Route))
#16 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Router.php(800): Illuminate\\Routing\\Router->runRouteWithinStack(Object(Illuminate\\Routing\\Route), Object(Illuminate\\Http\\Request))
#17 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Router.php(764): Illuminate\\Routing\\Router->runRoute(Object(Illuminate\\Http\\Request), Object(Illuminate\\Routing\\Route))
#18 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Router.php(753): Illuminate\\Routing\\Router->dispatchToRoute(Object(Illuminate\\Http\\Request))
#19 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Http/Kernel.php(200): Illuminate\\Routing\\Router->dispatch(Object(Illuminate\\Http\\Request))
#20 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(180): Illuminate\\Foundation\\Http\\Kernel->Illuminate\\Foundation\\Http\\{closure}(Object(Illuminate\\Http\\Request))
#21 /home/jiminny/vendor/barryvdh/laravel-debugbar/src/Middleware/InjectDebugbar.php(59): Illuminate\\Pipeline\\Pipeline->Illuminate\\Pipeline\\{closure}(Object(Illuminate\\Http\\Request))
#22 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(219): Barryvdh\\Debugbar\\Middleware\\InjectDebugbar->handle(Object(Illuminate\\Http\\Request), Object(Closure))
#23 /home/jiminny/vendor/laravel/framework/src/Illuminate/Http/Middleware/HandleCors.php(74): Illuminate\\Pipeline\\Pipeline->Illuminate\\Pipeline\\{closure}(Object(Illuminate\\Http\\Request))
#24 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(219): Illuminate\\Http\\Middleware\\HandleCors->handle(Object(Illuminate\\Http\\Request), Object(Closure))
#25 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Http/Middleware/TransformsRequest.php(21): Illuminate\\Pipeline\\Pipeline->Illuminate\\Pipeline\\{closure}(Object(Illuminate\\Http\\Request))
#26 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Http/Middleware/TrimStrings.php(51): Illuminate\\Foundation\\Http\\Middleware\\TransformsRequest->handle(Object(Illuminate\\Http\\Request), Object(Closure))
#27 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(219): Illuminate\\Foundation\\Http\\Middleware\\TrimStrings->handle(Object(Illuminate\\Http\\Request), Object(Closure))
#28 /home/jiminny/vendor/bepsvpt/secure-headers/src/SecureHeadersMiddleware.php(18): Illuminate\\Pipeline\\Pipeline->Illuminate\\Pipeline\\{closure}(Object(Illuminate\\Http\\Request))
#29 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(219): Bepsvpt\\SecureHeaders\\SecureHeadersMiddleware->handle(Object(Illuminate\\Http\\Request), Object(Closure))
#30 /home/jiminny/app/Http/Middleware/SentryContext.php(60): Illuminate\\Pipeline\\Pipeline->Illuminate\\Pipeline\\{closure}(Object(Illuminate\\Http\\Request))
#31 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(219): Jiminny\\Http\\Middleware\\SentryContext->handle(Object(Illuminate\\Http\\Request), Object(Closure))
#32 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Http/Middleware/PreventRequestsDuringMaintenance.php(109): Illuminate\\Pipeline\\Pipeline->Illuminate\\Pipeline\\{closure}(Object(Illuminate\\Http\\Request))
#33 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(219): Illuminate\\Foundation\\Http\\Middleware\\PreventRequestsDuringMaintenance->handle(Object(Illuminate\\Http\\Request), Object(Closure))
#34 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Http/Middleware/InvokeDeferredCallbacks.php(22): Illuminate\\Pipeline\\Pipeline->Illuminate\\Pipeline\\{closure}(Object(Illuminate\\Http\\Request))
#35 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(219): Illuminate\\Foundation\\Http\\Middleware\\InvokeDeferredCallbacks->handle(Object(Illuminate\\Http\\Request), Object(Closure))
#36 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(137): Illuminate\\Pipeline\\Pipeline->Illuminate\\Pipeline\\{closure}(Object(Illuminate\\Http\\Request))
#37 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Http/Kernel.php(175): Illuminate\\Pipeline\\Pipeline->then(Object(Closure))
#38 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Http/Kernel.php(144): Illuminate\\Foundation\\Http\\Kernel->sendRequestThroughRouter(Object(Illuminate\\Http\\Request))
#39 /home/jiminny/public/index.php(51): Illuminate\\Foundation\\Http\\Kernel->handle(Object(Illuminate\\Http\\Request))
#40 {main}
[previous exception] [object] (ReflectionException(code: -1): Class \"Jiminny\\Repositories\\AjReportsRepository\" does not exist at /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php:1122)
[stacktrace]
#0 /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php(1122): ReflectionClass->__construct('Jiminny\\\\Reposit...')
#1 /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php(933): Illuminate\\Container\\Container->build('Jiminny\\\\Reposit...')
#2 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Application.php(1078): Illuminate\\Container\\Container->resolve('Jiminny\\\\Reposit...', Array, true)
#3 /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php(864): Illuminate\\Foundation\\Application->resolve('Jiminny\\\\Reposit...', Array)
#4 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Application.php(1058): Illuminate\\Container\\Container->make('Jiminny\\\\Reposit...', Array)
#5 /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php(1336): Illuminate\\Foundation\\Application->make('Jiminny\\\\Reposit...')
#6 /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php(1237): Illuminate\\Container\\Container->resolveClass(Object(ReflectionParameter))
#7 /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php(1162): Illuminate\\Container\\Container->resolveDependencies(Array)
#8 /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php(933): Illuminate\\Container\\Container->build('Jiminny\\\\Http\\\\Co...')
#9 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Application.php(1078): Illuminate\\Container\\Container->resolve('Jiminny\\\\Http\\\\Co...', Array, true)
#10 /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php(864): Illuminate\\Foundation\\Application->resolve('Jiminny\\\\Http\\\\Co...', Array)
#11 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Application.php(1058): Illuminate\\Container\\Container->make('Jiminny\\\\Http\\\\Co...', Array)
#12 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Route.php(286): Illuminate\\Foundation\\Application->make('Jiminny\\\\Http\\\\Co...')
#13 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Route.php(1133): Illuminate\\Routing\\Route->getController()
#14 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Route.php(1062): Illuminate\\Routing\\Route->controllerMiddleware()
#15 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Router.php(834): Illuminate\\Routing\\Route->gatherMiddleware()
#16 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Router.php(816): Illuminate\\Routing\\Router->gatherRouteMiddleware(Object(Illuminate\\Routing\\Route))
#17 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Router.php(800): Illuminate\\Routing\\Router->runRouteWithinStack(Object(Illuminate\\Routing\\Route), Object(Illuminate\\Http\\Request))
#18 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Router.php(764): Illuminate\\Routing\\Router->runRoute(Object(Illuminate\\Http\\Request), Object(Illuminate\\Routing\\Route))
#19 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Router.php(753): Illuminate\\Routing\\Router->dispatchToRoute(Object(Illuminate\\Http\\Request))
#20 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Http/Kernel.php(200): Illuminate\\Routing\\Router->dispatch(Object(Illuminate\\Http\\Request))
#21 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(180): Illuminate\\Foundation\\Http\\Kernel->Illuminate\\Foundation\\Http\\{closure}(Object(Illuminate\\Http\\Request))
#22 /home/jiminny/vendor/barryvdh/laravel-debugbar/src/Middleware/InjectDebugbar.php(59): Illuminate\\Pipeline\\Pipeline->Illuminate\\Pipeline\\{closure}(Object(Illuminate\\Http\\Request))
#23 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(219): Barryvdh\\Debugbar\\Middleware\\InjectDebugbar->handle(Object(Illuminate\\Http\\Request), Object(Closure))
#24 /home/jiminny/vendor/laravel/framework/src/Illuminate/Http/Middleware/HandleCors.php(74): Illuminate\\Pipeline\\Pipeline->Illuminate\\Pipeline\\{closure}(Object(Illuminate\\Http\\Request))
#25 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(219): Illuminate\\Http\\Middleware\\HandleCors->handle(Object(Illuminate\\Http\\Request), Object(Closure))
#26 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Http/Middleware/TransformsRequest.php(21): Illuminate\\Pipeline\\Pipeline->Illuminate\\Pipeline\\{closure}(Object(Illuminate\\Http\\Request))
#27 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Http/Middleware/TrimStrings.php(51): Illuminate\\Foundation\\Http\\Middleware\\TransformsRequest->handle(Object(Illuminate\\Http\\Request), Object(Closure))
#28 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(219): Illuminate\\Foundation\\Http\\Middleware\\TrimStrings->handle(Object(Illuminate\\Http\\Request), Object(Closure))
#29 /home/jiminny/vendor/bepsvpt/secure-headers/src/SecureHeadersMiddleware.php(18): Illuminate\\Pipeline\\Pipeline->Illuminate\\Pipeline\\{closure}(Object(Illuminate\\Http\\Request))
#30 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(219): Bepsvpt\\SecureHeaders\\SecureHeadersMiddleware->handle(Object(Illuminate\\Http\\Request), Object(Closure))
#31 /home/jiminny/app/Http/Middleware/SentryContext.php(60): Illuminate\\Pipeline\\Pipeline->Illuminate\\Pipeline\\{closure}(Object(Illuminate\\Http\\Request))
#32 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(219): Jiminny\\Http\\Middleware\\SentryContext->handle(Object(Illuminate\\Http\\Request), Object(Closure))
#33 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Http/Middleware/PreventRequestsDuringMaintenance.php(109): Illuminate\\Pipeline\\Pipeline->Illuminate\\Pipeline\\{closure}(Object(Illuminate\\Http\\Request))
#34 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(219): Illuminate\\Foundation\\Http\\Middleware\\PreventRequestsDuringMaintenance->handle(Object(Illuminate\\Http\\Request), Object(Closure))
#35 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Http/Middleware/InvokeDeferredCallbacks.php(22): Illuminate\\Pipeline\\Pipeline->Illuminate\\Pipeline\\{closure}(Object(Illuminate\\Http\\Request))
#36 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(219): Illuminate\\Foundation\\Http\\Middleware\\InvokeDeferredCallbacks->handle(Object(Illuminate\\Http\\Request), Object(Closure))
#37 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(137): Illuminate\\Pipeline\\Pipeline->Illuminate\\Pipeline\\{closure}(Object(Illuminate\\Http\\Request))
#38 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Http/Kernel.php(175): Illuminate\\Pipeline\\Pipeline->then(Object(Closure))
#39 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Http/Kernel.php(144): Illuminate\\Foundation\\Http\\Kernel->sendRequestThroughRouter(Object(Illuminate\\Http\\Request))
#40 /home/jiminny/public/index.php(51): Illuminate\\Foundation\\Http\\Kernel->handle(Object(Illuminate\\Http\\Request))
#41 {main}
"} {"correlation_id":"de768781-5c05-418c-baf3-79f77a8c8694","trace_id":"fec74062-3f6d-4cfc-968a-1bb285496db3"}
[2026-04-27 10:28:12] local.INFO: Jiminny\Console\Commands\Command::run Memory usage before starting command {"command":"mailbox:skip-lists:refresh","memoryBeforeCommandInMb":60.0,"memoryPeakBeforeCommandInMb":99.727} {"correlation_id":"907d15ed-8c84-41a1-a473-590eebb55dbc","trace_id":"ec7bd353-516e-491d-89dd-de44136c1672"}
[2026-04-27 10:28:12] local.INFO: Jiminny\Console\Commands\Command::run Memory usage for command {"command":"mailbox:skip-lists:refresh","memoryBeforeCommandInMb":60.0,"memoryAfterCommandInMB":62.0,"memoryPeakBeforeCommandInMb":99.727,"memoryPeakAfterCommandInMB":99.727} {"correlation_id":"907d15ed-8c84-41a1-a473-590eebb55dbc","trace_id":"ec7bd353-516e-491d-89dd-de44136c1672"}
[2026-04-27 10:28:13] local.ERROR: Target class [Jiminny\Repositories\AjReportsRepository] does not exist. {"exception":"[object] (Illuminate\\Contracts\\Container\\BindingResolutionException(code: 0): Target class [Jiminny\\Repositories\\AjReportsRepository] does not exist. at /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php:1124)
[stacktrace]
#0 /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php(933): Illuminate\\Container\\Container->build('Jiminny\\\\Reposit...')
#1 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Application.php(1078): Illuminate\\Container\\Container->resolve('Jiminny\\\\Reposit...', Array, true)
#2 /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php(864): Illuminate\\Foundation\\Application->resolve('Jiminny\\\\Reposit...', Array)
#3 /home/jiminny/vendor/laravel/framewo...
|
[{"role":"AXButton","text" [{"role":"AXButton","text":"Project: faVsco.js, menu","depth":5,"bounds":{"left":0.025930852,"top":0.019952115,"width":0.03856383,"height":0.025538707},"on_screen":true,"help_text":"~/jiminny/app","role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"master, menu","depth":5,"bounds":{"left":0.064494684,"top":0.019952115,"width":0.034242023,"height":0.025538707},"on_screen":true,"help_text":"Git Branch: master","role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Start Listening for PHP Debug Connections","depth":5,"bounds":{"left":0.8081782,"top":0.019952115,"width":0.011303191,"height":0.025538707},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"AskJiminnyReportActivityServiceTest","depth":6,"bounds":{"left":0.8234708,"top":0.019952115,"width":0.09208777,"height":0.025538707},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Run 'AskJiminnyReportActivityServiceTest'","depth":6,"bounds":{"left":0.9155585,"top":0.019952115,"width":0.011303191,"height":0.025538707},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Debug 'AskJiminnyReportActivityServiceTest'","depth":6,"bounds":{"left":0.9268617,"top":0.019952115,"width":0.011303191,"height":0.025538707},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"More Actions","depth":6,"bounds":{"left":0.9381649,"top":0.019952115,"width":0.011303191,"height":0.025538707},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"JetBrains AI","depth":5,"bounds":{"left":0.96609044,"top":0.019952115,"width":0.011303191,"height":0.025538707},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Search Everywhere","depth":5,"bounds":{"left":0.9773936,"top":0.019952115,"width":0.011303191,"height":0.025538707},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"IDE and Project Settings","depth":5,"bounds":{"left":0.9886968,"top":0.019952115,"width":0.011303186,"height":0.025538707},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXTextArea","text":"[2026-04-27 10:26:25] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage before starting command {\"command\":\"mailbox:sync\",\"memoryBeforeCommandInMb\":60.0,\"memoryPeakBeforeCommandInMb\":99.727} {\"correlation_id\":\"7b1ea2fb-8f36-4897-9970-91b4abba2cc7\",\"trace_id\":\"265be7c0-bf8f-4037-a0d2-57df3ef1d7e1\"}\n[2026-04-27 10:26:25] local.INFO: [EmailSchedule] STARTING Inbox Sync {\"host\":\"docker_lamp_1\"} {\"correlation_id\":\"7b1ea2fb-8f36-4897-9970-91b4abba2cc7\",\"trace_id\":\"265be7c0-bf8f-4037-a0d2-57df3ef1d7e1\"}\n[2026-04-27 10:26:25] local.INFO: [EmailSchedule] FINISHED Inbox Sync {\"host\":\"docker_lamp_1\",\"events\":2} {\"correlation_id\":\"7b1ea2fb-8f36-4897-9970-91b4abba2cc7\",\"trace_id\":\"265be7c0-bf8f-4037-a0d2-57df3ef1d7e1\"}\n[2026-04-27 10:26:25] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage for command {\"command\":\"mailbox:sync\",\"memoryBeforeCommandInMb\":60.0,\"memoryAfterCommandInMB\":62.0,\"memoryPeakBeforeCommandInMb\":99.727,\"memoryPeakAfterCommandInMB\":99.727} {\"correlation_id\":\"7b1ea2fb-8f36-4897-9970-91b4abba2cc7\",\"trace_id\":\"265be7c0-bf8f-4037-a0d2-57df3ef1d7e1\"}\n[2026-04-27 10:26:26] local.INFO: [automated-reports] Started {\"correlation_id\":\"d3f79034-397b-4936-9305-bdd99b23f2dd\",\"trace_id\":\"aa0f8702-73eb-4947-a765-2e89244599c3\"}\n[2026-04-27 10:26:26] local.INFO: [automated-reports] Checking conditions {\"isMonday\":true,\"isWeekend\":false,\"isFirstDayOfMonth\":false,\"currentMonth\":4,\"isQuarterlyMonth\":true} {\"correlation_id\":\"d3f79034-397b-4936-9305-bdd99b23f2dd\",\"trace_id\":\"aa0f8702-73eb-4947-a765-2e89244599c3\"}\n[2026-04-27 10:26:26] local.INFO: [automated-reports] Processing daily reports {\"correlation_id\":\"d3f79034-397b-4936-9305-bdd99b23f2dd\",\"trace_id\":\"aa0f8702-73eb-4947-a765-2e89244599c3\"}\n[2026-04-27 10:26:26] local.INFO: [automated-reports] Found 1 daily reports to process {\"correlation_id\":\"d3f79034-397b-4936-9305-bdd99b23f2dd\",\"trace_id\":\"aa0f8702-73eb-4947-a765-2e89244599c3\"}\n[2026-04-27 10:26:26] local.INFO: [automated-reports] Dispatching Generate Report job for report {\"reportUuid\":\"4f6ca2b5-1993-48aa-99ad-b66f19f15d43\",\"teamId\":1,\"frequency\":\"weekly\",\"type\":\"ask_jiminny\"} {\"correlation_id\":\"d3f79034-397b-4936-9305-bdd99b23f2dd\",\"trace_id\":\"aa0f8702-73eb-4947-a765-2e89244599c3\"}\n[2026-04-27 10:26:26] local.INFO: [automated-reports] Processing weekly reports {\"correlation_id\":\"d3f79034-397b-4936-9305-bdd99b23f2dd\",\"trace_id\":\"aa0f8702-73eb-4947-a765-2e89244599c3\"}\n[2026-04-27 10:26:26] local.INFO: [automated-reports] Found 1 weekly reports to process {\"correlation_id\":\"d3f79034-397b-4936-9305-bdd99b23f2dd\",\"trace_id\":\"aa0f8702-73eb-4947-a765-2e89244599c3\"}\n[2026-04-27 10:26:26] local.INFO: [automated-reports] Dispatching Generate Report job for report {\"reportUuid\":\"4f6ca2b5-1993-48aa-99ad-b66f19f15d43\",\"teamId\":1,\"frequency\":\"weekly\",\"type\":\"ask_jiminny\"} {\"correlation_id\":\"d3f79034-397b-4936-9305-bdd99b23f2dd\",\"trace_id\":\"aa0f8702-73eb-4947-a765-2e89244599c3\"}\n[2026-04-27 10:26:26] local.INFO: [automated-reports] Completed {\"correlation_id\":\"d3f79034-397b-4936-9305-bdd99b23f2dd\",\"trace_id\":\"aa0f8702-73eb-4947-a765-2e89244599c3\"}\n[2026-04-27 10:26:26] local.INFO: [HubSpot Journal Polling] Getting offset from database {\"offset\":\"\",\"jiminny_team_id\":1} {\"correlation_id\":\"b453f0f4-0d2c-491c-a935-14c60265ec18\",\"trace_id\":\"a50202d6-5601-46b4-a02e-cf5ac8d061fa\"}\n[2026-04-27 10:26:26] local.INFO: [HubSpot Journal API] Fetching latest journal entry {\"url\":\"https://api.hubapi.com/webhooks/v4/journal/latest\"} {\"correlation_id\":\"b453f0f4-0d2c-491c-a935-14c60265ec18\",\"trace_id\":\"a50202d6-5601-46b4-a02e-cf5ac8d061fa\"}\n[2026-04-27 10:26:26] local.INFO: [AskJiminnyReport:Generate] Started {\"automatedReportUuid\":\"4f6ca2b5-1993-48aa-99ad-b66f19f15d43\"} {\"correlation_id\":\"c6277ddd-09a6-4b04-8d42-bd395fc07d0a\",\"trace_id\":\"aa0f8702-73eb-4947-a765-2e89244599c3\"}\n[2026-04-27 10:26:26] local.INFO: [HubSpot Journal Polling] No data {\"correlation_id\":\"b453f0f4-0d2c-491c-a935-14c60265ec18\",\"trace_id\":\"a50202d6-5601-46b4-a02e-cf5ac8d061fa\"}\n[2026-04-27 10:26:26] local.WARNING: [HubSpot Journal Polling] Maximum empty results reached, stopping {\"empty_results\":5,\"max_empty_results\":5} {\"correlation_id\":\"b453f0f4-0d2c-491c-a935-14c60265ec18\",\"trace_id\":\"a50202d6-5601-46b4-a02e-cf5ac8d061fa\"}\n[2026-04-27 10:26:26] local.WARNING: [HubSpot Journal Polling] Maximum empty results reached, stopping {\"empty_results\":5,\"max_empty_results\":5} {\"correlation_id\":\"b453f0f4-0d2c-491c-a935-14c60265ec18\",\"trace_id\":\"a50202d6-5601-46b4-a02e-cf5ac8d061fa\"}\n[2026-04-27 10:26:26] local.INFO: [HubSpot Journal Polling] Service ending {\"runtime_seconds\":56,\"total_cycles\":5,\"files_downloaded\":0,\"empty_files\":0,\"other_portal_skipped\":0,\"total_events\":0,\"events_per_file\":0,\"avg_api_ms\":273.7,\"avg_download_ms\":0.0,\"avg_transform_ms\":0.0,\"avg_process_ms\":0.0,\"peak_memory_mb\":99.73} {\"correlation_id\":\"b453f0f4-0d2c-491c-a935-14c60265ec18\",\"trace_id\":\"a50202d6-5601-46b4-a02e-cf5ac8d061fa\"}\n[2026-04-27 10:26:26] local.INFO: [HubSpot Journal Polling] Released polling lock {\"correlation_id\":\"b453f0f4-0d2c-491c-a935-14c60265ec18\",\"trace_id\":\"a50202d6-5601-46b4-a02e-cf5ac8d061fa\"}\n[2026-04-27 10:26:27] local.INFO: [AskJiminnyReport] Fetched activity IDs for saved search {\"saved_search_id\":1977,\"user_id\":143,\"activity_count\":0} {\"correlation_id\":\"c6277ddd-09a6-4b04-8d42-bd395fc07d0a\",\"trace_id\":\"aa0f8702-73eb-4947-a765-2e89244599c3\"}\n[2026-04-27 10:26:27] local.INFO: [AskJiminnyReport:Generate] Fetched activity IDs {\"automatedReportUuid\":\"4f6ca2b5-1993-48aa-99ad-b66f19f15d43\",\"activityCount\":0} {\"correlation_id\":\"c6277ddd-09a6-4b04-8d42-bd395fc07d0a\",\"trace_id\":\"aa0f8702-73eb-4947-a765-2e89244599c3\"}\n[2026-04-27 10:26:27] local.INFO: [AskJiminnyReport:Generate] Not enough activities, skipped {\"automatedReportUuid\":\"4f6ca2b5-1993-48aa-99ad-b66f19f15d43\",\"activityCount\":0} {\"correlation_id\":\"c6277ddd-09a6-4b04-8d42-bd395fc07d0a\",\"trace_id\":\"aa0f8702-73eb-4947-a765-2e89244599c3\"}\n[2026-04-27 10:26:27] local.INFO: [AskJiminnyReport:Generate] Dispatched not-generated notifications {\"automatedReportUuid\":\"4f6ca2b5-1993-48aa-99ad-b66f19f15d43\",\"recipientsCount\":1} {\"correlation_id\":\"c6277ddd-09a6-4b04-8d42-bd395fc07d0a\",\"trace_id\":\"aa0f8702-73eb-4947-a765-2e89244599c3\"}\n[2026-04-27 10:26:27] local.INFO: [AskJiminnyReport:Generate] Started {\"automatedReportUuid\":\"4f6ca2b5-1993-48aa-99ad-b66f19f15d43\"} {\"correlation_id\":\"f1d3e4df-9591-487f-ac2a-a43ddb89a466\",\"trace_id\":\"aa0f8702-73eb-4947-a765-2e89244599c3\"}\n[2026-04-27 10:26:27] local.INFO: [AskJiminnyReport] Fetched activity IDs for saved search {\"saved_search_id\":1977,\"user_id\":143,\"activity_count\":0} {\"correlation_id\":\"f1d3e4df-9591-487f-ac2a-a43ddb89a466\",\"trace_id\":\"aa0f8702-73eb-4947-a765-2e89244599c3\"}\n[2026-04-27 10:26:27] local.INFO: [AskJiminnyReport:Generate] Fetched activity IDs {\"automatedReportUuid\":\"4f6ca2b5-1993-48aa-99ad-b66f19f15d43\",\"activityCount\":0} {\"correlation_id\":\"f1d3e4df-9591-487f-ac2a-a43ddb89a466\",\"trace_id\":\"aa0f8702-73eb-4947-a765-2e89244599c3\"}\n[2026-04-27 10:26:27] local.INFO: [AskJiminnyReport:Generate] Not enough activities, skipped {\"automatedReportUuid\":\"4f6ca2b5-1993-48aa-99ad-b66f19f15d43\",\"activityCount\":0} {\"correlation_id\":\"f1d3e4df-9591-487f-ac2a-a43ddb89a466\",\"trace_id\":\"aa0f8702-73eb-4947-a765-2e89244599c3\"}\n[2026-04-27 10:26:27] local.INFO: [AskJiminnyReport:Generate] Dispatched not-generated notifications {\"automatedReportUuid\":\"4f6ca2b5-1993-48aa-99ad-b66f19f15d43\",\"recipientsCount\":1} {\"correlation_id\":\"f1d3e4df-9591-487f-ac2a-a43ddb89a466\",\"trace_id\":\"aa0f8702-73eb-4947-a765-2e89244599c3\"}\n[2026-04-27 10:26:27] local.INFO: [Send Report Not Generated Mail] Email sent {\"uuid\":\"cd5b5081-22f8-4d1a-9d6f-f82aca1121f6\",\"email\":\"lukas.kovalik@jiminny.com\"} {\"correlation_id\":\"4255983f-07a4-4872-9ea6-5491fa32bdd5\",\"trace_id\":\"aa0f8702-73eb-4947-a765-2e89244599c3\"}\n[2026-04-27 10:26:28] local.INFO: [Sync Mailbox] Sync start {\"inbox_id\":59} {\"correlation_id\":\"280d5bf6-c34c-45ea-bea7-05d9c028e9d3\",\"trace_id\":\"265be7c0-bf8f-4037-a0d2-57df3ef1d7e1\"}\n[2026-04-27 10:26:28] local.INFO: [Inbox service] Skipping METADATA SYNC for inbox 59 due to unauthorized access to the mailbox {\"correlation_id\":\"280d5bf6-c34c-45ea-bea7-05d9c028e9d3\",\"trace_id\":\"265be7c0-bf8f-4037-a0d2-57df3ef1d7e1\"}\n[2026-04-27 10:26:28] local.INFO: [Sync Mailbox] Sync complete {\"inbox_id\":59} {\"correlation_id\":\"280d5bf6-c34c-45ea-bea7-05d9c028e9d3\",\"trace_id\":\"265be7c0-bf8f-4037-a0d2-57df3ef1d7e1\"}\n[2026-04-27 10:26:28] local.INFO: [Sync Mailbox] Sync start {\"inbox_id\":212} {\"correlation_id\":\"d3e3eb3d-5c83-4d6e-9592-aaa38c1843a4\",\"trace_id\":\"265be7c0-bf8f-4037-a0d2-57df3ef1d7e1\"}\n[2026-04-27 10:26:28] local.INFO: [SocialAccountService] Fetching token {\"socialAccountId\":1500,\"provider\":\"salesforce\"} {\"correlation_id\":\"d3e3eb3d-5c83-4d6e-9592-aaa38c1843a4\",\"trace_id\":\"265be7c0-bf8f-4037-a0d2-57df3ef1d7e1\"}\n[2026-04-27 10:26:28] local.INFO: [SocialAccountService] Token retrieved {\"socialAccountId\":1500,\"provider\":\"salesforce\"} {\"correlation_id\":\"d3e3eb3d-5c83-4d6e-9592-aaa38c1843a4\",\"trace_id\":\"265be7c0-bf8f-4037-a0d2-57df3ef1d7e1\"}\n[2026-04-27 10:26:28] local.INFO: [EncryptedTokenManager] Generating access token. {\"mode\":\"legacy\"} {\"correlation_id\":\"d3e3eb3d-5c83-4d6e-9592-aaa38c1843a4\",\"trace_id\":\"265be7c0-bf8f-4037-a0d2-57df3ef1d7e1\"}\n[2026-04-27 10:26:28] local.INFO: [CrmOwnerResolver] Integration owner matched as CRM Owner {\"crm_provider\":\"salesforce\",\"crm_owner\":143,\"team_id\":1} {\"correlation_id\":\"d3e3eb3d-5c83-4d6e-9592-aaa38c1843a4\",\"trace_id\":\"265be7c0-bf8f-4037-a0d2-57df3ef1d7e1\"}\n[2026-04-27 10:26:28] local.INFO: [SocialAccountService] Fetching token {\"socialAccountId\":1354,\"provider\":\"google\"} {\"correlation_id\":\"d3e3eb3d-5c83-4d6e-9592-aaa38c1843a4\",\"trace_id\":\"265be7c0-bf8f-4037-a0d2-57df3ef1d7e1\"}\n[2026-04-27 10:26:28] local.INFO: [SocialAccountService] Token retrieved {\"socialAccountId\":1354,\"provider\":\"google\"} {\"correlation_id\":\"d3e3eb3d-5c83-4d6e-9592-aaa38c1843a4\",\"trace_id\":\"265be7c0-bf8f-4037-a0d2-57df3ef1d7e1\"}\n[2026-04-27 10:26:28] local.INFO: [EncryptedTokenManager] Generating access token. {\"mode\":\"legacy\"} {\"correlation_id\":\"d3e3eb3d-5c83-4d6e-9592-aaa38c1843a4\",\"trace_id\":\"265be7c0-bf8f-4037-a0d2-57df3ef1d7e1\"}\n[2026-04-27 10:26:28] local.INFO: [Gmail] Performing incremental sync for inbox 212 using history ID: @1777284288 {\"correlation_id\":\"d3e3eb3d-5c83-4d6e-9592-aaa38c1843a4\",\"trace_id\":\"265be7c0-bf8f-4037-a0d2-57df3ef1d7e1\"}\n[2026-04-27 10:26:28] local.INFO: [Gmail] imported 7 emails via full sync workflow for inbox 212 {\"correlation_id\":\"d3e3eb3d-5c83-4d6e-9592-aaa38c1843a4\",\"trace_id\":\"265be7c0-bf8f-4037-a0d2-57df3ef1d7e1\"}\n[2026-04-27 10:26:28] local.INFO: [Gmail] seeding inbox 212 with last message time : 2026-04-27 10:24:57 {\"correlation_id\":\"d3e3eb3d-5c83-4d6e-9592-aaa38c1843a4\",\"trace_id\":\"265be7c0-bf8f-4037-a0d2-57df3ef1d7e1\"}\n[2026-04-27 10:26:28] local.INFO: [Sync Mailbox] Sync complete {\"inbox_id\":212} {\"correlation_id\":\"d3e3eb3d-5c83-4d6e-9592-aaa38c1843a4\",\"trace_id\":\"265be7c0-bf8f-4037-a0d2-57df3ef1d7e1\"}\n[2026-04-27 10:27:04] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage before starting command {\"command\":\"meeting-bot:schedule-bot\",\"memoryBeforeCommandInMb\":60.0,\"memoryPeakBeforeCommandInMb\":99.727} {\"correlation_id\":\"f55fbf77-7e5f-4c87-accd-011697ca0fa3\",\"trace_id\":\"9bd1c431-26de-4e31-83d8-3129ed9b3b79\"}\n[2026-04-27 10:27:04] local.INFO: [ScheduleBotCommand] Number of activities to be captured: 0 {\"correlation_id\":\"f55fbf77-7e5f-4c87-accd-011697ca0fa3\",\"trace_id\":\"9bd1c431-26de-4e31-83d8-3129ed9b3b79\"}\n[2026-04-27 10:27:04] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage for command {\"command\":\"meeting-bot:schedule-bot\",\"memoryBeforeCommandInMb\":60.0,\"memoryAfterCommandInMB\":62.0,\"memoryPeakBeforeCommandInMb\":99.727,\"memoryPeakAfterCommandInMB\":99.727} {\"correlation_id\":\"f55fbf77-7e5f-4c87-accd-011697ca0fa3\",\"trace_id\":\"9bd1c431-26de-4e31-83d8-3129ed9b3b79\"}\n[2026-04-27 10:27:05] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage before starting command {\"command\":\"dialers:monitor-activities\",\"memoryBeforeCommandInMb\":60.0,\"memoryPeakBeforeCommandInMb\":99.727} {\"correlation_id\":\"da241053-5f3b-4d02-9504-4ef2987cdbd7\",\"trace_id\":\"364d3fc7-0f11-40c6-8697-d18eff2c4cb6\"}\n[2026-04-27 10:27:05] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage for command {\"command\":\"dialers:monitor-activities\",\"memoryBeforeCommandInMb\":60.0,\"memoryAfterCommandInMB\":62.0,\"memoryPeakBeforeCommandInMb\":99.727,\"memoryPeakAfterCommandInMB\":99.727} {\"correlation_id\":\"da241053-5f3b-4d02-9504-4ef2987cdbd7\",\"trace_id\":\"364d3fc7-0f11-40c6-8697-d18eff2c4cb6\"}\n[2026-04-27 10:27:06] local.NOTICE: Monitoring start {\"correlation_id\":\"ad0d871a-a1fd-4725-b2f7-ac8f0fc1857e\",\"trace_id\":\"cb504e84-d924-43a0-82d5-faf1840134ba\"}\n[2026-04-27 10:27:06] local.NOTICE: Monitoring end {\"correlation_id\":\"ad0d871a-a1fd-4725-b2f7-ac8f0fc1857e\",\"trace_id\":\"cb504e84-d924-43a0-82d5-faf1840134ba\"}\n[2026-04-27 10:27:08] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage before starting command {\"command\":\"mailbox:skip-lists:refresh\",\"memoryBeforeCommandInMb\":60.0,\"memoryPeakBeforeCommandInMb\":99.727} {\"correlation_id\":\"b8652f0d-1cee-44ae-a0ea-3d3137a882c1\",\"trace_id\":\"7fe19940-0813-4199-a2a8-958de09bd0ee\"}\n[2026-04-27 10:27:08] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage for command {\"command\":\"mailbox:skip-lists:refresh\",\"memoryBeforeCommandInMb\":60.0,\"memoryAfterCommandInMB\":62.0,\"memoryPeakBeforeCommandInMb\":99.727,\"memoryPeakAfterCommandInMB\":99.727} {\"correlation_id\":\"b8652f0d-1cee-44ae-a0ea-3d3137a882c1\",\"trace_id\":\"7fe19940-0813-4199-a2a8-958de09bd0ee\"}\n[2026-04-27 10:27:09] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage before starting command {\"command\":\"mailbox:batch:process\",\"memoryBeforeCommandInMb\":60.0,\"memoryPeakBeforeCommandInMb\":99.727} {\"correlation_id\":\"06190462-0f47-403f-8e95-ad5d05bff1fe\",\"trace_id\":\"4aa6bed6-05a1-4661-a9c9-edadc61d84ac\"}\n[2026-04-27 10:27:09] local.INFO: [EmailSchedule] STARTING batch process {\"host\":\"docker_lamp_1\"} {\"correlation_id\":\"06190462-0f47-403f-8e95-ad5d05bff1fe\",\"trace_id\":\"4aa6bed6-05a1-4661-a9c9-edadc61d84ac\"}\n[2026-04-27 10:27:09] local.INFO: [EmailSchedule] FINISHED batch process {\"host\":\"docker_lamp_1\",\"processed\":0} {\"correlation_id\":\"06190462-0f47-403f-8e95-ad5d05bff1fe\",\"trace_id\":\"4aa6bed6-05a1-4661-a9c9-edadc61d84ac\"}\n[2026-04-27 10:27:09] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage for command {\"command\":\"mailbox:batch:process\",\"memoryBeforeCommandInMb\":60.0,\"memoryAfterCommandInMB\":62.0,\"memoryPeakBeforeCommandInMb\":99.727,\"memoryPeakAfterCommandInMB\":99.727} {\"correlation_id\":\"06190462-0f47-403f-8e95-ad5d05bff1fe\",\"trace_id\":\"4aa6bed6-05a1-4661-a9c9-edadc61d84ac\"}\n[2026-04-27 10:27:11] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage before starting command {\"command\":\"mailbox:batch:create\",\"memoryBeforeCommandInMb\":60.0,\"memoryPeakBeforeCommandInMb\":99.727} {\"correlation_id\":\"b561c5d5-e6e3-4091-9517-b0458190a360\",\"trace_id\":\"bf9679fb-b785-40cb-b814-04a1dc385a1c\"}\n[2026-04-27 10:27:11] local.INFO: [EmailSchedule] STARTING batch create {\"host\":\"docker_lamp_1\"} {\"correlation_id\":\"b561c5d5-e6e3-4091-9517-b0458190a360\",\"trace_id\":\"bf9679fb-b785-40cb-b814-04a1dc385a1c\"}\n[2026-04-27 10:27:11] local.INFO: [EmailSchedule] FINISHED batch create {\"host\":\"docker_lamp_1\"} {\"correlation_id\":\"b561c5d5-e6e3-4091-9517-b0458190a360\",\"trace_id\":\"bf9679fb-b785-40cb-b814-04a1dc385a1c\"}\n[2026-04-27 10:27:11] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage for command {\"command\":\"mailbox:batch:create\",\"memoryBeforeCommandInMb\":60.0,\"memoryAfterCommandInMB\":62.0,\"memoryPeakBeforeCommandInMb\":99.727,\"memoryPeakAfterCommandInMB\":99.727} {\"correlation_id\":\"b561c5d5-e6e3-4091-9517-b0458190a360\",\"trace_id\":\"bf9679fb-b785-40cb-b814-04a1dc385a1c\"}\n[2026-04-27 10:27:14] local.INFO: [Jiminny\\Jobs\\Mailbox\\CreateBatches] processed 2 inboxes and created 1 batches {\"userId\":null,\"batchSize\":30,\"maxBatches\":1000} {\"correlation_id\":\"98416296-43c2-4834-927e-4a0e86dc99cf\",\"trace_id\":\"bf9679fb-b785-40cb-b814-04a1dc385a1c\"}\n[2026-04-27 10:28:04] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage before starting command {\"command\":\"meeting-bot:schedule-bot\",\"memoryBeforeCommandInMb\":60.0,\"memoryPeakBeforeCommandInMb\":99.727} {\"correlation_id\":\"5852bcf1-af92-4d24-8a2d-08a9d4a5cfac\",\"trace_id\":\"25af5e8c-a148-493e-ac9f-4d53f1960177\"}\n[2026-04-27 10:28:04] local.INFO: [ScheduleBotCommand] Number of activities to be captured: 0 {\"correlation_id\":\"5852bcf1-af92-4d24-8a2d-08a9d4a5cfac\",\"trace_id\":\"25af5e8c-a148-493e-ac9f-4d53f1960177\"}\n[2026-04-27 10:28:04] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage for command {\"command\":\"meeting-bot:schedule-bot\",\"memoryBeforeCommandInMb\":60.0,\"memoryAfterCommandInMB\":62.0,\"memoryPeakBeforeCommandInMb\":99.727,\"memoryPeakAfterCommandInMB\":99.727} {\"correlation_id\":\"5852bcf1-af92-4d24-8a2d-08a9d4a5cfac\",\"trace_id\":\"25af5e8c-a148-493e-ac9f-4d53f1960177\"}\n[2026-04-27 10:28:06] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage before starting command {\"command\":\"dialers:monitor-activities\",\"memoryBeforeCommandInMb\":60.0,\"memoryPeakBeforeCommandInMb\":99.727} {\"correlation_id\":\"cc1cff1f-71b4-4bb1-800f-66e27c0f2b3a\",\"trace_id\":\"c03103a3-9ab8-480b-9753-7c7c6488f246\"}\n[2026-04-27 10:28:06] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage for command {\"command\":\"dialers:monitor-activities\",\"memoryBeforeCommandInMb\":60.0,\"memoryAfterCommandInMB\":62.0,\"memoryPeakBeforeCommandInMb\":99.727,\"memoryPeakAfterCommandInMB\":99.727} {\"correlation_id\":\"cc1cff1f-71b4-4bb1-800f-66e27c0f2b3a\",\"trace_id\":\"c03103a3-9ab8-480b-9753-7c7c6488f246\"}\n[2026-04-27 10:28:09] local.ERROR: Target class [Jiminny\\Repositories\\AjReportsRepository] does not exist. {\"exception\":\"[object] (Illuminate\\\\Contracts\\\\Container\\\\BindingResolutionException(code: 0): Target class [Jiminny\\\\Repositories\\\\AjReportsRepository] does not exist. at /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php:1124)\n[stacktrace]\n#0 /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php(933): Illuminate\\\\Container\\\\Container->build('Jiminny\\\\\\\\Reposit...')\n#1 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Application.php(1078): Illuminate\\\\Container\\\\Container->resolve('Jiminny\\\\\\\\Reposit...', Array, true)\n#2 /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php(864): Illuminate\\\\Foundation\\\\Application->resolve('Jiminny\\\\\\\\Reposit...', Array)\n#3 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Application.php(1058): Illuminate\\\\Container\\\\Container->make('Jiminny\\\\\\\\Reposit...', Array)\n#4 /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php(1336): Illuminate\\\\Foundation\\\\Application->make('Jiminny\\\\\\\\Reposit...')\n#5 /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php(1237): Illuminate\\\\Container\\\\Container->resolveClass(Object(ReflectionParameter))\n#6 /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php(1162): Illuminate\\\\Container\\\\Container->resolveDependencies(Array)\n#7 /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php(933): Illuminate\\\\Container\\\\Container->build('Jiminny\\\\\\\\Http\\\\\\\\Co...')\n#8 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Application.php(1078): Illuminate\\\\Container\\\\Container->resolve('Jiminny\\\\\\\\Http\\\\\\\\Co...', Array, true)\n#9 /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php(864): Illuminate\\\\Foundation\\\\Application->resolve('Jiminny\\\\\\\\Http\\\\\\\\Co...', Array)\n#10 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Application.php(1058): Illuminate\\\\Container\\\\Container->make('Jiminny\\\\\\\\Http\\\\\\\\Co...', Array)\n#11 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Route.php(286): Illuminate\\\\Foundation\\\\Application->make('Jiminny\\\\\\\\Http\\\\\\\\Co...')\n#12 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Route.php(1133): Illuminate\\\\Routing\\\\Route->getController()\n#13 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Route.php(1062): Illuminate\\\\Routing\\\\Route->controllerMiddleware()\n#14 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Router.php(834): Illuminate\\\\Routing\\\\Route->gatherMiddleware()\n#15 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Router.php(816): Illuminate\\\\Routing\\\\Router->gatherRouteMiddleware(Object(Illuminate\\\\Routing\\\\Route))\n#16 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Router.php(800): Illuminate\\\\Routing\\\\Router->runRouteWithinStack(Object(Illuminate\\\\Routing\\\\Route), Object(Illuminate\\\\Http\\\\Request))\n#17 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Router.php(764): Illuminate\\\\Routing\\\\Router->runRoute(Object(Illuminate\\\\Http\\\\Request), Object(Illuminate\\\\Routing\\\\Route))\n#18 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Router.php(753): Illuminate\\\\Routing\\\\Router->dispatchToRoute(Object(Illuminate\\\\Http\\\\Request))\n#19 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Http/Kernel.php(200): Illuminate\\\\Routing\\\\Router->dispatch(Object(Illuminate\\\\Http\\\\Request))\n#20 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(180): Illuminate\\\\Foundation\\\\Http\\\\Kernel->Illuminate\\\\Foundation\\\\Http\\\\{closure}(Object(Illuminate\\\\Http\\\\Request))\n#21 /home/jiminny/vendor/barryvdh/laravel-debugbar/src/Middleware/InjectDebugbar.php(59): Illuminate\\\\Pipeline\\\\Pipeline->Illuminate\\\\Pipeline\\\\{closure}(Object(Illuminate\\\\Http\\\\Request))\n#22 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(219): Barryvdh\\\\Debugbar\\\\Middleware\\\\InjectDebugbar->handle(Object(Illuminate\\\\Http\\\\Request), Object(Closure))\n#23 /home/jiminny/vendor/laravel/framework/src/Illuminate/Http/Middleware/HandleCors.php(74): Illuminate\\\\Pipeline\\\\Pipeline->Illuminate\\\\Pipeline\\\\{closure}(Object(Illuminate\\\\Http\\\\Request))\n#24 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(219): Illuminate\\\\Http\\\\Middleware\\\\HandleCors->handle(Object(Illuminate\\\\Http\\\\Request), Object(Closure))\n#25 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Http/Middleware/TransformsRequest.php(21): Illuminate\\\\Pipeline\\\\Pipeline->Illuminate\\\\Pipeline\\\\{closure}(Object(Illuminate\\\\Http\\\\Request))\n#26 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Http/Middleware/TrimStrings.php(51): Illuminate\\\\Foundation\\\\Http\\\\Middleware\\\\TransformsRequest->handle(Object(Illuminate\\\\Http\\\\Request), Object(Closure))\n#27 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(219): Illuminate\\\\Foundation\\\\Http\\\\Middleware\\\\TrimStrings->handle(Object(Illuminate\\\\Http\\\\Request), Object(Closure))\n#28 /home/jiminny/vendor/bepsvpt/secure-headers/src/SecureHeadersMiddleware.php(18): Illuminate\\\\Pipeline\\\\Pipeline->Illuminate\\\\Pipeline\\\\{closure}(Object(Illuminate\\\\Http\\\\Request))\n#29 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(219): Bepsvpt\\\\SecureHeaders\\\\SecureHeadersMiddleware->handle(Object(Illuminate\\\\Http\\\\Request), Object(Closure))\n#30 /home/jiminny/app/Http/Middleware/SentryContext.php(60): Illuminate\\\\Pipeline\\\\Pipeline->Illuminate\\\\Pipeline\\\\{closure}(Object(Illuminate\\\\Http\\\\Request))\n#31 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(219): Jiminny\\\\Http\\\\Middleware\\\\SentryContext->handle(Object(Illuminate\\\\Http\\\\Request), Object(Closure))\n#32 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Http/Middleware/PreventRequestsDuringMaintenance.php(109): Illuminate\\\\Pipeline\\\\Pipeline->Illuminate\\\\Pipeline\\\\{closure}(Object(Illuminate\\\\Http\\\\Request))\n#33 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(219): Illuminate\\\\Foundation\\\\Http\\\\Middleware\\\\PreventRequestsDuringMaintenance->handle(Object(Illuminate\\\\Http\\\\Request), Object(Closure))\n#34 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Http/Middleware/InvokeDeferredCallbacks.php(22): Illuminate\\\\Pipeline\\\\Pipeline->Illuminate\\\\Pipeline\\\\{closure}(Object(Illuminate\\\\Http\\\\Request))\n#35 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(219): Illuminate\\\\Foundation\\\\Http\\\\Middleware\\\\InvokeDeferredCallbacks->handle(Object(Illuminate\\\\Http\\\\Request), Object(Closure))\n#36 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(137): Illuminate\\\\Pipeline\\\\Pipeline->Illuminate\\\\Pipeline\\\\{closure}(Object(Illuminate\\\\Http\\\\Request))\n#37 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Http/Kernel.php(175): Illuminate\\\\Pipeline\\\\Pipeline->then(Object(Closure))\n#38 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Http/Kernel.php(144): Illuminate\\\\Foundation\\\\Http\\\\Kernel->sendRequestThroughRouter(Object(Illuminate\\\\Http\\\\Request))\n#39 /home/jiminny/public/index.php(51): Illuminate\\\\Foundation\\\\Http\\\\Kernel->handle(Object(Illuminate\\\\Http\\\\Request))\n#40 {main}\n\n[previous exception] [object] (ReflectionException(code: -1): Class \\\"Jiminny\\\\Repositories\\\\AjReportsRepository\\\" does not exist at /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php:1122)\n[stacktrace]\n#0 /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php(1122): ReflectionClass->__construct('Jiminny\\\\\\\\Reposit...')\n#1 /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php(933): Illuminate\\\\Container\\\\Container->build('Jiminny\\\\\\\\Reposit...')\n#2 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Application.php(1078): Illuminate\\\\Container\\\\Container->resolve('Jiminny\\\\\\\\Reposit...', Array, true)\n#3 /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php(864): Illuminate\\\\Foundation\\\\Application->resolve('Jiminny\\\\\\\\Reposit...', Array)\n#4 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Application.php(1058): Illuminate\\\\Container\\\\Container->make('Jiminny\\\\\\\\Reposit...', Array)\n#5 /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php(1336): Illuminate\\\\Foundation\\\\Application->make('Jiminny\\\\\\\\Reposit...')\n#6 /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php(1237): Illuminate\\\\Container\\\\Container->resolveClass(Object(ReflectionParameter))\n#7 /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php(1162): Illuminate\\\\Container\\\\Container->resolveDependencies(Array)\n#8 /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php(933): Illuminate\\\\Container\\\\Container->build('Jiminny\\\\\\\\Http\\\\\\\\Co...')\n#9 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Application.php(1078): Illuminate\\\\Container\\\\Container->resolve('Jiminny\\\\\\\\Http\\\\\\\\Co...', Array, true)\n#10 /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php(864): Illuminate\\\\Foundation\\\\Application->resolve('Jiminny\\\\\\\\Http\\\\\\\\Co...', Array)\n#11 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Application.php(1058): Illuminate\\\\Container\\\\Container->make('Jiminny\\\\\\\\Http\\\\\\\\Co...', Array)\n#12 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Route.php(286): Illuminate\\\\Foundation\\\\Application->make('Jiminny\\\\\\\\Http\\\\\\\\Co...')\n#13 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Route.php(1133): Illuminate\\\\Routing\\\\Route->getController()\n#14 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Route.php(1062): Illuminate\\\\Routing\\\\Route->controllerMiddleware()\n#15 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Router.php(834): Illuminate\\\\Routing\\\\Route->gatherMiddleware()\n#16 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Router.php(816): Illuminate\\\\Routing\\\\Router->gatherRouteMiddleware(Object(Illuminate\\\\Routing\\\\Route))\n#17 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Router.php(800): Illuminate\\\\Routing\\\\Router->runRouteWithinStack(Object(Illuminate\\\\Routing\\\\Route), Object(Illuminate\\\\Http\\\\Request))\n#18 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Router.php(764): Illuminate\\\\Routing\\\\Router->runRoute(Object(Illuminate\\\\Http\\\\Request), Object(Illuminate\\\\Routing\\\\Route))\n#19 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Router.php(753): Illuminate\\\\Routing\\\\Router->dispatchToRoute(Object(Illuminate\\\\Http\\\\Request))\n#20 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Http/Kernel.php(200): Illuminate\\\\Routing\\\\Router->dispatch(Object(Illuminate\\\\Http\\\\Request))\n#21 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(180): Illuminate\\\\Foundation\\\\Http\\\\Kernel->Illuminate\\\\Foundation\\\\Http\\\\{closure}(Object(Illuminate\\\\Http\\\\Request))\n#22 /home/jiminny/vendor/barryvdh/laravel-debugbar/src/Middleware/InjectDebugbar.php(59): Illuminate\\\\Pipeline\\\\Pipeline->Illuminate\\\\Pipeline\\\\{closure}(Object(Illuminate\\\\Http\\\\Request))\n#23 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(219): Barryvdh\\\\Debugbar\\\\Middleware\\\\InjectDebugbar->handle(Object(Illuminate\\\\Http\\\\Request), Object(Closure))\n#24 /home/jiminny/vendor/laravel/framework/src/Illuminate/Http/Middleware/HandleCors.php(74): Illuminate\\\\Pipeline\\\\Pipeline->Illuminate\\\\Pipeline\\\\{closure}(Object(Illuminate\\\\Http\\\\Request))\n#25 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(219): Illuminate\\\\Http\\\\Middleware\\\\HandleCors->handle(Object(Illuminate\\\\Http\\\\Request), Object(Closure))\n#26 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Http/Middleware/TransformsRequest.php(21): Illuminate\\\\Pipeline\\\\Pipeline->Illuminate\\\\Pipeline\\\\{closure}(Object(Illuminate\\\\Http\\\\Request))\n#27 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Http/Middleware/TrimStrings.php(51): Illuminate\\\\Foundation\\\\Http\\\\Middleware\\\\TransformsRequest->handle(Object(Illuminate\\\\Http\\\\Request), Object(Closure))\n#28 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(219): Illuminate\\\\Foundation\\\\Http\\\\Middleware\\\\TrimStrings->handle(Object(Illuminate\\\\Http\\\\Request), Object(Closure))\n#29 /home/jiminny/vendor/bepsvpt/secure-headers/src/SecureHeadersMiddleware.php(18): Illuminate\\\\Pipeline\\\\Pipeline->Illuminate\\\\Pipeline\\\\{closure}(Object(Illuminate\\\\Http\\\\Request))\n#30 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(219): Bepsvpt\\\\SecureHeaders\\\\SecureHeadersMiddleware->handle(Object(Illuminate\\\\Http\\\\Request), Object(Closure))\n#31 /home/jiminny/app/Http/Middleware/SentryContext.php(60): Illuminate\\\\Pipeline\\\\Pipeline->Illuminate\\\\Pipeline\\\\{closure}(Object(Illuminate\\\\Http\\\\Request))\n#32 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(219): Jiminny\\\\Http\\\\Middleware\\\\SentryContext->handle(Object(Illuminate\\\\Http\\\\Request), Object(Closure))\n#33 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Http/Middleware/PreventRequestsDuringMaintenance.php(109): Illuminate\\\\Pipeline\\\\Pipeline->Illuminate\\\\Pipeline\\\\{closure}(Object(Illuminate\\\\Http\\\\Request))\n#34 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(219): Illuminate\\\\Foundation\\\\Http\\\\Middleware\\\\PreventRequestsDuringMaintenance->handle(Object(Illuminate\\\\Http\\\\Request), Object(Closure))\n#35 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Http/Middleware/InvokeDeferredCallbacks.php(22): Illuminate\\\\Pipeline\\\\Pipeline->Illuminate\\\\Pipeline\\\\{closure}(Object(Illuminate\\\\Http\\\\Request))\n#36 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(219): Illuminate\\\\Foundation\\\\Http\\\\Middleware\\\\InvokeDeferredCallbacks->handle(Object(Illuminate\\\\Http\\\\Request), Object(Closure))\n#37 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(137): Illuminate\\\\Pipeline\\\\Pipeline->Illuminate\\\\Pipeline\\\\{closure}(Object(Illuminate\\\\Http\\\\Request))\n#38 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Http/Kernel.php(175): Illuminate\\\\Pipeline\\\\Pipeline->then(Object(Closure))\n#39 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Http/Kernel.php(144): Illuminate\\\\Foundation\\\\Http\\\\Kernel->sendRequestThroughRouter(Object(Illuminate\\\\Http\\\\Request))\n#40 /home/jiminny/public/index.php(51): Illuminate\\\\Foundation\\\\Http\\\\Kernel->handle(Object(Illuminate\\\\Http\\\\Request))\n#41 {main}\n\"} {\"correlation_id\":\"dfad8817-1dde-4622-8320-f5fc1616a8fe\",\"trace_id\":\"d21232b2-60fb-4ca0-838c-ba42e61593f9\"}\n[2026-04-27 10:28:09] local.NOTICE: Monitoring start {\"correlation_id\":\"93744c67-4444-41ff-aaad-6aa9cafe294e\",\"trace_id\":\"80e396b0-b1ac-4ec8-838c-00021f98072c\"}\n[2026-04-27 10:28:09] local.NOTICE: Monitoring end {\"correlation_id\":\"93744c67-4444-41ff-aaad-6aa9cafe294e\",\"trace_id\":\"80e396b0-b1ac-4ec8-838c-00021f98072c\"}\n[2026-04-27 10:28:10] local.ERROR: Target class [Jiminny\\Repositories\\AjReportsRepository] does not exist. {\"exception\":\"[object] (Illuminate\\\\Contracts\\\\Container\\\\BindingResolutionException(code: 0): Target class [Jiminny\\\\Repositories\\\\AjReportsRepository] does not exist. at /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php:1124)\n[stacktrace]\n#0 /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php(933): Illuminate\\\\Container\\\\Container->build('Jiminny\\\\\\\\Reposit...')\n#1 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Application.php(1078): Illuminate\\\\Container\\\\Container->resolve('Jiminny\\\\\\\\Reposit...', Array, true)\n#2 /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php(864): Illuminate\\\\Foundation\\\\Application->resolve('Jiminny\\\\\\\\Reposit...', Array)\n#3 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Application.php(1058): Illuminate\\\\Container\\\\Container->make('Jiminny\\\\\\\\Reposit...', Array)\n#4 /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php(1336): Illuminate\\\\Foundation\\\\Application->make('Jiminny\\\\\\\\Reposit...')\n#5 /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php(1237): Illuminate\\\\Container\\\\Container->resolveClass(Object(ReflectionParameter))\n#6 /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php(1162): Illuminate\\\\Container\\\\Container->resolveDependencies(Array)\n#7 /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php(933): Illuminate\\\\Container\\\\Container->build('Jiminny\\\\\\\\Http\\\\\\\\Co...')\n#8 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Application.php(1078): Illuminate\\\\Container\\\\Container->resolve('Jiminny\\\\\\\\Http\\\\\\\\Co...', Array, true)\n#9 /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php(864): Illuminate\\\\Foundation\\\\Application->resolve('Jiminny\\\\\\\\Http\\\\\\\\Co...', Array)\n#10 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Application.php(1058): Illuminate\\\\Container\\\\Container->make('Jiminny\\\\\\\\Http\\\\\\\\Co...', Array)\n#11 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Route.php(286): Illuminate\\\\Foundation\\\\Application->make('Jiminny\\\\\\\\Http\\\\\\\\Co...')\n#12 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Route.php(1133): Illuminate\\\\Routing\\\\Route->getController()\n#13 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Route.php(1062): Illuminate\\\\Routing\\\\Route->controllerMiddleware()\n#14 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Router.php(834): Illuminate\\\\Routing\\\\Route->gatherMiddleware()\n#15 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Router.php(816): Illuminate\\\\Routing\\\\Router->gatherRouteMiddleware(Object(Illuminate\\\\Routing\\\\Route))\n#16 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Router.php(800): Illuminate\\\\Routing\\\\Router->runRouteWithinStack(Object(Illuminate\\\\Routing\\\\Route), Object(Illuminate\\\\Http\\\\Request))\n#17 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Router.php(764): Illuminate\\\\Routing\\\\Router->runRoute(Object(Illuminate\\\\Http\\\\Request), Object(Illuminate\\\\Routing\\\\Route))\n#18 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Router.php(753): Illuminate\\\\Routing\\\\Router->dispatchToRoute(Object(Illuminate\\\\Http\\\\Request))\n#19 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Http/Kernel.php(200): Illuminate\\\\Routing\\\\Router->dispatch(Object(Illuminate\\\\Http\\\\Request))\n#20 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(180): Illuminate\\\\Foundation\\\\Http\\\\Kernel->Illuminate\\\\Foundation\\\\Http\\\\{closure}(Object(Illuminate\\\\Http\\\\Request))\n#21 /home/jiminny/vendor/barryvdh/laravel-debugbar/src/Middleware/InjectDebugbar.php(59): Illuminate\\\\Pipeline\\\\Pipeline->Illuminate\\\\Pipeline\\\\{closure}(Object(Illuminate\\\\Http\\\\Request))\n#22 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(219): Barryvdh\\\\Debugbar\\\\Middleware\\\\InjectDebugbar->handle(Object(Illuminate\\\\Http\\\\Request), Object(Closure))\n#23 /home/jiminny/vendor/laravel/framework/src/Illuminate/Http/Middleware/HandleCors.php(74): Illuminate\\\\Pipeline\\\\Pipeline->Illuminate\\\\Pipeline\\\\{closure}(Object(Illuminate\\\\Http\\\\Request))\n#24 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(219): Illuminate\\\\Http\\\\Middleware\\\\HandleCors->handle(Object(Illuminate\\\\Http\\\\Request), Object(Closure))\n#25 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Http/Middleware/TransformsRequest.php(21): Illuminate\\\\Pipeline\\\\Pipeline->Illuminate\\\\Pipeline\\\\{closure}(Object(Illuminate\\\\Http\\\\Request))\n#26 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Http/Middleware/TrimStrings.php(51): Illuminate\\\\Foundation\\\\Http\\\\Middleware\\\\TransformsRequest->handle(Object(Illuminate\\\\Http\\\\Request), Object(Closure))\n#27 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(219): Illuminate\\\\Foundation\\\\Http\\\\Middleware\\\\TrimStrings->handle(Object(Illuminate\\\\Http\\\\Request), Object(Closure))\n#28 /home/jiminny/vendor/bepsvpt/secure-headers/src/SecureHeadersMiddleware.php(18): Illuminate\\\\Pipeline\\\\Pipeline->Illuminate\\\\Pipeline\\\\{closure}(Object(Illuminate\\\\Http\\\\Request))\n#29 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(219): Bepsvpt\\\\SecureHeaders\\\\SecureHeadersMiddleware->handle(Object(Illuminate\\\\Http\\\\Request), Object(Closure))\n#30 /home/jiminny/app/Http/Middleware/SentryContext.php(60): Illuminate\\\\Pipeline\\\\Pipeline->Illuminate\\\\Pipeline\\\\{closure}(Object(Illuminate\\\\Http\\\\Request))\n#31 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(219): Jiminny\\\\Http\\\\Middleware\\\\SentryContext->handle(Object(Illuminate\\\\Http\\\\Request), Object(Closure))\n#32 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Http/Middleware/PreventRequestsDuringMaintenance.php(109): Illuminate\\\\Pipeline\\\\Pipeline->Illuminate\\\\Pipeline\\\\{closure}(Object(Illuminate\\\\Http\\\\Request))\n#33 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(219): Illuminate\\\\Foundation\\\\Http\\\\Middleware\\\\PreventRequestsDuringMaintenance->handle(Object(Illuminate\\\\Http\\\\Request), Object(Closure))\n#34 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Http/Middleware/InvokeDeferredCallbacks.php(22): Illuminate\\\\Pipeline\\\\Pipeline->Illuminate\\\\Pipeline\\\\{closure}(Object(Illuminate\\\\Http\\\\Request))\n#35 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(219): Illuminate\\\\Foundation\\\\Http\\\\Middleware\\\\InvokeDeferredCallbacks->handle(Object(Illuminate\\\\Http\\\\Request), Object(Closure))\n#36 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(137): Illuminate\\\\Pipeline\\\\Pipeline->Illuminate\\\\Pipeline\\\\{closure}(Object(Illuminate\\\\Http\\\\Request))\n#37 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Http/Kernel.php(175): Illuminate\\\\Pipeline\\\\Pipeline->then(Object(Closure))\n#38 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Http/Kernel.php(144): Illuminate\\\\Foundation\\\\Http\\\\Kernel->sendRequestThroughRouter(Object(Illuminate\\\\Http\\\\Request))\n#39 /home/jiminny/public/index.php(51): Illuminate\\\\Foundation\\\\Http\\\\Kernel->handle(Object(Illuminate\\\\Http\\\\Request))\n#40 {main}\n\n[previous exception] [object] (ReflectionException(code: -1): Class \\\"Jiminny\\\\Repositories\\\\AjReportsRepository\\\" does not exist at /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php:1122)\n[stacktrace]\n#0 /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php(1122): ReflectionClass->__construct('Jiminny\\\\\\\\Reposit...')\n#1 /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php(933): Illuminate\\\\Container\\\\Container->build('Jiminny\\\\\\\\Reposit...')\n#2 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Application.php(1078): Illuminate\\\\Container\\\\Container->resolve('Jiminny\\\\\\\\Reposit...', Array, true)\n#3 /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php(864): Illuminate\\\\Foundation\\\\Application->resolve('Jiminny\\\\\\\\Reposit...', Array)\n#4 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Application.php(1058): Illuminate\\\\Container\\\\Container->make('Jiminny\\\\\\\\Reposit...', Array)\n#5 /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php(1336): Illuminate\\\\Foundation\\\\Application->make('Jiminny\\\\\\\\Reposit...')\n#6 /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php(1237): Illuminate\\\\Container\\\\Container->resolveClass(Object(ReflectionParameter))\n#7 /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php(1162): Illuminate\\\\Container\\\\Container->resolveDependencies(Array)\n#8 /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php(933): Illuminate\\\\Container\\\\Container->build('Jiminny\\\\\\\\Http\\\\\\\\Co...')\n#9 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Application.php(1078): Illuminate\\\\Container\\\\Container->resolve('Jiminny\\\\\\\\Http\\\\\\\\Co...', Array, true)\n#10 /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php(864): Illuminate\\\\Foundation\\\\Application->resolve('Jiminny\\\\\\\\Http\\\\\\\\Co...', Array)\n#11 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Application.php(1058): Illuminate\\\\Container\\\\Container->make('Jiminny\\\\\\\\Http\\\\\\\\Co...', Array)\n#12 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Route.php(286): Illuminate\\\\Foundation\\\\Application->make('Jiminny\\\\\\\\Http\\\\\\\\Co...')\n#13 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Route.php(1133): Illuminate\\\\Routing\\\\Route->getController()\n#14 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Route.php(1062): Illuminate\\\\Routing\\\\Route->controllerMiddleware()\n#15 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Router.php(834): Illuminate\\\\Routing\\\\Route->gatherMiddleware()\n#16 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Router.php(816): Illuminate\\\\Routing\\\\Router->gatherRouteMiddleware(Object(Illuminate\\\\Routing\\\\Route))\n#17 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Router.php(800): Illuminate\\\\Routing\\\\Router->runRouteWithinStack(Object(Illuminate\\\\Routing\\\\Route), Object(Illuminate\\\\Http\\\\Request))\n#18 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Router.php(764): Illuminate\\\\Routing\\\\Router->runRoute(Object(Illuminate\\\\Http\\\\Request), Object(Illuminate\\\\Routing\\\\Route))\n#19 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Router.php(753): Illuminate\\\\Routing\\\\Router->dispatchToRoute(Object(Illuminate\\\\Http\\\\Request))\n#20 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Http/Kernel.php(200): Illuminate\\\\Routing\\\\Router->dispatch(Object(Illuminate\\\\Http\\\\Request))\n#21 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(180): Illuminate\\\\Foundation\\\\Http\\\\Kernel->Illuminate\\\\Foundation\\\\Http\\\\{closure}(Object(Illuminate\\\\Http\\\\Request))\n#22 /home/jiminny/vendor/barryvdh/laravel-debugbar/src/Middleware/InjectDebugbar.php(59): Illuminate\\\\Pipeline\\\\Pipeline->Illuminate\\\\Pipeline\\\\{closure}(Object(Illuminate\\\\Http\\\\Request))\n#23 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(219): Barryvdh\\\\Debugbar\\\\Middleware\\\\InjectDebugbar->handle(Object(Illuminate\\\\Http\\\\Request), Object(Closure))\n#24 /home/jiminny/vendor/laravel/framework/src/Illuminate/Http/Middleware/HandleCors.php(74): Illuminate\\\\Pipeline\\\\Pipeline->Illuminate\\\\Pipeline\\\\{closure}(Object(Illuminate\\\\Http\\\\Request))\n#25 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(219): Illuminate\\\\Http\\\\Middleware\\\\HandleCors->handle(Object(Illuminate\\\\Http\\\\Request), Object(Closure))\n#26 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Http/Middleware/TransformsRequest.php(21): Illuminate\\\\Pipeline\\\\Pipeline->Illuminate\\\\Pipeline\\\\{closure}(Object(Illuminate\\\\Http\\\\Request))\n#27 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Http/Middleware/TrimStrings.php(51): Illuminate\\\\Foundation\\\\Http\\\\Middleware\\\\TransformsRequest->handle(Object(Illuminate\\\\Http\\\\Request), Object(Closure))\n#28 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(219): Illuminate\\\\Foundation\\\\Http\\\\Middleware\\\\TrimStrings->handle(Object(Illuminate\\\\Http\\\\Request), Object(Closure))\n#29 /home/jiminny/vendor/bepsvpt/secure-headers/src/SecureHeadersMiddleware.php(18): Illuminate\\\\Pipeline\\\\Pipeline->Illuminate\\\\Pipeline\\\\{closure}(Object(Illuminate\\\\Http\\\\Request))\n#30 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(219): Bepsvpt\\\\SecureHeaders\\\\SecureHeadersMiddleware->handle(Object(Illuminate\\\\Http\\\\Request), Object(Closure))\n#31 /home/jiminny/app/Http/Middleware/SentryContext.php(60): Illuminate\\\\Pipeline\\\\Pipeline->Illuminate\\\\Pipeline\\\\{closure}(Object(Illuminate\\\\Http\\\\Request))\n#32 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(219): Jiminny\\\\Http\\\\Middleware\\\\SentryContext->handle(Object(Illuminate\\\\Http\\\\Request), Object(Closure))\n#33 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Http/Middleware/PreventRequestsDuringMaintenance.php(109): Illuminate\\\\Pipeline\\\\Pipeline->Illuminate\\\\Pipeline\\\\{closure}(Object(Illuminate\\\\Http\\\\Request))\n#34 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(219): Illuminate\\\\Foundation\\\\Http\\\\Middleware\\\\PreventRequestsDuringMaintenance->handle(Object(Illuminate\\\\Http\\\\Request), Object(Closure))\n#35 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Http/Middleware/InvokeDeferredCallbacks.php(22): Illuminate\\\\Pipeline\\\\Pipeline->Illuminate\\\\Pipeline\\\\{closure}(Object(Illuminate\\\\Http\\\\Request))\n#36 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(219): Illuminate\\\\Foundation\\\\Http\\\\Middleware\\\\InvokeDeferredCallbacks->handle(Object(Illuminate\\\\Http\\\\Request), Object(Closure))\n#37 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(137): Illuminate\\\\Pipeline\\\\Pipeline->Illuminate\\\\Pipeline\\\\{closure}(Object(Illuminate\\\\Http\\\\Request))\n#38 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Http/Kernel.php(175): Illuminate\\\\Pipeline\\\\Pipeline->then(Object(Closure))\n#39 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Http/Kernel.php(144): Illuminate\\\\Foundation\\\\Http\\\\Kernel->sendRequestThroughRouter(Object(Illuminate\\\\Http\\\\Request))\n#40 /home/jiminny/public/index.php(51): Illuminate\\\\Foundation\\\\Http\\\\Kernel->handle(Object(Illuminate\\\\Http\\\\Request))\n#41 {main}\n\"} {\"correlation_id\":\"de768781-5c05-418c-baf3-79f77a8c8694\",\"trace_id\":\"fec74062-3f6d-4cfc-968a-1bb285496db3\"}\n[2026-04-27 10:28:12] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage before starting command {\"command\":\"mailbox:skip-lists:refresh\",\"memoryBeforeCommandInMb\":60.0,\"memoryPeakBeforeCommandInMb\":99.727} {\"correlation_id\":\"907d15ed-8c84-41a1-a473-590eebb55dbc\",\"trace_id\":\"ec7bd353-516e-491d-89dd-de44136c1672\"}\n[2026-04-27 10:28:12] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage for command {\"command\":\"mailbox:skip-lists:refresh\",\"memoryBeforeCommandInMb\":60.0,\"memoryAfterCommandInMB\":62.0,\"memoryPeakBeforeCommandInMb\":99.727,\"memoryPeakAfterCommandInMB\":99.727} {\"correlation_id\":\"907d15ed-8c84-41a1-a473-590eebb55dbc\",\"trace_id\":\"ec7bd353-516e-491d-89dd-de44136c1672\"}\n[2026-04-27 10:28:13] local.ERROR: Target class [Jiminny\\Repositories\\AjReportsRepository] does not exist. {\"exception\":\"[object] (Illuminate\\\\Contracts\\\\Container\\\\BindingResolutionException(code: 0): Target class [Jiminny\\\\Repositories\\\\AjReportsRepository] does not exist. at /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php:1124)\n[stacktrace]\n#0 /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php(933): Illuminate\\\\Container\\\\Container->build('Jiminny\\\\\\\\Reposit...')\n#1 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Application.php(1078): Illuminate\\\\Container\\\\Container->resolve('Jiminny\\\\\\\\Reposit...', Array, true)\n#2 /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php(864): Illuminate\\\\Foundation\\\\Application->resolve('Jiminny\\\\\\\\Reposit...', Array)\n#3 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Application.php(1058): Illuminate\\\\Container\\\\Container->make('Jiminny\\\\\\\\Reposit...', Array)\n#4 /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php(1336): Illuminate\\\\Foundation\\\\Application->make('Jiminny\\\\\\\\Reposit...')\n#5 /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php(1237): Illuminate\\\\Container\\\\Container->resolveClass(Object(ReflectionParameter))\n#6 /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php(1162): Illuminate\\\\Container\\\\Container->resolveDependencies(Array)\n#7 /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php(933): Illuminate\\\\Container\\\\Container->build('Jiminny\\\\\\\\Http\\\\\\\\Co...')\n#8 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Application.php(1078): Illuminate\\\\Container\\\\Container->resolve('Jiminny\\\\\\\\Http\\\\\\\\Co...', Array, true)\n#9 /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php(864): Illuminate\\\\Foundation\\\\Application->resolve('Jiminny\\\\\\\\Http\\\\\\\\Co...', Array)\n#10 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Application.php(1058): Illuminate\\\\Container\\\\Container->make('Jiminny\\\\\\\\Http\\\\\\\\Co...', Array)\n#11 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Route.php(286): Illuminate\\\\Foundation\\\\Application->make('Jiminny\\\\\\\\Http\\\\\\\\Co...')\n#12 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Route.php(1133): Illuminate\\\\Routing\\\\Route->getController()\n#13 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Route.php(1062): Illuminate\\\\Routing\\\\Route->controllerMiddleware()\n#14 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Router.php(834): Illuminate\\\\Routing\\\\Route->gatherMiddleware()\n#15 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Router.php(816): Illuminate\\\\Routing\\\\Router->gatherRouteMiddleware(Object(Illuminate\\\\Routing\\\\Route))\n#16 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Router.php(800): Illuminate\\\\Routing\\\\Router->runRouteWithinStack(Object(Illuminate\\\\Routing\\\\Route), Object(Illuminate\\\\Http\\\\Request))\n#17 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Router.php(764): Illuminate\\\\Routing\\\\Router->runRoute(Object(Illuminate\\\\Http\\\\Request), Object(Illuminate\\\\Routing\\\\Route))\n#18 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Router.php(753): Illuminate\\\\Routing\\\\Router->dispatchToRoute(Object(Illuminate\\\\Http\\\\Request))\n#19 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Http/Kernel.php(200): Illuminate\\\\Routing\\\\Router->dispatch(Object(Illuminate\\\\Http\\\\Request))\n#20 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(180): Illuminate\\\\Foundation\\\\Http\\\\Kernel->Illuminate\\\\Foundation\\\\Http\\\\{closure}(Object(Illuminate\\\\Http\\\\Request))\n#21 /home/jiminny/vendor/barryvdh/laravel-debugbar/src/Middleware/InjectDebugbar.php(59): Illuminate\\\\Pipeline\\\\Pipeline->Illuminate\\\\Pipeline\\\\{closure}(Object(Illuminate\\\\Http\\\\Request))\n#22 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(219): Barryvdh\\\\Debugbar\\\\Middleware\\\\InjectDebugbar->handle(Object(Illuminate\\\\Http\\\\Request), Object(Closure))\n#23 /home/jiminny/vendor/laravel/framework/src/Illuminate/Http/Middleware/HandleCors.php(74): Illuminate\\\\Pipeline\\\\Pipeline->Illuminate\\\\Pipeline\\\\{closure}(Object(Illuminate\\\\Http\\\\Request))\n#24 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(219): Illuminate\\\\Http\\\\Middleware\\\\HandleCors->handle(Object(Illuminate\\\\Http\\\\Request), Object(Closure))\n#25 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Http/Middleware/TransformsRequest.php(21): Illuminate\\\\Pipeline\\\\Pipeline->Illuminate\\\\Pipeline\\\\{closure}(Object(Illuminate\\\\Http\\\\Request))\n#26 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Http/Middleware/TrimStrings.php(51): Illuminate\\\\Foundation\\\\Http\\\\Middleware\\\\TransformsRequest->handle(Object(Illuminate\\\\Http\\\\Request), Object(Closure))\n#27 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(219): Illuminate\\\\Foundation\\\\Http\\\\Middleware\\\\TrimStrings->handle(Object(Illuminate\\\\Http\\\\Request), Object(Closure))\n#28 /home/jiminny/vendor/bepsvpt/secure-headers/src/SecureHeadersMiddleware.php(18): Illuminate\\\\Pipeline\\\\Pipeline->Illuminate\\\\Pipeline\\\\{closure}(Object(Illuminate\\\\Http\\\\Request))\n#29 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(219): Bepsvpt\\\\SecureHeaders\\\\SecureHeadersMiddleware->handle(Object(Illuminate\\\\Http\\\\Request), Object(Closure))\n#30 /home/jiminny/app/Http/Middleware/SentryContext.php(60): Illuminate\\\\Pipeline\\\\Pipeline->Illuminate\\\\Pipeline\\\\{closure}(Object(Illuminate\\\\Http\\\\Request))\n#31 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(219): Jiminny\\\\Http\\\\Middleware\\\\SentryContext->handle(Object(Illuminate\\\\Http\\\\Request), Object(Closure))\n#32 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Http/Middleware/PreventRequestsDuringMaintenance.php(109): Illuminate\\\\Pipeline\\\\Pipeline->Illuminate\\\\Pipeline\\\\{closure}(Object(Illuminate\\\\Http\\\\Request))\n#33 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(219): Illuminate\\\\Foundation\\\\Http\\\\Middleware\\\\PreventRequestsDuringMaintenance->handle(Object(Illuminate\\\\Http\\\\Request), Object(Closure))\n#34 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Http/Middleware/InvokeDeferredCallbacks.php(22): Illuminate\\\\Pipeline\\\\Pipeline->Illuminate\\\\Pipeline\\\\{closure}(Object(Illuminate\\\\Http\\\\Request))\n#35 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(219): Illuminate\\\\Foundation\\\\Http\\\\Middleware\\\\InvokeDeferredCallbacks->handle(Object(Illuminate\\\\Http\\\\Request), Object(Closure))\n#36 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(137): Illuminate\\\\Pipeline\\\\Pipeline->Illuminate\\\\Pipeline\\\\{closure}(Object(Illuminate\\\\Http\\\\Request))\n#37 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Http/Kernel.php(175): Illuminate\\\\Pipeline\\\\Pipeline->then(Object(Closure))\n#38 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Http/Kernel.php(144): Illuminate\\\\Foundation\\\\Http\\\\Kernel->sendRequestThroughRouter(Object(Illuminate\\\\Http\\\\Request))\n#39 /home/jiminny/public/index.php(51): Illuminate\\\\Foundation\\\\Http\\\\Kernel->handle(Object(Illuminate\\\\Http\\\\Request))\n#40 {main}\n\n[previous exception] [object] (ReflectionException(code: -1): Class \\\"Jiminny\\\\Repositories\\\\AjReportsRepository\\\" does not exist at /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php:1122)\n[stacktrace]\n#0 /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php(1122): ReflectionClass->__construct('Jiminny\\\\\\\\Reposit...')\n#1 /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php(933): Illuminate\\\\Container\\\\Container->build('Jiminny\\\\\\\\Reposit...')\n#2 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Application.php(1078): Illuminate\\\\Container\\\\Container->resolve('Jiminny\\\\\\\\Reposit...', Array, true)\n#3 /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php(864): Illuminate\\\\Foundation\\\\Application->resolve('Jiminny\\\\\\\\Reposit...', Array)\n#4 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Application.php(1058): Illuminate\\\\Container\\\\Container->make('Jiminny\\\\\\\\Reposit...', Array)\n#5 /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php(1336): Illuminate\\\\Foundation\\\\Application->make('Jiminny\\\\\\\\Reposit...')\n#6 /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php(1237): Illuminate\\\\Container\\\\Container->resolveClass(Object(ReflectionParameter))\n#7 /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php(1162): Illuminate\\\\Container\\\\Container->resolveDependencies(Array)\n#8 /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php(933): Illuminate\\\\Container\\\\Container->build('Jiminny\\\\\\\\Http\\\\\\\\Co...')\n#9 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Application.php(1078): Illuminate\\\\Container\\\\Container->resolve('Jiminny\\\\\\\\Http\\\\\\\\Co...', Array, true)\n#10 /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php(864): Illuminate\\\\Foundation\\\\Application->resolve('Jiminny\\\\\\\\Http\\\\\\\\Co...', Array)\n#11 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Application.php(1058): Illuminate\\\\Container\\\\Container->make('Jiminny\\\\\\\\Http\\\\\\\\Co...', Array)\n#12 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Route.php(286): Illuminate\\\\Foundation\\\\Application->make('Jiminny\\\\\\\\Http\\\\\\\\Co...')\n#13 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Route.php(1133): Illuminate\\\\Routing\\\\Route->getController()\n#14 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Route.php(1062): Illuminate\\\\Routing\\\\Route->controllerMiddleware()\n#15 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Router.php(834): Illuminate\\\\Routing\\\\Route->gatherMiddleware()\n#16 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Router.php(816): Illuminate\\\\Routing\\\\Router->gatherRouteMiddleware(Object(Illuminate\\\\Routing\\\\Route))\n#17 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Router.php(800): Illuminate\\\\Routing\\\\Router->runRouteWithinStack(Object(Illuminate\\\\Routing\\\\Route), Object(Illuminate\\\\Http\\\\Request))\n#18 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Router.php(764): Illuminate\\\\Routing\\\\Router->runRoute(Object(Illuminate\\\\Http\\\\Request), Object(Illuminate\\\\Routing\\\\Route))\n#19 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Router.php(753): Illuminate\\\\Routing\\\\Router->dispatchToRoute(Object(Illuminate\\\\Http\\\\Request))\n#20 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Http/Kernel.php(200): Illuminate\\\\Routing\\\\Router->dispatch(Object(Illuminate\\\\Http\\\\Request))\n#21 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(180): Illuminate\\\\Foundation\\\\Http\\\\Kernel->Illuminate\\\\Foundation\\\\Http\\\\{closure}(Object(Illuminate\\\\Http\\\\Request))\n#22 /home/jiminny/vendor/barryvdh/laravel-debugbar/src/Middleware/InjectDebugbar.php(59): Illuminate\\\\Pipeline\\\\Pipeline->Illuminate\\\\Pipeline\\\\{closure}(Object(Illuminate\\\\Http\\\\Request))\n#23 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(219): Barryvdh\\\\Debugbar\\\\Middleware\\\\InjectDebugbar->handle(Object(Illuminate\\\\Http\\\\Request), Object(Closure))\n#24 /home/jiminny/vendor/laravel/framework/src/Illuminate/Http/Middleware/HandleCors.php(74): Illuminate\\\\Pipeline\\\\Pipeline->Illuminate\\\\Pipeline\\\\{closure}(Object(Illuminate\\\\Http\\\\Request))\n#25 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(219): Illuminate\\\\Http\\\\Middleware\\\\HandleCors->handle(Object(Illuminate\\\\Http\\\\Request), Object(Closure))\n#26 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Http/Middleware/TransformsRequest.php(21): Illuminate\\\\Pipeline\\\\Pipeline->Illuminate\\\\Pipeline\\\\{closure}(Object(Illuminate\\\\Http\\\\Request))\n#27 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Http/Middleware/TrimStrings.php(51): Illuminate\\\\Foundation\\\\Http\\\\Middleware\\\\TransformsRequest->handle(Object(Illuminate\\\\Http\\\\Request), Object(Closure))\n#28 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(219): Illuminate\\\\Foundation\\\\Http\\\\Middleware\\\\TrimStrings->handle(Object(Illuminate\\\\Http\\\\Request), Object(Closure))\n#29 /home/jiminny/vendor/bepsvpt/secure-headers/src/SecureHeadersMiddleware.php(18): Illuminate\\\\Pipeline\\\\Pipeline->Illuminate\\\\Pipeline\\\\{closure}(Object(Illuminate\\\\Http\\\\Request))\n#30 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(219): Bepsvpt\\\\SecureHeaders\\\\SecureHeadersMiddleware->handle(Object(Illuminate\\\\Http\\\\Request), Object(Closure))\n#31 /home/jiminny/app/Http/Middleware/SentryContext.php(60): Illuminate\\\\Pipeline\\\\Pipeline->Illuminate\\\\Pipeline\\\\{closure}(Object(Illuminate\\\\Http\\\\Request))\n#32 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(219): Jiminny\\\\Http\\\\Middleware\\\\SentryContext->handle(Object(Illuminate\\\\Http\\\\Request), Object(Closure))\n#33 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Http/Middleware/PreventRequestsDuringMaintenance.php(109): Illuminate\\\\Pipeline\\\\Pipeline->Illuminate\\\\Pipeline\\\\{closure}(Object(Illuminate\\\\Http\\\\Request))\n#34 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(219): Illuminate\\\\Foundation\\\\Http\\\\Middleware\\\\PreventRequestsDuringMaintenance->handle(Object(Illuminate\\\\Http\\\\Request), Object(Closure))\n#35 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Http/Middleware/InvokeDeferredCallbacks.php(22): Illuminate\\\\Pipeline\\\\Pipeline->Illuminate\\\\Pipeline\\\\{closure}(Object(Illuminate\\\\Http\\\\Request))\n#36 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(219): Illuminate\\\\Foundation\\\\Http\\\\Middleware\\\\InvokeDeferredCallbacks->handle(Object(Illuminate\\\\Http\\\\Request), Object(Closure))\n#37 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(137): Illuminate\\\\Pipeline\\\\Pipeline->Illuminate\\\\Pipeline\\\\{closure}(Object(Illuminate\\\\Http\\\\Request))\n#38 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Http/Kernel.php(175): Illuminate\\\\Pipeline\\\\Pipeline->then(Object(Closure))\n#39 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Http/Kernel.php(144): Illuminate\\\\Foundation\\\\Http\\\\Kernel->sendRequestThroughRouter(Object(Illuminate\\\\Http\\\\Request))\n#40 /home/jiminny/public/index.php(51): Illuminate\\\\Foundation\\\\Http\\\\Kernel->handle(Object(Illuminate\\\\Http\\\\Request))\n#41 {main}\n\"} {\"correlation_id\":\"d8709bb9-0f8c-4798-a8c7-e68e24ab6265\",\"trace_id\":\"c5bea1cc-53fe-4bda-a8e2-bec002030bc2\"}\n[2026-04-27 10:28:14] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage before starting command {\"command\":\"mailbox:batch:process\",\"memoryBeforeCommandInMb\":60.0,\"memoryPeakBeforeCommandInMb\":99.727} {\"correlation_id\":\"5fb3c8c8-3160-4964-ac56-02d6e2cb998c\",\"trace_id\":\"3a6d5e64-ecf2-4700-98cd-3edd56a1dc34\"}\n[2026-04-27 10:28:14] local.INFO: [EmailSchedule] STARTING batch process {\"host\":\"docker_lamp_1\"} {\"correlation_id\":\"5fb3c8c8-3160-4964-ac56-02d6e2cb998c\",\"trace_id\":\"3a6d5e64-ecf2-4700-98cd-3edd56a1dc34\"}\n[2026-04-27 10:28:14] local.INFO: Processing email batch 98446 for inbox 212 {\"correlation_id\":\"5fb3c8c8-3160-4964-ac56-02d6e2cb998c\",\"trace_id\":\"3a6d5e64-ecf2-4700-98cd-3edd56a1dc34\"}\n[2026-04-27 10:28:14] local.INFO: [SocialAccountService] Fetching token {\"socialAccountId\":1500,\"provider\":\"salesforce\"} {\"correlation_id\":\"5fb3c8c8-3160-4964-ac56-02d6e2cb998c\",\"trace_id\":\"3a6d5e64-ecf2-4700-98cd-3edd56a1dc34\"}\n[2026-04-27 10:28:14] local.INFO: [SocialAccountService] Token retrieved {\"socialAccountId\":1500,\"provider\":\"salesforce\"} {\"correlation_id\":\"5fb3c8c8-3160-4964-ac56-02d6e2cb998c\",\"trace_id\":\"3a6d5e64-ecf2-4700-98cd-3edd56a1dc34\"}\n[2026-04-27 10:28:14] local.INFO: [EncryptedTokenManager] Generating access token. {\"mode\":\"legacy\"} {\"correlation_id\":\"5fb3c8c8-3160-4964-ac56-02d6e2cb998c\",\"trace_id\":\"3a6d5e64-ecf2-4700-98cd-3edd56a1dc34\"}\n[2026-04-27 10:28:14] local.INFO: [CrmOwnerResolver] Integration owner matched as CRM Owner {\"crm_provider\":\"salesforce\",\"crm_owner\":143,\"team_id\":1} {\"correlation_id\":\"5fb3c8c8-3160-4964-ac56-02d6e2cb998c\",\"trace_id\":\"3a6d5e64-ecf2-4700-98cd-3edd56a1dc34\"}\n[2026-04-27 10:28:14] local.INFO: [SocialAccountService] Fetching token {\"socialAccountId\":1354,\"provider\":\"google\"} {\"correlation_id\":\"5fb3c8c8-3160-4964-ac56-02d6e2cb998c\",\"trace_id\":\"3a6d5e64-ecf2-4700-98cd-3edd56a1dc34\"}\n[2026-04-27 10:28:14] local.INFO: [SocialAccountService] Token retrieved {\"socialAccountId\":1354,\"provider\":\"google\"} {\"correlation_id\":\"5fb3c8c8-3160-4964-ac56-02d6e2cb998c\",\"trace_id\":\"3a6d5e64-ecf2-4700-98cd-3edd56a1dc34\"}\n[2026-04-27 10:28:14] local.INFO: [EncryptedTokenManager] Generating access token. {\"mode\":\"legacy\"} {\"correlation_id\":\"5fb3c8c8-3160-4964-ac56-02d6e2cb998c\",\"trace_id\":\"3a6d5e64-ecf2-4700-98cd-3edd56a1dc34\"}\n[2026-04-27 10:28:15] local.INFO: Processing an email from inbox batch {\"batch\":98446,\"inbox_id\":212,\"email\":\"lukas.kovalik@jiminny.com\",\"email_id\":\"19dce788ac964bbf\",\"from\":\"Nikolay Yankov <notifications@github.com>\",\"to\":\"\\\"jiminny/app\\\" <app@noreply.github.com>\",\"cc\":\"Push <push@noreply.github.com>\"} {\"correlation_id\":\"5fb3c8c8-3160-4964-ac56-02d6e2cb998c\",\"trace_id\":\"3a6d5e64-ecf2-4700-98cd-3edd56a1dc34\"}\n[2026-04-27 10:28:15] local.INFO: [SocialAccountService] Fetching token {\"socialAccountId\":1500,\"provider\":\"salesforce\"} {\"correlation_id\":\"5fb3c8c8-3160-4964-ac56-02d6e2cb998c\",\"trace_id\":\"3a6d5e64-ecf2-4700-98cd-3edd56a1dc34\"}\n[2026-04-27 10:28:15] local.INFO: [SocialAccountService] Token retrieved {\"socialAccountId\":1500,\"provider\":\"salesforce\"} {\"correlation_id\":\"5fb3c8c8-3160-4964-ac56-02d6e2cb998c\",\"trace_id\":\"3a6d5e64-ecf2-4700-98cd-3edd56a1dc34\"}\n[2026-04-27 10:28:15] local.INFO: [EncryptedTokenManager] Generating access token. {\"mode\":\"legacy\"} {\"correlation_id\":\"5fb3c8c8-3160-4964-ac56-02d6e2cb998c\",\"trace_id\":\"3a6d5e64-ecf2-4700-98cd-3edd56a1dc34\"}\n[2026-04-27 10:28:15] local.INFO: [CrmOwnerResolver] Integration owner matched as CRM Owner {\"crm_provider\":\"salesforce\",\"crm_owner\":143,\"team_id\":1} {\"correlation_id\":\"5fb3c8c8-3160-4964-ac56-02d6e2cb998c\",\"trace_id\":\"3a6d5e64-ecf2-4700-98cd-3edd56a1dc34\"}\n[2026-04-27 10:28:15] local.INFO: [EmailImport\\ParticipantsResolver] The sender email is blacklisted, skipping {\"email\":\"notifications@github.com\",\"inbox_id\":212,\"message_provider_id\":\"19dce788ac964bbf\",\"team_id\":1} {\"correlation_id\":\"5fb3c8c8-3160-4964-ac56-02d6e2cb998c\",\"trace_id\":\"3a6d5e64-ecf2-4700-98cd-3edd56a1dc34\"}\n[2026-04-27 10:28:15] local.INFO: [EmailImport\\ParticipantsValidator] Email participants are less than 2 {\"inbox_id\":212,\"message_provider_id\":\"19dce788ac964bbf\",\"message_id\":\"<jiminny/app/pull/11998/before/2b8a97d6be8b053441d5a845b72dd04803cbb53c/after/8a68a94d39cc76ae546e19f354e8b0dabd577314@github.com>\"} {\"correlation_id\":\"5fb3c8c8-3160-4964-ac56-02d6e2cb998c\",\"trace_id\":\"3a6d5e64-ecf2-4700-98cd-3edd56a1dc34\"}\n[2026-04-27 10:28:15] local.INFO: Processing an email from inbox batch {\"batch\":98446,\"inbox_id\":212,\"email\":\"lukas.kovalik@jiminny.com\",\"email_id\":\"19dce7883f2fea19\",\"from\":\"The Jiminny Team <no-reply@dev.jiminny.com>\",\"to\":\"lukas.kovalik@jiminny.com\",\"cc\":null} {\"correlation_id\":\"5fb3c8c8-3160-4964-ac56-02d6e2cb998c\",\"trace_id\":\"3a6d5e64-ecf2-4700-98cd-3edd56a1dc34\"}\n[2026-04-27 10:28:15] local.INFO: [SocialAccountService] Fetching token {\"socialAccountId\":1500,\"provider\":\"salesforce\"} {\"correlation_id\":\"5fb3c8c8-3160-4964-ac56-02d6e2cb998c\",\"trace_id\":\"3a6d5e64-ecf2-4700-98cd-3edd56a1dc34\"}\n[2026-04-27 10:28:15] local.INFO: [SocialAccountService] Token retrieved {\"socialAccountId\":1500,\"provider\":\"salesforce\"} {\"correlation_id\":\"5fb3c8c8-3160-4964-ac56-02d6e2cb998c\",\"trace_id\":\"3a6d5e64-ecf2-4700-98cd-3edd56a1dc34\"}\n[2026-04-27 10:28:15] local.INFO: [EncryptedTokenManager] Generating access token. {\"mode\":\"legacy\"} {\"correlation_id\":\"5fb3c8c8-3160-4964-ac56-02d6e2cb998c\",\"trace_id\":\"3a6d5e64-ecf2-4700-98cd-3edd56a1dc34\"}\n[2026-04-27 10:28:15] local.INFO: [CrmOwnerResolver] Integration owner matched as CRM Owner {\"crm_provider\":\"salesforce\",\"crm_owner\":143,\"team_id\":1} {\"correlation_id\":\"5fb3c8c8-3160-4964-ac56-02d6e2cb998c\",\"trace_id\":\"3a6d5e64-ecf2-4700-98cd-3edd56a1dc34\"}\n[2026-04-27 10:28:15] local.INFO: [EmailImport\\ParticipantsResolver] The sender email is blacklisted, skipping {\"email\":\"no-reply@dev.jiminny.com\",\"inbox_id\":212,\"message_provider_id\":\"19dce7883f2fea19\",\"team_id\":1} {\"correlation_id\":\"5fb3c8c8-3160-4964-ac56-02d6e2cb998c\",\"trace_id\":\"3a6d5e64-ecf2-4700-98cd-3edd56a1dc34\"}\n[2026-04-27 10:28:15] local.INFO: [EmailImport\\ParticipantsValidator] Email participants are less than 2 {\"inbox_id\":212,\"message_provider_id\":\"19dce7883f2fea19\",\"message_id\":\"<d70689e8-7b14-42ac-a3bf-5f1e81da943e@mtasv.net>\"} {\"correlation_id\":\"5fb3c8c8-3160-4964-ac56-02d6e2cb998c\",\"trace_id\":\"3a6d5e64-ecf2-4700-98cd-3edd56a1dc34\"}\n[2026-04-27 10:28:15] local.INFO: Processing an email from inbox batch {\"batch\":98446,\"inbox_id\":212,\"email\":\"lukas.kovalik@jiminny.com\",\"email_id\":\"19dce761cb049fba\",\"from\":\"The Jiminny Team <no-reply@dev.jiminny.com>\",\"to\":\"lukas.kovalik@jiminny.com\",\"cc\":null} {\"correlation_id\":\"5fb3c8c8-3160-4964-ac56-02d6e2cb998c\",\"trace_id\":\"3a6d5e64-ecf2-4700-98cd-3edd56a1dc34\"}\n[2026-04-27 10:28:15] local.INFO: [SocialAccountService] Fetching token {\"socialAccountId\":1500,\"provider\":\"salesforce\"} {\"correlation_id\":\"5fb3c8c8-3160-4964-ac56-02d6e2cb998c\",\"trace_id\":\"3a6d5e64-ecf2-4700-98cd-3edd56a1dc34\"}\n[2026-04-27 10:28:15] local.INFO: [SocialAccountService] Token retrieved {\"socialAccountId\":1500,\"provider\":\"salesforce\"} {\"correlation_id\":\"5fb3c8c8-3160-4964-ac56-02d6e2cb998c\",\"trace_id\":\"3a6d5e64-ecf2-4700-98cd-3edd56a1dc34\"}\n[2026-04-27 10:28:15] local.INFO: [EncryptedTokenManager] Generating access token. {\"mode\":\"legacy\"} {\"correlation_id\":\"5fb3c8c8-3160-4964-ac56-02d6e2cb998c\",\"trace_id\":\"3a6d5e64-ecf2-4700-98cd-3edd56a1dc34\"}\n[2026-04-27 10:28:15] local.INFO: [CrmOwnerResolver] Integration owner matched as CRM Owner {\"crm_provider\":\"salesforce\",\"crm_owner\":143,\"team_id\":1} {\"correlation_id\":\"5fb3c8c8-3160-4964-ac56-02d6e2cb998c\",\"trace_id\":\"3a6d5e64-ecf2-4700-98cd-3edd56a1dc34\"}\n[2026-04-27 10:28:15] local.INFO: [EmailImport\\ParticipantsResolver] The sender email is blacklisted, skipping {\"email\":\"no-reply@dev.jiminny.com\",\"inbox_id\":212,\"message_provider_id\":\"19dce761cb049fba\",\"team_id\":1} {\"correlation_id\":\"5fb3c8c8-3160-4964-ac56-02d6e2cb998c\",\"trace_id\":\"3a6d5e64-ecf2-4700-98cd-3edd56a1dc34\"}\n[2026-04-27 10:28:15] local.INFO: [EmailImport\\ParticipantsValidator] Email participants are less than 2 {\"inbox_id\":212,\"message_provider_id\":\"19dce761cb049fba\",\"message_id\":\"<017f0e66-9989-446e-9788-7ae83a99137e@mtasv.net>\"} {\"correlation_id\":\"5fb3c8c8-3160-4964-ac56-02d6e2cb998c\",\"trace_id\":\"3a6d5e64-ecf2-4700-98cd-3edd56a1dc34\"}\n[2026-04-27 10:28:15] local.INFO: Processing an email from inbox batch {\"batch\":98446,\"inbox_id\":212,\"email\":\"lukas.kovalik@jiminny.com\",\"email_id\":\"19dce74ed398d2c1\",\"from\":\"\\\"sonarqubecloud[bot]\\\" <notifications@github.com>\",\"to\":\"\\\"jiminny/app\\\" <app@noreply.github.com>\",\"cc\":\"Lukas Kovalik <kovaliklukas@gmail.com>, Author <author@noreply.github.com>\"} {\"correlation_id\":\"5fb3c8c8-3160-4964-ac56-02d6e2cb998c\",\"trace_id\":\"3a6d5e64-ecf2-4700-98cd-3edd56a1dc34\"}\n[2026-04-27 10:28:15] local.INFO: [SocialAccountService] Fetching token {\"socialAccountId\":1500,\"provider\":\"salesforce\"} {\"correlation_id\":\"5fb3c8c8-3160-4964-ac56-02d6e2cb998c\",\"trace_id\":\"3a6d5e64-ecf2-4700-98cd-3edd56a1dc34\"}\n[2026-04-27 10:28:15] local.INFO: [SocialAccountService] Token retrieved {\"socialAccountId\":1500,\"provider\":\"salesforce\"} {\"correlation_id\":\"5fb3c8c8-3160-4964-ac56-02d6e2cb998c\",\"trace_id\":\"3a6d5e64-ecf2-4700-98cd-3edd56a1dc34\"}\n[2026-04-27 10:28:15] local.INFO: [EncryptedTokenManager] Generating access token. {\"mode\":\"legacy\"} {\"correlation_id\":\"5fb3c8c8-3160-4964-ac56-02d6e2cb998c\",\"trace_id\":\"3a6d5e64-ecf2-4700-98cd-3edd56a1dc34\"}\n[2026-04-27 10:28:15] local.INFO: [CrmOwnerResolver] Integration owner matched as CRM Owner {\"crm_provider\":\"salesforce\",\"crm_owner\":143,\"team_id\":1} {\"correlation_id\":\"5fb3c8c8-3160-4964-ac56-02d6e2cb998c\",\"trace_id\":\"3a6d5e64-ecf2-4700-98cd-3edd56a1dc34\"}\n[2026-04-27 10:28:15] local.INFO: [EmailImport\\ParticipantsResolver] The sender email is blacklisted, skipping {\"email\":\"notifications@github.com\",\"inbox_id\":212,\"message_provider_id\":\"19dce74ed398d2c1\",\"team_id\":1} {\"correlation_id\":\"5fb3c8c8-3160-4964-ac56-02d6e2cb998c\",\"trace_id\":\"3a6d5e64-ecf2-4700-98cd-3edd56a1dc34\"}\n[2026-04-27 10:28:15] local.INFO: [EmailImport\\ParticipantsValidator] Email participants are less than 2 {\"inbox_id\":212,\"message_provider_id\":\"19dce74ed398d2c1\",\"message_id\":\"<jiminny/app/pull/12016/c4326142661@github.com>\"} {\"correlation_id\":\"5fb3c8c8-3160-4964-ac56-02d6e2cb998c\",\"trace_id\":\"3a6d5e64-ecf2-4700-98cd-3edd56a1dc34\"}\n[2026-04-27 10:28:15] local.INFO: Processing an email from inbox batch {\"batch\":98446,\"inbox_id\":212,\"email\":\"lukas.kovalik@jiminny.com\",\"email_id\":\"19dce713dbecf41f\",\"from\":\"\\\"sonarqubecloud[bot]\\\" <notifications@github.com>\",\"to\":\"\\\"jiminny/infrastructure\\\" <infrastructure@noreply.github.com>\",\"cc\":\"Subscribed <subscribed@noreply.github.com>\"} {\"correlation_id\":\"5fb3c8c8-3160-4964-ac56-02d6e2cb998c\",\"trace_id\":\"3a6d5e64-ecf2-4700-98cd-3edd56a1dc34\"}\n[2026-04-27 10:28:15] local.INFO: [SocialAccountService] Fetching token {\"socialAccountId\":1500,\"provider\":\"salesforce\"} {\"correlation_id\":\"5fb3c8c8-3160-4964-ac56-02d6e2cb998c\",\"trace_id\":\"3a6d5e64-ecf2-4700-98cd-3edd56a1dc34\"}\n[2026-04-27 10:28:15] local.INFO: [SocialAccountService] Token retrieved {\"socialAccountId\":1500,\"provider\":\"salesforce\"} {\"correlation_id\":\"5fb3c8c8-3160-4964-ac56-02d6e2cb998c\",\"trace_id\":\"3a6d5e64-ecf2-4700-98cd-3edd56a1dc34\"}\n[2026-04-27 10:28:15] local.INFO: [EncryptedTokenManager] Generating access token. {\"mode\":\"legacy\"} {\"correlation_id\":\"5fb3c8c8-3160-4964-ac56-02d6e2cb998c\",\"trace_id\":\"3a6d5e64-ecf2-4700-98cd-3edd56a1dc34\"}\n[2026-04-27 10:28:15] local.INFO: [CrmOwnerResolver] Integration owner matched as CRM Owner {\"crm_provider\":\"salesforce\",\"crm_owner\":143,\"team_id\":1} {\"correlation_id\":\"5fb3c8c8-3160-4964-ac56-02d6e2cb998c\",\"trace_id\":\"3a6d5e64-ecf2-4700-98cd-3edd56a1dc34\"}\n[2026-04-27 10:28:15] local.INFO: [EmailImport\\ParticipantsResolver] The sender email is blacklisted, skipping {\"email\":\"notifications@github.com\",\"inbox_id\":212,\"message_provider_id\":\"19dce713dbecf41f\",\"team_id\":1} {\"correlation_id\":\"5fb3c8c8-3160-4964-ac56-02d6e2cb998c\",\"trace_id\":\"3a6d5e64-ecf2-4700-98cd-3edd56a1dc34\"}\n[2026-04-27 10:28:15] local.INFO: [EmailImport\\ParticipantsValidator] Email participants are less than 2 {\"inbox_id\":212,\"message_provider_id\":\"19dce713dbecf41f\",\"message_id\":\"<jiminny/infrastructure/pull/725/c4326108250@github.com>\"} {\"correlation_id\":\"5fb3c8c8-3160-4964-ac56-02d6e2cb998c\",\"trace_id\":\"3a6d5e64-ecf2-4700-98cd-3edd56a1dc34\"}\n[2026-04-27 10:28:15] local.INFO: Processing an email from inbox batch {\"batch\":98446,\"inbox_id\":212,\"email\":\"lukas.kovalik@jiminny.com\",\"email_id\":\"19dce708ed8b5acb\",\"from\":\"Veselin Kulov <notifications@github.com>\",\"to\":\"\\\"jiminny/infrastructure\\\" <infrastructure@noreply.github.com>\",\"cc\":\"Push <push@noreply.github.com>\"} {\"correlation_id\":\"5fb3c8c8-3160-4964-ac56-02d6e2cb998c\",\"trace_id\":\"3a6d5e64-ecf2-4700-98cd-3edd56a1dc34\"}\n[2026-04-27 10:28:15] local.INFO: [SocialAccountService] Fetching token {\"socialAccountId\":1500,\"provider\":\"salesforce\"} {\"correlation_id\":\"5fb3c8c8-3160-4964-ac56-02d6e2cb998c\",\"trace_id\":\"3a6d5e64-ecf2-4700-98cd-3edd56a1dc34\"}\n[2026-04-27 10:28:15] local.INFO: [SocialAccountService] Token retrieved {\"socialAccountId\":1500,\"provider\":\"salesforce\"} {\"correlation_id\":\"5fb3c8c8-3160-4964-ac56-02d6e2cb998c\",\"trace_id\":\"3a6d5e64-ecf2-4700-98cd-3edd56a1dc34\"}\n[2026-04-27 10:28:15] local.INFO: [EncryptedTokenManager] Generating access token. {\"mode\":\"legacy\"} {\"correlation_id\":\"5fb3c8c8-3160-4964-ac56-02d6e2cb998c\",\"trace_id\":\"3a6d5e64-ecf2-4700-98cd-3edd56a1dc34\"}\n[2026-04-27 10:28:15] local.INFO: [CrmOwnerResolver] Integration owner matched as CRM Owner {\"crm_provider\":\"salesforce\",\"crm_owner\":143,\"team_id\":1} {\"correlation_id\":\"5fb3c8c8-3160-4964-ac56-02d6e2cb998c\",\"trace_id\":\"3a6d5e64-ecf2-4700-98cd-3edd56a1dc34\"}\n[2026-04-27 10:28:15] local.INFO: [EmailImport\\ParticipantsResolver] The sender email is blacklisted, skipping {\"email\":\"notifications@github.com\",\"inbox_id\":212,\"message_provider_id\":\"19dce708ed8b5acb\",\"team_id\":1} {\"correlation_id\":\"5fb3c8c8-3160-4964-ac56-02d6e2cb998c\",\"trace_id\":\"3a6d5e64-ecf2-4700-98cd-3edd56a1dc34\"}\n[2026-04-27 10:28:15] local.INFO: [EmailImport\\ParticipantsValidator] Email participants are less than 2 {\"inbox_id\":212,\"message_provider_id\":\"19dce708ed8b5acb\",\"message_id\":\"<jiminny/infrastructure/pull/725/before/186952cc584428fcca17aa364f6cc535d65e804a/after/078246a06b25fb93ad61c7f14472ecb755483f4d@github.com>\"} {\"correlation_id\":\"5fb3c8c8-3160-4964-ac56-02d6e2cb998c\",\"trace_id\":\"3a6d5e64-ecf2-4700-98cd-3edd56a1dc34\"}\n[2026-04-27 10:28:15] local.INFO: Processing an email from inbox batch {\"batch\":98446,\"inbox_id\":212,\"email\":\"lukas.kovalik@jiminny.com\",\"email_id\":\"19dce6eea657f2fa\",\"from\":\"\\\"sonarqubecloud[bot]\\\" <notifications@github.com>\",\"to\":\"\\\"jiminny/app\\\" <app@noreply.github.com>\",\"cc\":\"Subscribed <subscribed@noreply.github.com>\"} {\"correlation_id\":\"5fb3c8c8-3160-4964-ac56-02d6e2cb998c\",\"trace_id\":\"3a6d5e64-ecf2-4700-98cd-3edd56a1dc34\"}\n[2026-04-27 10:28:15] local.INFO: [SocialAccountService] Fetching token {\"socialAccountId\":1500,\"provider\":\"salesforce\"} {\"correlation_id\":\"5fb3c8c8-3160-4964-ac56-02d6e2cb998c\",\"trace_id\":\"3a6d5e64-ecf2-4700-98cd-3edd56a1dc34\"}\n[2026-04-27 10:28:15] local.INFO: [SocialAccountService] Token retrieved {\"socialAccountId\":1500,\"provider\":\"salesforce\"} {\"correlation_id\":\"5fb3c8c8-3160-4964-ac56-02d6e2cb998c\",\"trace_id\":\"3a6d5e64-ecf2-4700-98cd-3edd56a1dc34\"}\n[2026-04-27 10:28:15] local.INFO: [EncryptedTokenManager] Generating access token. {\"mode\":\"legacy\"} {\"correlation_id\":\"5fb3c8c8-3160-4964-ac56-02d6e2cb998c\",\"trace_id\":\"3a6d5e64-ecf2-4700-98cd-3edd56a1dc34\"}\n[2026-04-27 10:28:15] local.INFO: [CrmOwnerResolver] Integration owner matched as CRM Owner {\"crm_provider\":\"salesforce\",\"crm_owner\":143,\"team_id\":1} {\"correlation_id\":\"5fb3c8c8-3160-4964-ac56-02d6e2cb998c\",\"trace_id\":\"3a6d5e64-ecf2-4700-98cd-3edd56a1dc34\"}\n[2026-04-27 10:28:15] local.INFO: [EmailImport\\ParticipantsResolver] The sender email is blacklisted, skipping {\"email\":\"notifications@github.com\",\"inbox_id\":212,\"message_provider_id\":\"19dce6eea657f2fa\",\"team_id\":1} {\"correlation_id\":\"5fb3c8c8-3160-4964-ac56-02d6e2cb998c\",\"trace_id\":\"3a6d5e64-ecf2-4700-98cd-3edd56a1dc34\"}\n[2026-04-27 10:28:15] local.INFO: [EmailImport\\ParticipantsValidator] Email participants are less than 2 {\"inbox_id\":212,\"message_provider_id\":\"19dce6eea657f2fa\",\"message_id\":\"<jiminny/app/pull/11998/c4326088534@github.com>\"} {\"correlation_id\":\"5fb3c8c8-3160-4964-ac56-02d6e2cb998c\",\"trace_id\":\"3a6d5e64-ecf2-4700-98cd-3edd56a1dc34\"}\n[2026-04-27 10:28:15] local.INFO: Deleting successfully processed batch 98446 for inbox 212 {\"correlation_id\":\"5fb3c8c8-3160-4964-ac56-02d6e2cb998c\",\"trace_id\":\"3a6d5e64-ecf2-4700-98cd-3edd56a1dc34\"}\n[2026-04-27 10:28:15] local.INFO: [EmailSchedule] FINISHED batch process {\"host\":\"docker_lamp_1\",\"processed\":1} {\"correlation_id\":\"5fb3c8c8-3160-4964-ac56-02d6e2cb998c\",\"trace_id\":\"3a6d5e64-ecf2-4700-98cd-3edd56a1dc34\"}\n[2026-04-27 10:28:15] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage for command {\"command\":\"mailbox:batch:process\",\"memoryBeforeCommandInMb\":60.0,\"memoryAfterCommandInMB\":62.0,\"memoryPeakBeforeCommandInMb\":99.727,\"memoryPeakAfterCommandInMB\":99.727} {\"correlation_id\":\"5fb3c8c8-3160-4964-ac56-02d6e2cb998c\",\"trace_id\":\"3a6d5e64-ecf2-4700-98cd-3edd56a1dc34\"}\n[2026-04-27 10:28:16] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage before starting command {\"command\":\"conference:monitor:count\",\"memoryBeforeCommandInMb\":60.0,\"memoryPeakBeforeCommandInMb\":99.727} {\"correlation_id\":\"ef5b3e1a-a06a-4527-9120-b2adfe4e1846\",\"trace_id\":\"d932d64a-3f63-4aca-8abf-bb13e0892cba\"}\n[2026-04-27 10:28:16] local.INFO: Running conference:monitor:count command for activities in (2026-04-27 10:26:00, 2026-04-27 10:28:00] {\"correlation_id\":\"ef5b3e1a-a06a-4527-9120-b2adfe4e1846\",\"trace_id\":\"d932d64a-3f63-4aca-8abf-bb13e0892cba\"}\n[2026-04-27 10:28:16] local.INFO: [conference:monitor:count] No activities found in (2026-04-27 10:26:00, 2026-04-27 10:28:00] {\"correlation_id\":\"ef5b3e1a-a06a-4527-9120-b2adfe4e1846\",\"trace_id\":\"d932d64a-3f63-4aca-8abf-bb13e0892cba\"}\n[2026-04-27 10:28:16] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage for command {\"command\":\"conference:monitor:count\",\"memoryBeforeCommandInMb\":60.0,\"memoryAfterCommandInMB\":62.0,\"memoryPeakBeforeCommandInMb\":99.727,\"memoryPeakAfterCommandInMB\":99.727} {\"correlation_id\":\"ef5b3e1a-a06a-4527-9120-b2adfe4e1846\",\"trace_id\":\"d932d64a-3f63-4aca-8abf-bb13e0892cba\"}\n[2026-04-27 10:28:17] local.ERROR: Target class [Jiminny\\Repositories\\AjReportsRepository] does not exist. {\"exception\":\"[object] (Illuminate\\\\Contracts\\\\Container\\\\BindingResolutionException(code: 0): Target class [Jiminny\\\\Repositories\\\\AjReportsRepository] does not exist. at /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php:1124)\n[stacktrace]\n#0 /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php(933): Illuminate\\\\Container\\\\Container->build('Jiminny\\\\\\\\Reposit...')\n#1 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Application.php(1078): Illuminate\\\\Container\\\\Container->resolve('Jiminny\\\\\\\\Reposit...', Array, true)\n#2 /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php(864): Illuminate\\\\Foundation\\\\Application->resolve('Jiminny\\\\\\\\Reposit...', Array)\n#3 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Application.php(1058): Illuminate\\\\Container\\\\Container->make('Jiminny\\\\\\\\Reposit...', Array)\n#4 /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php(1336): Illuminate\\\\Foundation\\\\Application->make('Jiminny\\\\\\\\Reposit...')\n#5 /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php(1237): Illuminate\\\\Container\\\\Container->resolveClass(Object(ReflectionParameter))\n#6 /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php(1162): Illuminate\\\\Container\\\\Container->resolveDependencies(Array)\n#7 /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php(933): Illuminate\\\\Container\\\\Container->build('Jiminny\\\\\\\\Http\\\\\\\\Co...')\n#8 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Application.php(1078): Illuminate\\\\Container\\\\Container->resolve('Jiminny\\\\\\\\Http\\\\\\\\Co...', Array, true)\n#9 /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php(864): Illuminate\\\\Foundation\\\\Application->resolve('Jiminny\\\\\\\\Http\\\\\\\\Co...', Array)\n#10 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Application.php(1058): Illuminate\\\\Container\\\\Container->make('Jiminny\\\\\\\\Http\\\\\\\\Co...', Array)\n#11 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Route.php(286): Illuminate\\\\Foundation\\\\Application->make('Jiminny\\\\\\\\Http\\\\\\\\Co...')\n#12 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Route.php(1133): Illuminate\\\\Routing\\\\Route->getController()\n#13 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Route.php(1062): Illuminate\\\\Routing\\\\Route->controllerMiddleware()\n#14 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Router.php(834): Illuminate\\\\Routing\\\\Route->gatherMiddleware()\n#15 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Router.php(816): Illuminate\\\\Routing\\\\Router->gatherRouteMiddleware(Object(Illuminate\\\\Routing\\\\Route))\n#16 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Router.php(800): Illuminate\\\\Routing\\\\Router->runRouteWithinStack(Object(Illuminate\\\\Routing\\\\Route), Object(Illuminate\\\\Http\\\\Request))\n#17 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Router.php(764): Illuminate\\\\Routing\\\\Router->runRoute(Object(Illuminate\\\\Http\\\\Request), Object(Illuminate\\\\Routing\\\\Route))\n#18 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Router.php(753): Illuminate\\\\Routing\\\\Router->dispatchToRoute(Object(Illuminate\\\\Http\\\\Request))\n#19 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Http/Kernel.php(200): Illuminate\\\\Routing\\\\Router->dispatch(Object(Illuminate\\\\Http\\\\Request))\n#20 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(180): Illuminate\\\\Foundation\\\\Http\\\\Kernel->Illuminate\\\\Foundation\\\\Http\\\\{closure}(Object(Illuminate\\\\Http\\\\Request))\n#21 /home/jiminny/vendor/barryvdh/laravel-debugbar/src/Middleware/InjectDebugbar.php(59): Illuminate\\\\Pipeline\\\\Pipeline->Illuminate\\\\Pipeline\\\\{closure}(Object(Illuminate\\\\Http\\\\Request))\n#22 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(219): Barryvdh\\\\Debugbar\\\\Middleware\\\\InjectDebugbar->handle(Object(Illuminate\\\\Http\\\\Request), Object(Closure))\n#23 /home/jiminny/vendor/laravel/framework/src/Illuminate/Http/Middleware/HandleCors.php(74): Illuminate\\\\Pipeline\\\\Pipeline->Illuminate\\\\Pipeline\\\\{closure}(Object(Illuminate\\\\Http\\\\Request))\n#24 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(219): Illuminate\\\\Http\\\\Middleware\\\\HandleCors->handle(Object(Illuminate\\\\Http\\\\Request), Object(Closure))\n#25 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Http/Middleware/TransformsRequest.php(21): Illuminate\\\\Pipeline\\\\Pipeline->Illuminate\\\\Pipeline\\\\{closure}(Object(Illuminate\\\\Http\\\\Request))\n#26 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Http/Middleware/TrimStrings.php(51): Illuminate\\\\Foundation\\\\Http\\\\Middleware\\\\TransformsRequest->handle(Object(Illuminate\\\\Http\\\\Request), Object(Closure))\n#27 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(219): Illuminate\\\\Foundation\\\\Http\\\\Middleware\\\\TrimStrings->handle(Object(Illuminate\\\\Http\\\\Request), Object(Closure))\n#28 /home/jiminny/vendor/bepsvpt/secure-headers/src/SecureHeadersMiddleware.php(18): Illuminate\\\\Pipeline\\\\Pipeline->Illuminate\\\\Pipeline\\\\{closure}(Object(Illuminate\\\\Http\\\\Request))\n#29 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(219): Bepsvpt\\\\SecureHeaders\\\\SecureHeadersMiddleware->handle(Object(Illuminate\\\\Http\\\\Request), Object(Closure))\n#30 /home/jiminny/app/Http/Middleware/SentryContext.php(60): Illuminate\\\\Pipeline\\\\Pipeline->Illuminate\\\\Pipeline\\\\{closure}(Object(Illuminate\\\\Http\\\\Request))\n#31 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(219): Jiminny\\\\Http\\\\Middleware\\\\SentryContext->handle(Object(Illuminate\\\\Http\\\\Request), Object(Closure))\n#32 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Http/Middleware/PreventRequestsDuringMaintenance.php(109): Illuminate\\\\Pipeline\\\\Pipeline->Illuminate\\\\Pipeline\\\\{closure}(Object(Illuminate\\\\Http\\\\Request))\n#33 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(219): Illuminate\\\\Foundation\\\\Http\\\\Middleware\\\\PreventRequestsDuringMaintenance->handle(Object(Illuminate\\\\Http\\\\Request), Object(Closure))\n#34 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Http/Middleware/InvokeDeferredCallbacks.php(22): Illuminate\\\\Pipeline\\\\Pipeline->Illuminate\\\\Pipeline\\\\{closure}(Object(Illuminate\\\\Http\\\\Request))\n#35 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(219): Illuminate\\\\Foundation\\\\Http\\\\Middleware\\\\InvokeDeferredCallbacks->handle(Object(Illuminate\\\\Http\\\\Request), Object(Closure))\n#36 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(137): Illuminate\\\\Pipeline\\\\Pipeline->Illuminate\\\\Pipeline\\\\{closure}(Object(Illuminate\\\\Http\\\\Request))\n#37 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Http/Kernel.php(175): Illuminate\\\\Pipeline\\\\Pipeline->then(Object(Closure))\n#38 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Http/Kernel.php(144): Illuminate\\\\Foundation\\\\Http\\\\Kernel->sendRequestThroughRouter(Object(Illuminate\\\\Http\\\\Request))\n#39 /home/jiminny/public/index.php(51): Illuminate\\\\Foundation\\\\Http\\\\Kernel->handle(Object(Illuminate\\\\Http\\\\Request))\n#40 {main}\n\n[previous exception] [object] (ReflectionException(code: -1): Class \\\"Jiminny\\\\Repositories\\\\AjReportsRepository\\\" does not exist at /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php:1122)\n[stacktrace]\n#0 /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php(1122): ReflectionClass->__construct('Jiminny\\\\\\\\Reposit...')\n#1 /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php(933): Illuminate\\\\Container\\\\Container->build('Jiminny\\\\\\\\Reposit...')\n#2 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Application.php(1078): Illuminate\\\\Container\\\\Container->resolve('Jiminny\\\\\\\\Reposit...', Array, true)\n#3 /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php(864): Illuminate\\\\Foundation\\\\Application->resolve('Jiminny\\\\\\\\Reposit...', Array)\n#4 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Application.php(1058): Illuminate\\\\Container\\\\Container->make('Jiminny\\\\\\\\Reposit...', Array)\n#5 /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php(1336): Illuminate\\\\Foundation\\\\Application->make('Jiminny\\\\\\\\Reposit...')\n#6 /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php(1237): Illuminate\\\\Container\\\\Container->resolveClass(Object(ReflectionParameter))\n#7 /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php(1162): Illuminate\\\\Container\\\\Container->resolveDependencies(Array)\n#8 /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php(933): Illuminate\\\\Container\\\\Container->build('Jiminny\\\\\\\\Http\\\\\\\\Co...')\n#9 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Application.php(1078): Illuminate\\\\Container\\\\Container->resolve('Jiminny\\\\\\\\Http\\\\\\\\Co...', Array, true)\n#10 /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php(864): Illuminate\\\\Foundation\\\\Application->resolve('Jiminny\\\\\\\\Http\\\\\\\\Co...', Array)\n#11 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Application.php(1058): Illuminate\\\\Container\\\\Container->make('Jiminny\\\\\\\\Http\\\\\\\\Co...', Array)\n#12 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Route.php(286): Illuminate\\\\Foundation\\\\Application->make('Jiminny\\\\\\\\Http\\\\\\\\Co...')\n#13 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Route.php(1133): Illuminate\\\\Routing\\\\Route->getController()\n#14 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Route.php(1062): Illuminate\\\\Routing\\\\Route->controllerMiddleware()\n#15 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Router.php(834): Illuminate\\\\Routing\\\\Route->gatherMiddleware()\n#16 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Router.php(816): Illuminate\\\\Routing\\\\Router->gatherRouteMiddleware(Object(Illuminate\\\\Routing\\\\Route))\n#17 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Router.php(800): Illuminate\\\\Routing\\\\Router->runRouteWithinStack(Object(Illuminate\\\\Routing\\\\Route), Object(Illuminate\\\\Http\\\\Request))\n#18 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Router.php(764): Illuminate\\\\Routing\\\\Router->runRoute(Object(Illuminate\\\\Http\\\\Request), Object(Illuminate\\\\Routing\\\\Route))\n#19 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Router.php(753): Illuminate\\\\Routing\\\\Router->dispatchToRoute(Object(Illuminate\\\\Http\\\\Request))\n#20 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Http/Kernel.php(200): Illuminate\\\\Routing\\\\Router->dispatch(Object(Illuminate\\\\Http\\\\Request))\n#21 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(180): Illuminate\\\\Foundation\\\\Http\\\\Kernel->Illuminate\\\\Foundation\\\\Http\\\\{closure}(Object(Illuminate\\\\Http\\\\Request))\n#22 /home/jiminny/vendor/barryvdh/laravel-debugbar/src/Middleware/InjectDebugbar.php(59): Illuminate\\\\Pipeline\\\\Pipeline->Illuminate\\\\Pipeline\\\\{closure}(Object(Illuminate\\\\Http\\\\Request))\n#23 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(219): Barryvdh\\\\Debugbar\\\\Middleware\\\\InjectDebugbar->handle(Object(Illuminate\\\\Http\\\\Request), Object(Closure))\n#24 /home/jiminny/vendor/laravel/framework/src/Illuminate/Http/Middleware/HandleCors.php(74): Illuminate\\\\Pipeline\\\\Pipeline->Illuminate\\\\Pipeline\\\\{closure}(Object(Illuminate\\\\Http\\\\Request))\n#25 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(219): Illuminate\\\\Http\\\\Middleware\\\\HandleCors->handle(Object(Illuminate\\\\Http\\\\Request), Object(Closure))\n#26 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Http/Middleware/TransformsRequest.php(21): Illuminate\\\\Pipeline\\\\Pipeline->Illuminate\\\\Pipeline\\\\{closure}(Object(Illuminate\\\\Http\\\\Request))\n#27 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Http/Middleware/TrimStrings.php(51): Illuminate\\\\Foundation\\\\Http\\\\Middleware\\\\TransformsRequest->handle(Object(Illuminate\\\\Http\\\\Request), Object(Closure))\n#28 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(219): Illuminate\\\\Foundation\\\\Http\\\\Middleware\\\\TrimStrings->handle(Object(Illuminate\\\\Http\\\\Request), Object(Closure))\n#29 /home/jiminny/vendor/bepsvpt/secure-headers/src/SecureHeadersMiddleware.php(18): Illuminate\\\\Pipeline\\\\Pipeline->Illuminate\\\\Pipeline\\\\{closure}(Object(Illuminate\\\\Http\\\\Request))\n#30 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(219): Bepsvpt\\\\SecureHeaders\\\\SecureHeadersMiddleware->handle(Object(Illuminate\\\\Http\\\\Request), Object(Closure))\n#31 /home/jiminny/app/Http/Middleware/SentryContext.php(60): Illuminate\\\\Pipeline\\\\Pipeline->Illuminate\\\\Pipeline\\\\{closure}(Object(Illuminate\\\\Http\\\\Request))\n#32 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(219): Jiminny\\\\Http\\\\Middleware\\\\SentryContext->handle(Object(Illuminate\\\\Http\\\\Request), Object(Closure))\n#33 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Http/Middleware/PreventRequestsDuringMaintenance.php(109): Illuminate\\\\Pipeline\\\\Pipeline->Illuminate\\\\Pipeline\\\\{closure}(Object(Illuminate\\\\Http\\\\Request))\n#34 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(219): Illuminate\\\\Foundation\\\\Http\\\\Middleware\\\\PreventRequestsDuringMaintenance->handle(Object(Illuminate\\\\Http\\\\Request), Object(Closure))\n#35 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Http/Middleware/InvokeDeferredCallbacks.php(22): Illuminate\\\\Pipeline\\\\Pipeline->Illuminate\\\\Pipeline\\\\{closure}(Object(Illuminate\\\\Http\\\\Request))\n#36 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(219): Illuminate\\\\Foundation\\\\Http\\\\Middleware\\\\InvokeDeferredCallbacks->handle(Object(Illuminate\\\\Http\\\\Request), Object(Closure))\n#37 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(137): Illuminate\\\\Pipeline\\\\Pipeline->Illuminate\\\\Pipeline\\\\{closure}(Object(Illuminate\\\\Http\\\\Request))\n#38 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Http/Kernel.php(175): Illuminate\\\\Pipeline\\\\Pipeline->then(Object(Closure))\n#39 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Http/Kernel.php(144): Illuminate\\\\Foundation\\\\Http\\\\Kernel->sendRequestThroughRouter(Object(Illuminate\\\\Http\\\\Request))\n#40 /home/jiminny/public/index.php(51): Illuminate\\\\Foundation\\\\Http\\\\Kernel->handle(Object(Illuminate\\\\Http\\\\Request))\n#41 {main}\n\"} {\"correlation_id\":\"46b082fc-5a28-4cc4-8bd1-c670f8662e7f\",\"trace_id\":\"8ae7dc26-61c7-42e4-8617-0b85f53e54c1\"}\n[2026-04-27 10:28:19] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage before starting command {\"command\":\"calendar:sync\",\"memoryBeforeCommandInMb\":60.0,\"memoryPeakBeforeCommandInMb\":99.727} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:19] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage before starting command {\"command\":\"mailbox:batch:retry-failed\",\"memoryBeforeCommandInMb\":60.0,\"memoryPeakBeforeCommandInMb\":99.727} {\"correlation_id\":\"e155562a-84a1-45f5-8897-1779f96886f6\",\"trace_id\":\"12141267-1032-479b-aab5-e17e133007d9\"}\n[2026-04-27 10:28:19] local.NOTICE: Calendar sync start {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:19] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage for command {\"command\":\"mailbox:batch:retry-failed\",\"memoryBeforeCommandInMb\":60.0,\"memoryAfterCommandInMB\":62.0,\"memoryPeakBeforeCommandInMb\":99.727,\"memoryPeakAfterCommandInMB\":99.727} {\"correlation_id\":\"e155562a-84a1-45f5-8897-1779f96886f6\",\"trace_id\":\"12141267-1032-479b-aab5-e17e133007d9\"}\n[2026-04-27 10:28:19] local.INFO: [SocialAccountService] Fetching token {\"socialAccountId\":1393,\"provider\":\"google\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:19] local.INFO: [SocialAccountService] Token needs refreshing {\"socialAccountId\":1393,\"provider\":\"google\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:19] local.INFO: [EncryptedTokenManager] Generating access token. {\"mode\":\"legacy\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:19] local.INFO: [SocialAccountService] Refreshing token from provider {\"socialAccountId\":1393,\"provider\":\"google\",\"refreshToken\":\"5aa7e2d96b53201cd16fca5d2e4ef3ad03320971fc064781d18aee3ae7b99fbf\",\"state\":\"full-refresh\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:20] local.ERROR: [SocialAccountService] Failed to refresh token {\"socialAccountId\":1393,\"provider\":\"google\",\"responseBody\":{\"error\":\"invalid_grant\",\"error_description\":\"Account has been deleted\"}} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:20] local.INFO: [SocialAccountObserver] Saving model {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:20] local.ERROR: [SocialAccountService] Failed to refresh token {\"socialAccountId\":1393,\"provider\":\"google\",\"reason\":\"Flow refresh required.\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:20] local.INFO: [SocialAccountService] Fetching token {\"socialAccountId\":1387,\"provider\":\"google\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:20] local.INFO: [SocialAccountService] Token needs refreshing {\"socialAccountId\":1387,\"provider\":\"google\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:20] local.INFO: [EncryptedTokenManager] Generating access token. {\"mode\":\"legacy\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:20] local.INFO: [SocialAccountService] Refreshing token from provider {\"socialAccountId\":1387,\"provider\":\"google\",\"refreshToken\":\"8157ac6de94842937194009e9c50e459253600f799dacf6a40755ffdbeb5bba6\",\"state\":\"full-refresh\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:21] local.ERROR: [SocialAccountService] Failed to refresh token {\"socialAccountId\":1387,\"provider\":\"google\",\"responseBody\":{\"error\":\"invalid_grant\",\"error_description\":\"Account has been deleted\"}} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:21] local.INFO: [SocialAccountObserver] Saving model {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:21] local.ERROR: [SocialAccountService] Failed to refresh token {\"socialAccountId\":1387,\"provider\":\"google\",\"reason\":\"Flow refresh required.\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:21] local.INFO: [SocialAccountService] Fetching token {\"socialAccountId\":1348,\"provider\":\"google\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:21] local.INFO: [SocialAccountService] Token needs refreshing {\"socialAccountId\":1348,\"provider\":\"google\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:21] local.INFO: [EncryptedTokenManager] Generating access token. {\"mode\":\"legacy\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:21] local.INFO: [SocialAccountService] Refreshing token from provider {\"socialAccountId\":1348,\"provider\":\"google\",\"refreshToken\":\"9e7d13d3032d0cb1b79d8e95aef01383e8e91eb52ff8ee960c8a0b6b95cd8c73\",\"state\":\"full-refresh\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:21] local.ERROR: [SocialAccountService] Failed to refresh token {\"socialAccountId\":1348,\"provider\":\"google\",\"responseBody\":{\"error\":\"invalid_grant\",\"error_description\":\"Bad Request\"}} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:21] local.INFO: [SocialAccountObserver] Saving model {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:21] local.ERROR: [SocialAccountService] Failed to refresh token {\"socialAccountId\":1348,\"provider\":\"google\",\"reason\":\"Flow refresh required.\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:21] local.INFO: [SocialAccountService] Fetching token {\"socialAccountId\":1361,\"provider\":\"google\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:21] local.INFO: [SocialAccountService] Token needs refreshing {\"socialAccountId\":1361,\"provider\":\"google\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:21] local.INFO: [EncryptedTokenManager] Generating access token. {\"mode\":\"legacy\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:21] local.INFO: [SocialAccountService] Refreshing token from provider {\"socialAccountId\":1361,\"provider\":\"google\",\"refreshToken\":\"6c843da199c2b9907445329304fcc4ec5057a4ee748d8299641764395c08e1fd\",\"state\":\"full-refresh\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:22] local.ERROR: [SocialAccountService] Failed to refresh token {\"socialAccountId\":1361,\"provider\":\"google\",\"responseBody\":{\"error\":\"invalid_grant\",\"error_description\":\"Account has been deleted\"}} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:22] local.INFO: [SocialAccountObserver] Saving model {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:22] local.ERROR: [SocialAccountService] Failed to refresh token {\"socialAccountId\":1361,\"provider\":\"google\",\"reason\":\"Flow refresh required.\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:22] local.INFO: [SocialAccountService] Fetching token {\"socialAccountId\":1310,\"provider\":\"google\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:22] local.INFO: [SocialAccountService] Token needs refreshing {\"socialAccountId\":1310,\"provider\":\"google\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:22] local.INFO: [EncryptedTokenManager] Generating access token. {\"mode\":\"legacy\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:22] local.INFO: [SocialAccountService] Refreshing token from provider {\"socialAccountId\":1310,\"provider\":\"google\",\"refreshToken\":\"e34818922c2830a660813a63f6169a4a9a992ae2cccd7dc8dd7796cfdb470ef1\",\"state\":\"full-refresh\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:23] local.ERROR: [SocialAccountService] Failed to refresh token {\"socialAccountId\":1310,\"provider\":\"google\",\"responseBody\":{\"error\":\"invalid_grant\",\"error_description\":\"Bad Request\"}} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:23] local.INFO: [SocialAccountObserver] Saving model {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:23] local.ERROR: [SocialAccountService] Failed to refresh token {\"socialAccountId\":1310,\"provider\":\"google\",\"reason\":\"Flow refresh required.\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:23] local.INFO: [SocialAccountService] Fetching token {\"socialAccountId\":1333,\"provider\":\"google\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:23] local.INFO: [SocialAccountService] Token needs refreshing {\"socialAccountId\":1333,\"provider\":\"google\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:23] local.INFO: [EncryptedTokenManager] Generating access token. {\"mode\":\"legacy\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:23] local.INFO: [SocialAccountService] Refreshing token from provider {\"socialAccountId\":1333,\"provider\":\"google\",\"refreshToken\":\"6c902986546d8e8da1dc539b046cdc1d458f519acc972e5b5f1d6a1a295165e0\",\"state\":\"full-refresh\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:23] local.ERROR: [SocialAccountService] Failed to refresh token {\"socialAccountId\":1333,\"provider\":\"google\",\"responseBody\":{\"error\":\"unauthorized_client\",\"error_description\":\"Unauthorized\"}} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:23] local.INFO: [SocialAccountObserver] Saving model {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:23] local.ERROR: [SocialAccountService] Failed to refresh token {\"socialAccountId\":1333,\"provider\":\"google\",\"reason\":\"Flow refresh required.\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:24] local.INFO: [SocialAccountService] Fetching token {\"socialAccountId\":1368,\"provider\":\"google\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:24] local.INFO: [SocialAccountService] Token needs refreshing {\"socialAccountId\":1368,\"provider\":\"google\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:24] local.INFO: [EncryptedTokenManager] Generating access token. {\"mode\":\"legacy\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:24] local.INFO: [SocialAccountService] Refreshing token from provider {\"socialAccountId\":1368,\"provider\":\"google\",\"refreshToken\":\"d2f128898ff8543bd16b69cfae37896ab85119b0f5ed2b431d739593bb600333\",\"state\":\"full-refresh\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:24] local.ERROR: [SocialAccountService] Failed to refresh token {\"socialAccountId\":1368,\"provider\":\"google\",\"responseBody\":{\"error\":\"invalid_grant\",\"error_description\":\"Bad Request\"}} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:24] local.INFO: [SocialAccountObserver] Saving model {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:24] local.ERROR: [SocialAccountService] Failed to refresh token {\"socialAccountId\":1368,\"provider\":\"google\",\"reason\":\"Flow refresh required.\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:24] local.INFO: [SocialAccountService] Fetching token {\"socialAccountId\":1365,\"provider\":\"google\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:24] local.INFO: [SocialAccountService] Token needs refreshing {\"socialAccountId\":1365,\"provider\":\"google\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:24] local.INFO: [EncryptedTokenManager] Generating access token. {\"mode\":\"legacy\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:24] local.INFO: [SocialAccountService] Refreshing token from provider {\"socialAccountId\":1365,\"provider\":\"google\",\"refreshToken\":\"7676e4a9afcd082b413248ab5ec6e487021fec6a9bdf315860a59cefad9caad8\",\"state\":\"full-refresh\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:25] local.ERROR: [SocialAccountService] Failed to refresh token {\"socialAccountId\":1365,\"provider\":\"google\",\"responseBody\":{\"error\":\"unauthorized_client\",\"error_description\":\"Unauthorized\"}} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:25] local.INFO: [SocialAccountObserver] Saving model {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:25] local.ERROR: [SocialAccountService] Failed to refresh token {\"socialAccountId\":1365,\"provider\":\"google\",\"reason\":\"Flow refresh required.\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:25] local.INFO: [SocialAccountService] Fetching token {\"socialAccountId\":1364,\"provider\":\"google\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:25] local.INFO: [SocialAccountService] Token needs refreshing {\"socialAccountId\":1364,\"provider\":\"google\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:25] local.INFO: [EncryptedTokenManager] Generating access token. {\"mode\":\"legacy\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:25] local.INFO: [SocialAccountService] Refreshing token from provider {\"socialAccountId\":1364,\"provider\":\"google\",\"refreshToken\":\"dd5882ebce76e645292ce33ae74238abbb77c0a4ecc6a2bfe723cad82e72ba8e\",\"state\":\"full-refresh\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:25] local.ERROR: [SocialAccountService] Failed to refresh token {\"socialAccountId\":1364,\"provider\":\"google\",\"responseBody\":{\"error\":\"unauthorized_client\",\"error_description\":\"Unauthorized\"}} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:25] local.INFO: [SocialAccountObserver] Saving model {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:25] local.ERROR: [SocialAccountService] Failed to refresh token {\"socialAccountId\":1364,\"provider\":\"google\",\"reason\":\"Flow refresh required.\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:25] local.INFO: [SocialAccountService] Fetching token {\"socialAccountId\":1370,\"provider\":\"office\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:25] local.INFO: [SocialAccountService] Token needs refreshing {\"socialAccountId\":1370,\"provider\":\"office\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:25] local.INFO: [EncryptedTokenManager] Generating access token. {\"mode\":\"legacy\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:25] local.INFO: [SocialAccountService] Refreshing token from provider {\"socialAccountId\":1370,\"provider\":\"office\",\"refreshToken\":\"b7ee8035306d0043cea6e00e7c4fe14f745e44074a1194db62a31cdf8b70af3e\",\"state\":\"full-refresh\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:26] local.ERROR: [SocialAccountService] Failed to refresh token {\"socialAccountId\":1370,\"provider\":\"office\",\"responseBody\":\"{\\\"error\\\":\\\"invalid_client\\\",\\\"error_description\\\":\\\"AADSTS7000215: Invalid client secret provided. Ensure the secret being sent in the request is the client secret value, not the client secret ID, for a secret added to app 'bbcbb2ef-6200-4fae-82bd-d81f5dd738da'. Trace ID: 2be54cfa-1691-4388-a284-754fdf414400 Correlation ID: 9a934049-899b-45c5-99a5-15c9c470d8a2 Timestamp: 2026-04-27 10:28:26Z\\\",\\\"error_codes\\\":[7000215],\\\"timestamp\\\":\\\"2026-04-27 10:28:26Z\\\",\\\"trace_id\\\":\\\"2be54cfa-1691-4388-a284-754fdf414400\\\",\\\"correlation_id\\\":\\\"9a934049-899b-45c5-99a5-15c9c470d8a2\\\",\\\"error_uri\\\":\\\"https://login.microsoftonline.com/error?code=7000215\\\"}\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:26] local.INFO: [SocialAccountObserver] Saving model {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:26] local.ERROR: [SocialAccountService] Failed to refresh token {\"socialAccountId\":1370,\"provider\":\"office\",\"reason\":\"Flow refresh required.\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:26] local.INFO: [SocialAccountService] Fetching token {\"socialAccountId\":1202,\"provider\":\"office\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:26] local.INFO: [SocialAccountService] Token needs refreshing {\"socialAccountId\":1202,\"provider\":\"office\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:26] local.INFO: [EncryptedTokenManager] Generating access token. {\"mode\":\"legacy\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:26] local.INFO: [SocialAccountService] Refreshing token from provider {\"socialAccountId\":1202,\"provider\":\"office\",\"refreshToken\":\"b458799ccc29b21a6e2eb5260fdb63e49ccba21bf942a3973fb63799bd7f0afe\",\"state\":\"full-refresh\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:26] local.ERROR: [SocialAccountService] Failed to refresh token {\"socialAccountId\":1202,\"provider\":\"office\",\"responseBody\":\"{\\\"error\\\":\\\"invalid_client\\\",\\\"error_description\\\":\\\"AADSTS7000215: Invalid client secret provided. Ensure the secret being sent in the request is the client secret value, not the client secret ID, for a secret added to app 'bbcbb2ef-6200-4fae-82bd-d81f5dd738da'. Trace ID: 27729e9a-0943-41bd-9752-4ec9a8444500 Correlation ID: 824a9761-9387-4c53-ae31-5c242b1d4298 Timestamp: 2026-04-27 10:28:26Z\\\",\\\"error_codes\\\":[7000215],\\\"timestamp\\\":\\\"2026-04-27 10:28:26Z\\\",\\\"trace_id\\\":\\\"27729e9a-0943-41bd-9752-4ec9a8444500\\\",\\\"correlation_id\\\":\\\"824a9761-9387-4c53-ae31-5c242b1d4298\\\",\\\"error_uri\\\":\\\"https://login.microsoftonline.com/error?code=7000215\\\"}\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:26] local.INFO: [SocialAccountObserver] Saving model {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:26] local.ERROR: [SocialAccountService] Failed to refresh token {\"socialAccountId\":1202,\"provider\":\"office\",\"reason\":\"Flow refresh required.\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:26] local.INFO: [SocialAccountService] Fetching token {\"socialAccountId\":1502,\"provider\":\"google\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:26] local.INFO: [SocialAccountService] Token needs refreshing {\"socialAccountId\":1502,\"provider\":\"google\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:26] local.INFO: [EncryptedTokenManager] Generating access token. {\"mode\":\"legacy\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:26] local.INFO: [SocialAccountService] Refreshing token from provider {\"socialAccountId\":1502,\"provider\":\"google\",\"refreshToken\":\"d417c92ebaa137295a04675f715d0511ae8acac9d779b102eac50d6300116d3e\",\"state\":\"connected\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:27] local.INFO: [SocialAccountObserver] Saving model {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:27] local.INFO: [SocialAccountObserver] Access token was modified, encrypting {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:27] local.INFO: [SocialAccountService] Token refreshed {\"socialAccountId\":1502,\"provider\":\"google\",\"state\":\"connected\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:27] local.INFO: Calendar sync job dispatched {\"calendar_id\":501} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:27] local.INFO: [SocialAccountService] Fetching token {\"socialAccountId\":1300,\"provider\":\"google\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:27] local.INFO: [SocialAccountService] Token needs refreshing {\"socialAccountId\":1300,\"provider\":\"google\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:27] local.INFO: [EncryptedTokenManager] Generating access token. {\"mode\":\"legacy\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:27] local.INFO: [SocialAccountService] Refreshing token from provider {\"socialAccountId\":1300,\"provider\":\"google\",\"refreshToken\":\"4b811db0725fd9602a95943519a7da935e2a5065da7d9ebfcb170752e3e1ddb8\",\"state\":\"full-refresh\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:27] local.ERROR: [SocialAccountService] Failed to refresh token {\"socialAccountId\":1300,\"provider\":\"google\",\"responseBody\":{\"error\":\"invalid_grant\",\"error_description\":\"Account has been deleted\"}} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:27] local.INFO: [SocialAccountObserver] Saving model {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:27] local.ERROR: [SocialAccountService] Failed to refresh token {\"socialAccountId\":1300,\"provider\":\"google\",\"reason\":\"Flow refresh required.\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:27] local.INFO: [SocialAccountService] Fetching token {\"socialAccountId\":1409,\"provider\":\"google\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:27] local.INFO: [SocialAccountService] Token needs refreshing {\"socialAccountId\":1409,\"provider\":\"google\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:27] local.INFO: [EncryptedTokenManager] Generating access token. {\"mode\":\"legacy\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:27] local.INFO: [SocialAccountService] Refreshing token from provider {\"socialAccountId\":1409,\"provider\":\"google\",\"refreshToken\":\"e2a3f2d06894894eed1ee87d9db1ace77d4d42ee6e1288a8940ad2c10333b0c4\",\"state\":\"full-refresh\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:28] local.ERROR: [SocialAccountService] Failed to refresh token {\"socialAccountId\":1409,\"provider\":\"google\",\"responseBody\":{\"error\":\"invalid_grant\",\"error_description\":\"Bad Request\"}} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:28] local.INFO: [SocialAccountObserver] Saving model {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:28] local.ERROR: [SocialAccountService] Failed to refresh token {\"socialAccountId\":1409,\"provider\":\"google\",\"reason\":\"Flow refresh required.\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:28] local.INFO: [SocialAccountService] Fetching token {\"socialAccountId\":1352,\"provider\":\"google\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:28] local.INFO: [SocialAccountService] Token needs refreshing {\"socialAccountId\":1352,\"provider\":\"google\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:28] local.INFO: [EncryptedTokenManager] Generating access token. {\"mode\":\"legacy\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:28] local.INFO: [SocialAccountService] Refreshing token from provider {\"socialAccountId\":1352,\"provider\":\"google\",\"refreshToken\":\"dd4b16b00fdc1216da6b717c02338c073636e29162826b2de6db3f064fc029eb\",\"state\":\"full-refresh\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:28] local.ERROR: [SocialAccountService] Failed to refresh token {\"socialAccountId\":1352,\"provider\":\"google\",\"responseBody\":{\"error\":\"unauthorized_client\",\"error_description\":\"Unauthorized\"}} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:28] local.INFO: [SocialAccountObserver] Saving model {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:28] local.ERROR: [SocialAccountService] Failed to refresh token {\"socialAccountId\":1352,\"provider\":\"google\",\"reason\":\"Flow refresh required.\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:28] local.INFO: [SocialAccountService] Fetching token {\"socialAccountId\":1296,\"provider\":\"office\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:28] local.INFO: [SocialAccountService] Token needs refreshing {\"socialAccountId\":1296,\"provider\":\"office\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:28] local.INFO: [EncryptedTokenManager] Generating access token. {\"mode\":\"legacy\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:28] local.INFO: [SocialAccountService] Refreshing token from provider {\"socialAccountId\":1296,\"provider\":\"office\",\"refreshToken\":\"011ae723c9d800c674e0b4be76f49fc046dac7d501b66c59ef0d9549cfa56ae5\",\"state\":\"full-refresh\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:28] local.ERROR: [SocialAccountService] Failed to refresh token {\"socialAccountId\":1296,\"provider\":\"office\",\"responseBody\":\"{\\\"error\\\":\\\"invalid_client\\\",\\\"error_description\\\":\\\"AADSTS7000215: Invalid client secret provided. Ensure the secret being sent in the request is the client secret value, not the client secret ID, for a secret added to app 'bbcbb2ef-6200-4fae-82bd-d81f5dd738da'. Trace ID: 73b326f0-9fcd-4ff5-ad61-edd6a0504500 Correlation ID: 6bd8fef3-b24c-46d8-8bba-a9b2ab41c108 Timestamp: 2026-04-27 10:28:28Z\\\",\\\"error_codes\\\":[7000215],\\\"timestamp\\\":\\\"2026-04-27 10:28:28Z\\\",\\\"trace_id\\\":\\\"73b326f0-9fcd-4ff5-ad61-edd6a0504500\\\",\\\"correlation_id\\\":\\\"6bd8fef3-b24c-46d8-8bba-a9b2ab41c108\\\",\\\"error_uri\\\":\\\"https://login.microsoftonline.com/error?code=7000215\\\"}\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:28] local.INFO: [SocialAccountObserver] Saving model {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:28] local.ERROR: [SocialAccountService] Failed to refresh token {\"socialAccountId\":1296,\"provider\":\"office\",\"reason\":\"Flow refresh required.\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:28] local.INFO: [SocialAccountService] Fetching token {\"socialAccountId\":391,\"provider\":\"office\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:28] local.INFO: [SocialAccountService] Token needs refreshing {\"socialAccountId\":391,\"provider\":\"office\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:28] local.INFO: [EncryptedTokenManager] Generating access token. {\"mode\":\"legacy\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:28] local.INFO: [SocialAccountService] Refreshing token from provider {\"socialAccountId\":391,\"provider\":\"office\",\"refreshToken\":\"00045eebae0f39b34887c6d53f92ae78064f7145e1f4b67754aebd03cfb2d881\",\"state\":\"full-refresh\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:28] local.INFO: [SocialAccountService] Fetching token {\"socialAccountId\":1502,\"provider\":\"google\"} {\"correlation_id\":\"8a9488be-81de-40f5-8554-2503eec42566\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:28] local.INFO: [SocialAccountService] Token retrieved {\"socialAccountId\":1502,\"provider\":\"google\"} {\"correlation_id\":\"8a9488be-81de-40f5-8554-2503eec42566\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:28] local.INFO: [EncryptedTokenManager] Generating access token. {\"mode\":\"legacy\"} {\"correlation_id\":\"8a9488be-81de-40f5-8554-2503eec42566\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:29] local.INFO: [Calendar] Processing sync {\"calendarId\":\"a33076c1-8d97-431a-99f0-85c9524e118b\",\"from\":null,\"to\":null,\"delta\":\"CIiFh8TP44kDEIiFh8TP44kDGAUgkZvkzgIokZvkzgI=\",\"last_sync\":\"2024-12-09 07:12:53\",\"dateMode\":\"daily\"} {\"correlation_id\":\"8a9488be-81de-40f5-8554-2503eec42566\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:29] local.INFO: [CrmOwnerResolver] Integration owner matched as CRM Owner {\"crm_provider\":\"integration-app\",\"crm_owner\":1695,\"team_id\":3143} {\"correlation_id\":\"8a9488be-81de-40f5-8554-2503eec42566\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:29] local.INFO: [SocialAccountService] Fetching token {\"socialAccountId\":1502,\"provider\":\"google\"} {\"correlation_id\":\"8a9488be-81de-40f5-8554-2503eec42566\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:29] local.INFO: [SocialAccountService] Token retrieved {\"socialAccountId\":1502,\"provider\":\"google\"} {\"correlation_id\":\"8a9488be-81de-40f5-8554-2503eec42566\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:29] local.INFO: [EncryptedTokenManager] Generating access token. {\"mode\":\"legacy\"} {\"correlation_id\":\"8a9488be-81de-40f5-8554-2503eec42566\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:29] local.ERROR: [SocialAccountService] Failed to refresh token {\"socialAccountId\":391,\"provider\":\"office\",\"responseBody\":\"{\\\"error\\\":\\\"invalid_client\\\",\\\"error_description\\\":\\\"AADSTS7000215: Invalid client secret provided. Ensure the secret being sent in the request is the client secret value, not the client secret ID, for a secret added to app 'bbcbb2ef-6200-4fae-82bd-d81f5dd738da'. Trace ID: c5f1db6f-22fb-41cc-bd89-c46497634200 Correlation ID: 7b4df0cc-bf50-4017-9b1a-e25ffb6fe9bf Timestamp: 2026-04-27 10:28:29Z\\\",\\\"error_codes\\\":[7000215],\\\"timestamp\\\":\\\"2026-04-27 10:28:29Z\\\",\\\"trace_id\\\":\\\"c5f1db6f-22fb-41cc-bd89-c46497634200\\\",\\\"correlation_id\\\":\\\"7b4df0cc-bf50-4017-9b1a-e25ffb6fe9bf\\\",\\\"error_uri\\\":\\\"https://login.microsoftonline.com/error?code=7000215\\\"}\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:29] local.INFO: [SocialAccountObserver] Saving model {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:29] local.ERROR: [SocialAccountService] Failed to refresh token {\"socialAccountId\":391,\"provider\":\"office\",\"reason\":\"Flow refresh required.\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:29] local.INFO: [SocialAccountService] Fetching token {\"socialAccountId\":1271,\"provider\":\"office\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:29] local.INFO: [SocialAccountService] Token needs refreshing {\"socialAccountId\":1271,\"provider\":\"office\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:29] local.INFO: [EncryptedTokenManager] Generating access token. {\"mode\":\"legacy\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:29] local.INFO: [SocialAccountService] Refreshing token from provider {\"socialAccountId\":1271,\"provider\":\"office\",\"refreshToken\":\"118cde2c06993147b07ccaec4cbcd5026a819dea6c71081166a492933e392afb\",\"state\":\"full-refresh\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:29] local.INFO: [Google Calendar] Failed to watch channel for calendar {\"calendarId\":\"a33076c1-8d97-431a-99f0-85c9524e118b\",\"code\":400,\"reason\":\"{\n \\\"error\\\": {\n \\\"errors\\\": [\n {\n \\\"domain\\\": \\\"global\\\",\n \\\"reason\\\": \\\"push.webhookUrlNotHttps\\\",\n \\\"message\\\": \\\"WebHook callback must be HTTPS: /webhook/calendar/google?resourceType=event\\\"\n }\n ],\n \\\"code\\\": 400,\n \\\"message\\\": \\\"WebHook callback must be HTTPS: /webhook/calendar/google?resourceType=event\\\"\n }\n}\"} {\"correlation_id\":\"8a9488be-81de-40f5-8554-2503eec42566\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:29] local.WARNING: [Calendar] Sync failed {\"calendarId\":\"a33076c1-8d97-431a-99f0-85c9524e118b\",\"code\":400,\"reason\":\"{\n \\\"error\\\": {\n \\\"errors\\\": [\n {\n \\\"domain\\\": \\\"global\\\",\n \\\"reason\\\": \\\"push.webhookUrlNotHttps\\\",\n \\\"message\\\": \\\"WebHook callback must be HTTPS: /webhook/calendar/google?resourceType=event\\\"\n }\n ],\n \\\"code\\\": 400,\n \\\"message\\\": \\\"WebHook callback must be HTTPS: /webhook/calendar/google?resourceType=event\\\"\n }\n}\"} {\"correlation_id\":\"8a9488be-81de-40f5-8554-2503eec42566\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:29] local.ERROR: [SocialAccountService] Failed to refresh token {\"socialAccountId\":1271,\"provider\":\"office\",\"responseBody\":\"{\\\"error\\\":\\\"invalid_client\\\",\\\"error_description\\\":\\\"AADSTS7000215: Invalid client secret provided. Ensure the secret being sent in the request is the client secret value, not the client secret ID, for a secret added to app 'bbcbb2ef-6200-4fae-82bd-d81f5dd738da'. Trace ID: 8d6e82ba-88c3-4ec9-95ee-a4a905803900 Correlation ID: 6ede51d5-704d-4e24-9fb7-88f69f3da5db Timestamp: 2026-04-27 10:28:29Z\\\",\\\"error_codes\\\":[7000215],\\\"timestamp\\\":\\\"2026-04-27 10:28:29Z\\\",\\\"trace_id\\\":\\\"8d6e82ba-88c3-4ec9-95ee-a4a905803900\\\",\\\"correlation_id\\\":\\\"6ede51d5-704d-4e24-9fb7-88f69f3da5db\\\",\\\"error_uri\\\":\\\"https://login.microsoftonline.com/error?code=7000215\\\"}\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:30] local.INFO: [SocialAccountObserver] Saving model {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:30] local.ERROR: [SocialAccountService] Failed to refresh token {\"socialAccountId\":1271,\"provider\":\"office\",\"reason\":\"Flow refresh required.\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:30] local.INFO: [SocialAccountService] Fetching token {\"socialAccountId\":1351,\"provider\":\"google\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:30] local.INFO: [SocialAccountService] Token needs refreshing {\"socialAccountId\":1351,\"provider\":\"google\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:30] local.INFO: [EncryptedTokenManager] Generating access token. {\"mode\":\"legacy\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:30] local.INFO: [SocialAccountService] Refreshing token from provider {\"socialAccountId\":1351,\"provider\":\"google\",\"refreshToken\":\"4271d15b9e60a606439caddc68337f783e472c85b03dacff14d1b6dfded9051c\",\"state\":\"full-refresh\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:30] local.ERROR: [SocialAccountService] Failed to refresh token {\"socialAccountId\":1351,\"provider\":\"google\",\"responseBody\":{\"error\":\"invalid_grant\",\"error_description\":\"Token has been expired or revoked.\"}} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:30] local.INFO: [SocialAccountObserver] Saving model {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:30] local.ERROR: [SocialAccountService] Failed to refresh token {\"socialAccountId\":1351,\"provider\":\"google\",\"reason\":\"Flow refresh required.\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:30] local.INFO: [SocialAccountService] Fetching token {\"socialAccountId\":1366,\"provider\":\"google\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:30] local.INFO: [SocialAccountService] Token needs refreshing {\"socialAccountId\":1366,\"provider\":\"google\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:30] local.INFO: [EncryptedTokenManager] Generating access token. {\"mode\":\"legacy\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:30] local.INFO: [SocialAccountService] Refreshing token from provider {\"socialAccountId\":1366,\"provider\":\"google\",\"refreshToken\":\"ae21385059b2eebfd43f68aecd56eccd702a1aabb6598f1f7ab594ed8af491b4\",\"state\":\"full-refresh\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:30] local.ERROR: [SocialAccountService] Failed to refresh token {\"socialAccountId\":1366,\"provider\":\"google\",\"responseBody\":{\"error\":\"invalid_grant\",\"error_description\":\"Bad Request\"}} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:30] local.INFO: [SocialAccountObserver] Saving model {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:30] local.ERROR: [SocialAccountService] Failed to refresh token {\"socialAccountId\":1366,\"provider\":\"google\",\"reason\":\"Flow refresh required.\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:30] local.INFO: [SocialAccountService] Fetching token {\"socialAccountId\":1115,\"provider\":\"google\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:30] local.INFO: [SocialAccountService] Token needs refreshing {\"socialAccountId\":1115,\"provider\":\"google\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:30] local.INFO: [EncryptedTokenManager] Generating access token. {\"mode\":\"legacy\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:30] local.INFO: [SocialAccountService] Refreshing token from provider {\"socialAccountId\":1115,\"provider\":\"google\",\"refreshToken\":\"356b60f12e262a5e24d3042386ef47d6a6cfe3074c242f4426edcec8646192b1\",\"state\":\"connected\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:30] local.INFO: [SocialAccountObserver] Saving model {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:30] local.INFO: [SocialAccountObserver] Access token was modified, encrypting {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:31] local.INFO: [SocialAccountService] Token refreshed {\"socialAccountId\":1115,\"provider\":\"google\",\"state\":\"connected\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:31] local.INFO: Calendar sync job dispatched {\"calendar_id\":378} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:31] local.INFO: [SocialAccountService] Fetching token {\"socialAccountId\":1421,\"provider\":\"office\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:31] local.INFO: [SocialAccountService] Token needs refreshing {\"socialAccountId\":1421,\"provider\":\"office\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:31] local.INFO: [EncryptedTokenManager] Generating access token. {\"mode\":\"legacy\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:31] local.INFO: [SocialAccountService] Refreshing token from provider {\"socialAccountId\":1421,\"provider\":\"office\",\"refreshToken\":\"9e3e287aa0cc900726e3d4eead2600fb64aa34b437790f6a1fd8048306cb2db9\",\"state\":\"connected\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:31] local.INFO: [SocialAccountService] Fetching token {\"socialAccountId\":1115,\"provider\":\"google\"} {\"correlation_id\":\"8fdf411d-8a4f-46c1-a46a-cc84f3c2f2b1\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:31] local.INFO: [SocialAccountService] Token retrieved {\"socialAccountId\":1115,\"provider\":\"google\"} {\"correlation_id\":\"8fdf411d-8a4f-46c1-a46a-cc84f3c2f2b1\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:31] local.INFO: [EncryptedTokenManager] Generating access token. {\"mode\":\"legacy\"} {\"correlation_id\":\"8fdf411d-8a4f-46c1-a46a-cc84f3c2f2b1\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:31] local.INFO: [Calendar] Processing sync {\"calendarId\":\"2676cb6d-f86c-427e-bf78-591e388e3c1e\",\"from\":null,\"to\":null,\"delta\":\"CJ_x49O3jpIDEJ_x49O3jpIDGAUgw67KlwMow67KlwM=\",\"last_sync\":\"2026-01-19 07:48:40\",\"dateMode\":\"daily\"} {\"correlation_id\":\"8fdf411d-8a4f-46c1-a46a-cc84f3c2f2b1\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:31] local.WARNING: [Pipedrive] Account not connected for user {\"userId\":\"e6538737-e7b4-455f-a37a-3e79b665a220\",\"account\":{\"Jiminny\\\\Models\\\\SocialAccount\":{\"id\":1116,\"sociable_id\":241,\"provider_user_id\":\"19555731\",\"expires\":1775683749,\"refresh_token_expires\":null,\"provider\":\"pipedrive\",\"state\":\"full-refresh\",\"auth_scope\":\"base,deals:full,activities:full,contacts:full,search:read\",\"retry_after\":null,\"created_at\":\"2023-09-08 09:44:29\",\"updated_at\":\"2026-04-08 22:58:34\"}}} {\"correlation_id\":\"8fdf411d-8a4f-46c1-a46a-cc84f3c2f2b1\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:31] local.INFO: [CrmOwnerResolver] Integration owner is not connected, attempting team members {\"crm_provider\":\"pipedrive\",\"crm_owner\":241,\"team_id\":19} {\"correlation_id\":\"8fdf411d-8a4f-46c1-a46a-cc84f3c2f2b1\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:31] local.INFO: [CrmOwnerResolver] No team members found with active crm connection {\"crm_provider\":\"pipedrive\",\"team_id\":19} {\"correlation_id\":\"8fdf411d-8a4f-46c1-a46a-cc84f3c2f2b1\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:31] local.INFO: [CrmOwnerResolver] No team member found with active crm connection {\"crm_provider\":\"pipedrive\",\"team_id\":19} {\"correlation_id\":\"8fdf411d-8a4f-46c1-a46a-cc84f3c2f2b1\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:31] local.WARNING: [Calendar] CRM disconnected for user so events will not be matched {\"provider\":\"pipedrive\",\"user_id\":241,\"message\":\"Your Pipedrive account has become disconnected. Please login to Jiminny to reconnect.\"} {\"correlation_id\":\"8fdf411d-8a4f-46c1-a46a-cc84f3c2f2b1\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:31] local.INFO: [SocialAccountService] Fetching token {\"socialAccountId\":1115,\"provider\":\"google\"} {\"correlation_id\":\"8fdf411d-8a4f-46c1-a46a-cc84f3c2f2b1\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:31] local.INFO: [SocialAccountService] Token retrieved {\"socialAccountId\":1115,\"provider\":\"google\"} {\"correlation_id\":\"8fdf411d-8a4f-46c1-a46a-cc84f3c2f2b1\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:31] local.INFO: [EncryptedTokenManager] Generating access token. {\"mode\":\"legacy\"} {\"correlation_id\":\"8fdf411d-8a4f-46c1-a46a-cc84f3c2f2b1\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:31] local.INFO: [Google Calendar] Failed to watch channel for calendar {\"calendarId\":\"2676cb6d-f86c-427e-bf78-591e388e3c1e\",\"code\":400,\"reason\":\"{\n \\\"error\\\": {\n \\\"errors\\\": [\n {\n \\\"domain\\\": \\\"global\\\",\n \\\"reason\\\": \\\"push.webhookUrlNotHttps\\\",\n \\\"message\\\": \\\"WebHook callback must be HTTPS: /webhook/calendar/google?resourceType=event\\\"\n }\n ],\n \\\"code\\\": 400,\n \\\"message\\\": \\\"WebHook callback must be HTTPS: /webhook/calendar/google?resourceType=event\\\"\n }\n}\"} {\"correlation_id\":\"8fdf411d-8a4f-46c1-a46a-cc84f3c2f2b1\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:31] local.WARNING: [Calendar] Sync failed {\"calendarId\":\"2676cb6d-f86c-427e-bf78-591e388e3c1e\",\"code\":400,\"reason\":\"{\n \\\"error\\\": {\n \\\"errors\\\": [\n {\n \\\"domain\\\": \\\"global\\\",\n \\\"reason\\\": \\\"push.webhookUrlNotHttps\\\",\n \\\"message\\\": \\\"WebHook callback must be HTTPS: /webhook/calendar/google?resourceType=event\\\"\n }\n ],\n \\\"code\\\": 400,\n \\\"message\\\": \\\"WebHook callback must be HTTPS: /webhook/calendar/google?resourceType=event\\\"\n }\n}\"} {\"correlation_id\":\"8fdf411d-8a4f-46c1-a46a-cc84f3c2f2b1\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:32] local.INFO: [SocialAccountObserver] Saving model {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:32] local.INFO: [SocialAccountObserver] Access token was modified, encrypting {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:32] local.INFO: [SocialAccountObserver] Refresh token was modified, encrypting {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:32] local.INFO: [SocialAccountService] Token refreshed {\"socialAccountId\":1421,\"provider\":\"office\",\"state\":\"connected\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:32] local.INFO: Calendar sync job dispatched {\"calendar_id\":504} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:32] local.NOTICE: Calendar sync end {\"retrieved_calendars\":31,\"processed_calendars\":3} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:32] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage for command {\"command\":\"calendar:sync\",\"memoryBeforeCommandInMb\":60.0,\"memoryAfterCommandInMB\":62.0,\"memoryPeakBeforeCommandInMb\":99.727,\"memoryPeakAfterCommandInMB\":99.727} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:33] local.INFO: [SocialAccountService] Fetching token {\"socialAccountId\":1421,\"provider\":\"office\"} {\"correlation_id\":\"52b2ff86-82c7-4ed0-b528-35d7d00d69f0\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:33] local.INFO: [SocialAccountService] Token retrieved {\"socialAccountId\":1421,\"provider\":\"office\"} {\"correlation_id\":\"52b2ff86-82c7-4ed0-b528-35d7d00d69f0\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:33] local.INFO: [EncryptedTokenManager] Generating access token. {\"mode\":\"legacy\"} {\"correlation_id\":\"52b2ff86-82c7-4ed0-b528-35d7d00d69f0\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:34] local.INFO: [Calendar] Processing sync {\"calendarId\":\"9e8b1a2c-1a8f-42bd-b161-810fc0baf540\",\"from\":null,\"to\":null,\"delta\":\"R0usmcdvmMuZCBYV0hguCMx0nGVsTQEodM41jSlu-5eY2JUTYHZXE-bDro2tnepp8q_Vq3ITNS8CkncjGjcBfaHkza05dCTIlaXcSIoPNGOl9vNckYy_ZhVgE1SWvgRcCeVT-SyogdB9hP-oaWbH9r51OQrrzAwmalK5aCksyUVh9jG_A-mGVzRPnUB4SKa77o5P659lpx1egON48YU2tQ.D2oErKma-mQrBdXxAKdH7beUkp6HS2PXUWYLCgvbP2c\",\"last_sync\":\"2026-04-27 06:28:41\",\"dateMode\":\"daily\"} {\"correlation_id\":\"52b2ff86-82c7-4ed0-b528-35d7d00d69f0\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:34] local.INFO: [SocialAccountService] Fetching token {\"socialAccountId\":1499,\"provider\":\"hubspot\"} {\"correlation_id\":\"52b2ff86-82c7-4ed0-b528-35d7d00d69f0\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:34] local.INFO: [SocialAccountService] Token retrieved {\"socialAccountId\":1499,\"provider\":\"hubspot\"} {\"correlation_id\":\"52b2ff86-82c7-4ed0-b528-35d7d00d69f0\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:34] local.INFO: [EncryptedTokenManager] Generating access token. {\"mode\":\"legacy\"} {\"correlation_id\":\"52b2ff86-82c7-4ed0-b528-35d7d00d69f0\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:34] local.INFO: [CrmOwnerResolver] Integration owner matched as CRM Owner {\"crm_provider\":\"hubspot\",\"crm_owner\":89,\"team_id\":2} {\"correlation_id\":\"52b2ff86-82c7-4ed0-b528-35d7d00d69f0\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:34] local.INFO: [MS Office Calendar] Skipping delta sync for daily mode {\"calendarId\":\"9e8b1a2c-1a8f-42bd-b161-810fc0baf540\"} {\"correlation_id\":\"52b2ff86-82c7-4ed0-b528-35d7d00d69f0\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:29:06] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage before starting command {\"command\":\"meeting-bot:schedule-bot\",\"memoryBeforeCommandInMb\":60.0,\"memoryPeakBeforeCommandInMb\":99.727} {\"correlation_id\":\"7e95c05b-50c8-4e0b-a01c-efeed6af544c\",\"trace_id\":\"07db4d4f-d6e7-44d9-8e54-4f038ea69afb\"}\n[2026-04-27 10:29:06] local.INFO: [ScheduleBotCommand] Number of activities to be captured: 0 {\"correlation_id\":\"7e95c05b-50c8-4e0b-a01c-efeed6af544c\",\"trace_id\":\"07db4d4f-d6e7-44d9-8e54-4f038ea69afb\"}\n[2026-04-27 10:29:06] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage for command {\"command\":\"meeting-bot:schedule-bot\",\"memoryBeforeCommandInMb\":60.0,\"memoryAfterCommandInMB\":62.0,\"memoryPeakBeforeCommandInMb\":99.727,\"memoryPeakAfterCommandInMB\":99.727} {\"correlation_id\":\"7e95c05b-50c8-4e0b-a01c-efeed6af544c\",\"trace_id\":\"07db4d4f-d6e7-44d9-8e54-4f038ea69afb\"}\n[2026-04-27 10:29:09] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage before starting command {\"command\":\"dialers:monitor-activities\",\"memoryBeforeCommandInMb\":60.0,\"memoryPeakBeforeCommandInMb\":99.727} {\"correlation_id\":\"e6e7fbf8-6e80-4529-858e-a4e95d0d3805\",\"trace_id\":\"f91a115a-9fdc-46ce-b9f2-13a1e6d0e325\"}\n[2026-04-27 10:29:09] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage for command {\"command\":\"dialers:monitor-activities\",\"memoryBeforeCommandInMb\":60.0,\"memoryAfterCommandInMB\":62.0,\"memoryPeakBeforeCommandInMb\":99.727,\"memoryPeakAfterCommandInMB\":99.727} {\"correlation_id\":\"e6e7fbf8-6e80-4529-858e-a4e95d0d3805\",\"trace_id\":\"f91a115a-9fdc-46ce-b9f2-13a1e6d0e325\"}\n[2026-04-27 10:29:10] local.NOTICE: Monitoring start {\"correlation_id\":\"211e5e36-1c6c-4198-b928-d2f1525ab0ea\",\"trace_id\":\"c26e980d-6e32-426d-9667-f1d902432b8d\"}\n[2026-04-27 10:29:10] local.NOTICE: Monitoring end {\"correlation_id\":\"211e5e36-1c6c-4198-b928-d2f1525ab0ea\",\"trace_id\":\"c26e980d-6e32-426d-9667-f1d902432b8d\"}\n[2026-04-27 10:29:12] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage before starting command {\"command\":\"mailbox:skip-lists:refresh\",\"memoryBeforeCommandInMb\":60.0,\"memoryPeakBeforeCommandInMb\":99.727} {\"correlation_id\":\"684bdf68-76ee-42dd-9d97-62f414a33b1b\",\"trace_id\":\"284c98ef-5b7f-4572-996c-72c5bc2995f9\"}\n[2026-04-27 10:29:13] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage for command {\"command\":\"mailbox:skip-lists:refresh\",\"memoryBeforeCommandInMb\":60.0,\"memoryAfterCommandInMB\":62.0,\"memoryPeakBeforeCommandInMb\":99.727,\"memoryPeakAfterCommandInMB\":99.727} {\"correlation_id\":\"684bdf68-76ee-42dd-9d97-62f414a33b1b\",\"trace_id\":\"284c98ef-5b7f-4572-996c-72c5bc2995f9\"}\n[2026-04-27 10:29:18] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage before starting command {\"command\":\"mailbox:batch:process\",\"memoryBeforeCommandInMb\":60.0,\"memoryPeakBeforeCommandInMb\":99.727} {\"correlation_id\":\"6e74b148-ae54-42ff-ab72-2ded18d10ff4\",\"trace_id\":\"d05bc9a0-450c-45b2-be3b-225a551ed909\"}\n[2026-04-27 10:29:18] local.INFO: [EmailSchedule] STARTING batch process {\"host\":\"docker_lamp_1\"} {\"correlation_id\":\"6e74b148-ae54-42ff-ab72-2ded18d10ff4\",\"trace_id\":\"d05bc9a0-450c-45b2-be3b-225a551ed909\"}\n[2026-04-27 10:29:18] local.INFO: [EmailSchedule] FINISHED batch process {\"host\":\"docker_lamp_1\",\"processed\":0} {\"correlation_id\":\"6e74b148-ae54-42ff-ab72-2ded18d10ff4\",\"trace_id\":\"d05bc9a0-450c-45b2-be3b-225a551ed909\"}\n[2026-04-27 10:29:18] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage for command {\"command\":\"mailbox:batch:process\",\"memoryBeforeCommandInMb\":60.0,\"memoryAfterCommandInMB\":62.0,\"memoryPeakBeforeCommandInMb\":99.727,\"memoryPeakAfterCommandInMB\":99.727} {\"correlation_id\":\"6e74b148-ae54-42ff-ab72-2ded18d10ff4\",\"trace_id\":\"d05bc9a0-450c-45b2-be3b-225a551ed909\"}\n[2026-04-27 10:30:08] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage before starting command {\"command\":\"meeting-bot:schedule-bot\",\"memoryBeforeCommandInMb\":60.0,\"memoryPeakBeforeCommandInMb\":99.727} {\"correlation_id\":\"24b807e0-b413-4ecc-a89b-365280be88ac\",\"trace_id\":\"542ac70c-6756-4409-8224-0844a7576d9c\"}\n[2026-04-27 10:30:08] local.INFO: [ScheduleBotCommand] Number of activities to be captured: 0 {\"correlation_id\":\"24b807e0-b413-4ecc-a89b-365280be88ac\",\"trace_id\":\"542ac70c-6756-4409-8224-0844a7576d9c\"}\n[2026-04-27 10:30:08] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage for command {\"command\":\"meeting-bot:schedule-bot\",\"memoryBeforeCommandInMb\":60.0,\"memoryAfterCommandInMB\":62.0,\"memoryPeakBeforeCommandInMb\":99.727,\"memoryPeakAfterCommandInMB\":99.727} {\"correlation_id\":\"24b807e0-b413-4ecc-a89b-365280be88ac\",\"trace_id\":\"542ac70c-6756-4409-8224-0844a7576d9c\"}\n[2026-04-27 10:30:10] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage before starting command {\"command\":\"dialers:monitor-activities\",\"memoryBeforeCommandInMb\":60.0,\"memoryPeakBeforeCommandInMb\":99.727} {\"correlation_id\":\"2711ca04-b4a7-4f3a-b0b2-5826fea4da3b\",\"trace_id\":\"2613ffca-f181-4d91-ad55-ec1bdd97ee7e\"}\n[2026-04-27 10:30:10] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage for command {\"command\":\"dialers:monitor-activities\",\"memoryBeforeCommandInMb\":60.0,\"memoryAfterCommandInMB\":62.0,\"memoryPeakBeforeCommandInMb\":99.727,\"memoryPeakAfterCommandInMB\":99.727} {\"correlation_id\":\"2711ca04-b4a7-4f3a-b0b2-5826fea4da3b\",\"trace_id\":\"2613ffca-f181-4d91-ad55-ec1bdd97ee7e\"}\n[2026-04-27 10:30:17] local.NOTICE: Monitoring start {\"correlation_id\":\"e1537475-8481-4144-b9f5-596ea8034b8a\",\"trace_id\":\"f5561d0a-0cb7-4c72-8bde-cce617183529\"}\n[2026-04-27 10:30:17] local.NOTICE: Monitoring end {\"correlation_id\":\"e1537475-8481-4144-b9f5-596ea8034b8a\",\"trace_id\":\"f5561d0a-0cb7-4c72-8bde-cce617183529\"}\n[2026-04-27 10:30:21] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage before starting command {\"command\":\"mailbox:skip-lists:refresh\",\"memoryBeforeCommandInMb\":60.0,\"memoryPeakBeforeCommandInMb\":99.727} {\"correlation_id\":\"8a911b60-16a0-416e-934e-3b7e0280e452\",\"trace_id\":\"3c2bbdd6-985d-4624-b42b-fd9cc2b4756f\"}\n[2026-04-27 10:30:21] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage for command {\"command\":\"mailbox:skip-lists:refresh\",\"memoryBeforeCommandInMb\":60.0,\"memoryAfterCommandInMB\":62.0,\"memoryPeakBeforeCommandInMb\":99.727,\"memoryPeakAfterCommandInMB\":99.727} {\"correlation_id\":\"8a911b60-16a0-416e-934e-3b7e0280e452\",\"trace_id\":\"3c2bbdd6-985d-4624-b42b-fd9cc2b4756f\"}\n[2026-04-27 10:30:25] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage before starting command {\"command\":\"mailbox:batch:process\",\"memoryBeforeCommandInMb\":60.0,\"memoryPeakBeforeCommandInMb\":99.727} {\"correlation_id\":\"f62efaa6-201f-43b2-bf45-9619982dffb1\",\"trace_id\":\"f1ad3d60-af4e-4427-9d77-6aa71cfed172\"}\n[2026-04-27 10:30:25] local.INFO: [EmailSchedule] STARTING batch process {\"host\":\"docker_lamp_1\"} {\"correlation_id\":\"f62efaa6-201f-43b2-bf45-9619982dffb1\",\"trace_id\":\"f1ad3d60-af4e-4427-9d77-6aa71cfed172\"}\n[2026-04-27 10:30:25] local.INFO: [EmailSchedule] FINISHED batch process {\"host\":\"docker_lamp_1\",\"processed\":0} {\"correlation_id\":\"f62efaa6-201f-43b2-bf45-9619982dffb1\",\"trace_id\":\"f1ad3d60-af4e-4427-9d77-6aa71cfed172\"}\n[2026-04-27 10:30:25] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage for command {\"command\":\"mailbox:batch:process\",\"memoryBeforeCommandInMb\":60.0,\"memoryAfterCommandInMB\":62.0,\"memoryPeakBeforeCommandInMb\":99.727,\"memoryPeakAfterCommandInMB\":99.727} {\"correlation_id\":\"f62efaa6-201f-43b2-bf45-9619982dffb1\",\"trace_id\":\"f1ad3d60-af4e-4427-9d77-6aa71cfed172\"}\n[2026-04-27 10:30:30] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage before starting command {\"command\":\"conference:monitor:count\",\"memoryBeforeCommandInMb\":60.0,\"memoryPeakBeforeCommandInMb\":99.727} {\"correlation_id\":\"cf6dc132-ecb8-4efc-bead-06bf1c96cae6\",\"trace_id\":\"9e2b073b-940d-498a-babe-115f633dd954\"}\n[2026-04-27 10:30:30] local.INFO: Running conference:monitor:count command for activities in (2026-04-27 10:28:00, 2026-04-27 10:30:00] {\"correlation_id\":\"cf6dc132-ecb8-4efc-bead-06bf1c96cae6\",\"trace_id\":\"9e2b073b-940d-498a-babe-115f633dd954\"}\n[2026-04-27 10:30:30] local.INFO: [conference:monitor:count] No activities found in (2026-04-27 10:28:00, 2026-04-27 10:30:00] {\"correlation_id\":\"cf6dc132-ecb8-4efc-bead-06bf1c96cae6\",\"trace_id\":\"9e2b073b-940d-498a-babe-115f633dd954\"}\n[2026-04-27 10:30:30] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage for command {\"command\":\"conference:monitor:count\",\"memoryBeforeCommandInMb\":60.0,\"memoryAfterCommandInMB\":62.0,\"memoryPeakBeforeCommandInMb\":99.727,\"memoryPeakAfterCommandInMB\":99.727} {\"correlation_id\":\"cf6dc132-ecb8-4efc-bead-06bf1c96cae6\",\"trace_id\":\"9e2b073b-940d-498a-babe-115f633dd954\"}\n[2026-04-27 10:30:32] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage before starting command {\"command\":\"activity:purge-stale\",\"memoryBeforeCommandInMb\":60.0,\"memoryPeakBeforeCommandInMb\":99.727} {\"correlation_id\":\"f455260d-d1c1-456f-8937-d4bb026d9078\",\"trace_id\":\"f8c2e585-0137-4103-9e16-1b4ce05df2ef\"}\n[2026-04-27 10:30:32] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage for command {\"command\":\"activity:purge-stale\",\"memoryBeforeCommandInMb\":60.0,\"memoryAfterCommandInMB\":62.0,\"memoryPeakBeforeCommandInMb\":99.727,\"memoryPeakAfterCommandInMB\":99.727} {\"correlation_id\":\"f455260d-d1c1-456f-8937-d4bb026d9078\",\"trace_id\":\"f8c2e585-0137-4103-9e16-1b4ce05df2ef\"}\n[2026-04-27 10:30:35] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage before starting command {\"command\":\"mailbox:text-relay:sync\",\"memoryBeforeCommandInMb\":60.0,\"memoryPeakBeforeCommandInMb\":99.727} {\"correlation_id\":\"be7d3c6a-664a-42e8-a4b3-cbd021296404\",\"trace_id\":\"0b250827-4c02-4237-8b34-e5ddd004eba1\"}\n[2026-04-27 10:30:35] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage for command {\"command\":\"mailbox:text-relay:sync\",\"memoryBeforeCommandInMb\":60.0,\"memoryAfterCommandInMB\":62.0,\"memoryPeakBeforeCommandInMb\":99.727,\"memoryPeakAfterCommandInMB\":99.727} {\"correlation_id\":\"be7d3c6a-664a-42e8-a4b3-cbd021296404\",\"trace_id\":\"0b250827-4c02-4237-8b34-e5ddd004eba1\"}\n[2026-04-27 10:30:37] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage before starting command {\"command\":\"conference:pre-meeting-notification\",\"memoryBeforeCommandInMb\":60.0,\"memoryPeakBeforeCommandInMb\":99.727} {\"correlation_id\":\"5d8fbdf6-2737-4d73-babe-699c88279e1f\",\"trace_id\":\"0882a963-b02f-4049-b670-12b92f80829a\"}\n[2026-04-27 10:30:37] local.INFO: Running pre-meeting notification command {\"correlation_id\":\"5d8fbdf6-2737-4d73-babe-699c88279e1f\",\"trace_id\":\"0882a963-b02f-4049-b670-12b92f80829a\"}\n[2026-04-27 10:30:37] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage for command {\"command\":\"conference:pre-meeting-notification\",\"memoryBeforeCommandInMb\":60.0,\"memoryAfterCommandInMB\":62.0,\"memoryPeakBeforeCommandInMb\":99.727,\"memoryPeakAfterCommandInMB\":99.727} {\"correlation_id\":\"5d8fbdf6-2737-4d73-babe-699c88279e1f\",\"trace_id\":\"0882a963-b02f-4049-b670-12b92f80829a\"}\n[2026-04-27 10:30:39] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage before starting command {\"command\":\"conference:monitor:start\",\"memoryBeforeCommandInMb\":60.0,\"memoryPeakBeforeCommandInMb\":99.727} {\"correlation_id\":\"e8968e47-9acd-4bd4-9e7d-3c415f46df05\",\"trace_id\":\"287fba85-f395-41ab-813d-49cb5d0ea06d\"}\n[2026-04-27 10:30:39] local.INFO: Running conference:monitor:start command for activities in (2026-04-27 10:20:00, 2026-04-27 10:25:00] {\"correlation_id\":\"e8968e47-9acd-4bd4-9e7d-3c415f46df05\",\"trace_id\":\"287fba85-f395-41ab-813d-49cb5d0ea06d\"}\n[2026-04-27 10:30:39] local.INFO: [conference:monitor:start] No activities found in (2026-04-27 10:20:00, 2026-04-27 10:25:00] {\"correlation_id\":\"e8968e47-9acd-4bd4-9e7d-3c415f46df05\",\"trace_id\":\"287fba85-f395-41ab-813d-49cb5d0ea06d\"}\n[2026-04-27 10:30:39] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage for command {\"command\":\"conference:monitor:start\",\"memoryBeforeCommandInMb\":60.0,\"memoryAfterCommandInMB\":62.0,\"memoryPeakBeforeCommandInMb\":99.727,\"memoryPeakAfterCommandInMB\":99.727} {\"correlation_id\":\"e8968e47-9acd-4bd4-9e7d-3c415f46df05\",\"trace_id\":\"287fba85-f395-41ab-813d-49cb5d0ea06d\"}\n[2026-04-27 10:30:43] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage before starting command {\"command\":\"conference:monitor:end\",\"memoryBeforeCommandInMb\":60.0,\"memoryPeakBeforeCommandInMb\":99.727} {\"correlation_id\":\"b87eb011-7108-46bd-aa90-975ebe41f483\",\"trace_id\":\"1f54c2a6-fad1-4804-87d8-4972a378e91b\"}\n[2026-04-27 10:30:43] local.INFO: conference:monitor:end:Jiminny\\Console\\Commands\\Activities\\MonitorMeetingEndCommand::logActivitiesEnded {\"from\":\"10:25\",\"to\":\"10:30\"} {\"correlation_id\":\"b87eb011-7108-46bd-aa90-975ebe41f483\",\"trace_id\":\"1f54c2a6-fad1-4804-87d8-4972a378e91b\"}\n[2026-04-27 10:30:44] local.INFO: conference:monitor:end:Jiminny\\Console\\Commands\\Activities\\MonitorMeetingEndCommand::logActivitiesWithUnfinishedSession {\"from\":\"00:20\",\"to\":\"00:25\"} {\"correlation_id\":\"b87eb011-7108-46bd-aa90-975ebe41f483\",\"trace_id\":\"1f54c2a6-fad1-4804-87d8-4972a378e91b\"}\n[2026-04-27 10:30:44] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage for command {\"command\":\"conference:monitor:end\",\"memoryBeforeCommandInMb\":60.0,\"memoryAfterCommandInMB\":62.0,\"memoryPeakBeforeCommandInMb\":99.727,\"memoryPeakAfterCommandInMB\":99.727} {\"correlation_id\":\"b87eb011-7108-46bd-aa90-975ebe41f483\",\"trace_id\":\"1f54c2a6-fad1-4804-87d8-4972a378e91b\"}\n[2026-04-27 10:30:51] local.NOTICE: Repairing HubSpot tokens start {\"correlation_id\":\"2e6dfa3d-f07d-4eee-8232-6138133e2fbb\",\"trace_id\":\"41dea86e-0ecc-414e-8303-e60d8ae09976\"}\n[2026-04-27 10:30:51] local.INFO: Trying to refresh HubSpot token {\"account_id\":59,\"updated_at\":\"2025-10-03 09:32:05\"} {\"correlation_id\":\"2e6dfa3d-f07d-4eee-8232-6138133e2fbb\",\"trace_id\":\"41dea86e-0ecc-414e-8303-e60d8ae09976\"}\n[2026-04-27 10:30:51] local.INFO: [EncryptedTokenManager] Generating access token. {\"mode\":\"legacy\"} {\"correlation_id\":\"2e6dfa3d-f07d-4eee-8232-6138133e2fbb\",\"trace_id\":\"41dea86e-0ecc-414e-8303-e60d8ae09976\"}\n[2026-04-27 10:30:51] local.INFO: [SocialAccountService] Refreshing token from provider {\"socialAccountId\":59,\"provider\":\"hubspot\",\"refreshToken\":\"97b78f6e2cc49965c00c2492b602b02708b1392551e6b3f113fbaa48992af90b\",\"state\":\"full-refresh\"} {\"correlation_id\":\"2e6dfa3d-f07d-4eee-8232-6138133e2fbb\",\"trace_id\":\"41dea86e-0ecc-414e-8303-e60d8ae09976\"}\n[2026-04-27 10:30:52] local.ERROR: Failed to refresh HubSpot token {\"account_id\":59,\"updated_at\":\"2025-10-03 09:32:05\",\"reason\":\"missing or invalid refresh token\",\"previous\":\"\"} {\"correlation_id\":\"2e6dfa3d-f07d-4eee-8232-6138133e2fbb\",\"trace_id\":\"41dea86e-0ecc-414e-8303-e60d8ae09976\"}\n[2026-04-27 10:30:52] local.INFO: Trying to refresh HubSpot token {\"account_id\":306,\"updated_at\":\"2023-11-27 09:30:03\"} {\"correlation_id\":\"2e6dfa3d-f07d-4eee-8232-6138133e2fbb\",\"trace_id\":\"41dea86e-0ecc-414e-8303-e60d8ae09976\"}\n[2026-04-27 10:30:52] local.INFO: [EncryptedTokenManager] Generating access token. {\"mode\":\"legacy\"} {\"correlation_id\":\"2e6dfa3d-f07d-4eee-8232-6138133e2fbb\",\"trace_id\":\"41dea86e-0ecc-414e-8303-e60d8ae09976\"}\n[2026-04-27 10:30:52] local.INFO: [SocialAccountService] Refreshing token from provider {\"socialAccountId\":306,\"provider\":\"hubspot\",\"refreshToken\":\"6fa6aa8cc641d131231acc3470f5c03cb3b07b2e580fb18f8acb3b1dbb72549b\",\"state\":\"full-refresh\"} {\"correlation_id\":\"2e6dfa3d-f07d-4eee-8232-6138133e2fbb\",\"trace_id\":\"41dea86e-0ecc-414e-8303-e60d8ae09976\"}\n[2026-04-27 10:30:52] local.ERROR: Failed to refresh HubSpot token {\"account_id\":306,\"updated_at\":\"2023-11-27 09:30:03\",\"reason\":\"missing or invalid refresh token\",\"previous\":\"\"} {\"correlation_id\":\"2e6dfa3d-f07d-4eee-8232-6138133e2fbb\",\"trace_id\":\"41dea86e-0ecc-414e-8303-e60d8ae09976\"}\n[2026-04-27 10:30:52] local.INFO: Trying to refresh HubSpot token {\"account_id\":1372,\"updated_at\":\"2025-10-02 14:47:06\"} {\"correlation_id\":\"2e6dfa3d-f07d-4eee-8232-6138133e2fbb\",\"trace_id\":\"41dea86e-0ecc-414e-8303-e60d8ae09976\"}\n[2026-04-27 10:30:52] local.INFO: [EncryptedTokenManager] Generating access token. {\"mode\":\"legacy\"} {\"correlation_id\":\"2e6dfa3d-f07d-4eee-8232-6138133e2fbb\",\"trace_id\":\"41dea86e-0ecc-414e-8303-e60d8ae09976\"}\n[2026-04-27 10:30:52] local.INFO: [SocialAccountService] Refreshing token from provider {\"socialAccountId\":1372,\"provider\":\"hubspot\",\"refreshToken\":\"9aa73948c761da29dce46c177cf9aee1fde483a44169ca38723f9f0597d7a8c4\",\"state\":\"full-refresh\"} {\"correlation_id\":\"2e6dfa3d-f07d-4eee-8232-6138133e2fbb\",\"trace_id\":\"41dea86e-0ecc-414e-8303-e60d8ae09976\"}\n[2026-04-27 10:30:52] local.ERROR: Failed to refresh HubSpot token {\"account_id\":1372,\"updated_at\":\"2025-10-02 14:47:06\",\"reason\":\"missing or invalid refresh token\",\"previous\":\"\"} {\"correlation_id\":\"2e6dfa3d-f07d-4eee-8232-6138133e2fbb\",\"trace_id\":\"41dea86e-0ecc-414e-8303-e60d8ae09976\"}\n[2026-04-27 10:30:52] local.NOTICE: Repairing HubSpot tokens end {\"total\":3,\"fixed\":0,\"failed\":3} {\"correlation_id\":\"2e6dfa3d-f07d-4eee-8232-6138133e2fbb\",\"trace_id\":\"41dea86e-0ecc-414e-8303-e60d8ae09976\"}\n[2026-04-27 10:30:56] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage before starting command {\"command\":\"jiminny:transcription:retry-failed\",\"memoryBeforeCommandInMb\":60.0,\"memoryPeakBeforeCommandInMb\":99.727} {\"correlation_id\":\"a6655a64-38ad-4c65-96f1-f5a294dbb343\",\"trace_id\":\"52cdca32-c0c8-4215-81a2-4263bbde2158\"}\n[2026-04-27 10:30:56] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage before starting command {\"command\":\"conference:pre-meeting-reminder\",\"memoryBeforeCommandInMb\":60.0,\"memoryPeakBeforeCommandInMb\":99.727} {\"correlation_id\":\"ec2d49dd-ab36-4f4e-8a60-7bf5ea384cac\",\"trace_id\":\"b3e89960-9a27-4bf7-8b55-95f5704f9a10\"}\n[2026-04-27 10:30:57] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage for command {\"command\":\"jiminny:transcription:retry-failed\",\"memoryBeforeCommandInMb\":60.0,\"memoryAfterCommandInMB\":62.0,\"memoryPeakBeforeCommandInMb\":99.727,\"memoryPeakAfterCommandInMB\":99.727} {\"correlation_id\":\"a6655a64-38ad-4c65-96f1-f5a294dbb343\",\"trace_id\":\"52cdca32-c0c8-4215-81a2-4263bbde2158\"}\n[2026-04-27 10:30:57] local.INFO: [HubSpot Journal Polling] Getting offset from database {\"offset\":\"\",\"jiminny_team_id\":1} {\"correlation_id\":\"1f928e93-b17e-4518-a1b9-a8280d1d95e8\",\"trace_id\":\"ba203ed6-fe98-4fd7-aa62-8de9fb8ffeca\"}\n[2026-04-27 10:30:57] local.INFO: [HubSpot Journal Command] Starting polling service {\"correlation_id\":\"1f928e93-b17e-4518-a1b9-a8280d1d95e8\",\"trace_id\":\"ba203ed6-fe98-4fd7-aa62-8de9fb8ffeca\"}\n[2026-04-27 10:30:57] local.INFO: [HubSpot Journal Polling] Service starting {\"memory_limit\":\"256M\",\"max_execution_time\":\"0\",\"initial_memory_mb\":62.0} {\"correlation_id\":\"1f928e93-b17e-4518-a1b9-a8280d1d95e8\",\"trace_id\":\"ba203ed6-fe98-4fd7-aa62-8de9fb8ffeca\"}\n[2026-04-27 10:30:57] local.INFO: [HubSpot Journal Polling] Acquired polling lock {\"expires_at\":\"2026-04-27T10:32:57.492763Z\"} {\"correlation_id\":\"1f928e93-b17e-4518-a1b9-a8280d1d95e8\",\"trace_id\":\"ba203ed6-fe98-4fd7-aa62-8de9fb8ffeca\"}\n[2026-04-27 10:30:57] local.INFO: [HubSpot Journal Polling] Getting offset from database {\"offset\":\"\",\"jiminny_team_id\":1} {\"correlation_id\":\"1f928e93-b17e-4518-a1b9-a8280d1d95e8\",\"trace_id\":\"ba203ed6-fe98-4fd7-aa62-8de9fb8ffeca\"}\n[2026-04-27 10:30:57] local.INFO: [HubSpot Journal API] Fetching latest journal entry {\"url\":\"https://api.hubapi.com/webhooks/v4/journal/latest\"} {\"correlation_id\":\"1f928e93-b17e-4518-a1b9-a8280d1d95e8\",\"trace_id\":\"ba203ed6-fe98-4fd7-aa62-8de9fb8ffeca\"}\n[2026-04-27 10:30:57] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage for command {\"command\":\"conference:pre-meeting-reminder\",\"memoryBeforeCommandInMb\":60.0,\"memoryAfterCommandInMB\":62.0,\"memoryPeakBeforeCommandInMb\":99.727,\"memoryPeakAfterCommandInMB\":99.727} {\"correlation_id\":\"ec2d49dd-ab36-4f4e-8a60-7bf5ea384cac\",\"trace_id\":\"b3e89960-9a27-4bf7-8b55-95f5704f9a10\"}\n[2026-04-27 10:30:58] local.INFO: [HubSpot Journal Polling] No data {\"correlation_id\":\"1f928e93-b17e-4518-a1b9-a8280d1d95e8\",\"trace_id\":\"ba203ed6-fe98-4fd7-aa62-8de9fb8ffeca\"}\n[2026-04-27 10:31:02] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage before starting command {\"command\":\"crm:reset-governor\",\"memoryBeforeCommandInMb\":60.0,\"memoryPeakBeforeCommandInMb\":99.727} {\"correlation_id\":\"f261cd75-c916-4f21-b4f4-856fe0ed315c\",\"trace_id\":\"6db8819d-ffaa-4fe8-8ccd-0dcc326b8cdc\"}\n[2026-04-27 10:31:02] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage for command {\"command\":\"crm:reset-governor\",\"memoryBeforeCommandInMb\":60.0,\"memoryAfterCommandInMB\":62.0,\"memoryPeakBeforeCommandInMb\":99.727,\"memoryPeakAfterCommandInMB\":99.727} {\"correlation_id\":\"f261cd75-c916-4f21-b4f4-856fe0ed315c\",\"trace_id\":\"6db8819d-ffaa-4fe8-8ccd-0dcc326b8cdc\"}\n[2026-04-27 10:31:03] local.INFO: [HubSpot Journal Polling] Getting offset from database {\"offset\":\"\",\"jiminny_team_id\":1} {\"correlation_id\":\"1f928e93-b17e-4518-a1b9-a8280d1d95e8\",\"trace_id\":\"ba203ed6-fe98-4fd7-aa62-8de9fb8ffeca\"}\n[2026-04-27 10:31:03] local.INFO: [HubSpot Journal API] Fetching latest journal entry {\"url\":\"https://api.hubapi.com/webhooks/v4/journal/latest\"} {\"correlation_id\":\"1f928e93-b17e-4518-a1b9-a8280d1d95e8\",\"trace_id\":\"ba203ed6-fe98-4fd7-aa62-8de9fb8ffeca\"}\n[2026-04-27 10:31:03] local.INFO: [HubSpot Journal Polling] No data {\"correlation_id\":\"1f928e93-b17e-4518-a1b9-a8280d1d95e8\",\"trace_id\":\"ba203ed6-fe98-4fd7-aa62-8de9fb8ffeca\"}\n[2026-04-27 10:31:06] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage before starting command {\"command\":\"activity:sync\",\"memoryBeforeCommandInMb\":60.0,\"memoryPeakBeforeCommandInMb\":99.727} {\"correlation_id\":\"f9151ed9-60aa-4b2b-b962-9eab5f75405b\",\"trace_id\":\"dba432b8-cb4b-4e76-8684-01d96f91e453\"}\n[2026-04-27 10:31:07] local.INFO: Dispatching activity sync job {\"import_id\":811739,\"provider\":\"twilio-flex\",\"team\":\"jiminny\"} {\"correlation_id\":\"f9151ed9-60aa-4b2b-b962-9eab5f75405b\",\"trace_id\":\"dba432b8-cb4b-4e76-8684-01d96f91e453\"}\n[2026-04-27 10:31:07] local.INFO: Dispatching activity sync job {\"import_id\":811740,\"provider\":\"xant\",\"team\":\"jiminny\"} {\"correlation_id\":\"f9151ed9-60aa-4b2b-b962-9eab5f75405b\",\"trace_id\":\"dba432b8-cb4b-4e76-8684-01d96f91e453\"}\n[2026-04-27 10:31:07] local.INFO: Dispatching activity sync job {\"import_id\":811741,\"provider\":\"apollo\",\"team\":\"jiminny\"} {\"correlation_id\":\"f9151ed9-60aa-4b2b-b962-9eab5f75405b\",\"trace_id\":\"dba432b8-cb4b-4e76-8684-01d96f91e453\"}\n[2026-04-27 10:31:07] local.INFO: Dispatching activity sync job {\"import_id\":811742,\"provider\":\"groove\",\"team\":\"jiminny\"} {\"correlation_id\":\"f9151ed9-60aa-4b2b-b962-9eab5f75405b\",\"trace_id\":\"dba432b8-cb4b-4e76-8684-01d96f91e453\"}\n[2026-04-27 10:31:07] local.INFO: Dispatching activity sync job {\"import_id\":811743,\"provider\":\"twilio-video\",\"team\":\"jiminny\"} {\"correlation_id\":\"f9151ed9-60aa-4b2b-b962-9eab5f75405b\",\"trace_id\":\"dba432b8-cb4b-4e76-8684-01d96f91e453\"}\n[2026-04-27 10:31:07] local.INFO: Dispatching activity sync job {\"import_id\":811744,\"provider\":\"hubspot\",\"team\":\"hubspot\"} {\"correlation_id\":\"f9151ed9-60aa-4b2b-b962-9eab5f75405b\",\"trace_id\":\"dba432b8-cb4b-4e76-8684-01d96f91e453\"}\n[2026-04-27 10:31:07] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage for command {\"command\":\"activity:sync\",\"memoryBeforeCommandInMb\":60.0,\"memoryAfterCommandInMB\":62.0,\"memoryPeakBeforeCommandInMb\":99.727,\"memoryPeakAfterCommandInMB\":99.727} {\"correlation_id\":\"f9151ed9-60aa-4b2b-b962-9eab5f75405b\",\"trace_id\":\"dba432b8-cb4b-4e76-8684-01d96f91e453\"}\n[2026-04-27 10:31:07] local.WARNING: [Salesforce] Account not connected for user {\"userId\":\"cdf8b554-d951-4758-bc2b-c1b85d1cd0b9\",\"account\":null} {\"correlation_id\":\"e7a521c5-fad4-48c2-b880-d572b5d237f4\",\"trace_id\":\"dba432b8-cb4b-4e76-8684-01d96f91e453\"}\n[2026-04-27 10:31:07] local.INFO: [CrmOwnerResolver] Integration owner is not connected, attempting team members {\"crm_provider\":\"salesforce\",\"crm_owner\":3,\"team_id\":1} {\"correlation_id\":\"e7a521c5-fad4-48c2-b880-d572b5d237f4\",\"trace_id\":\"dba432b8-cb4b-4e76-8684-01d96f91e453\"}\n[2026-04-27 10:31:07] local.INFO: [SocialAccountService] Fetching token {\"socialAccountId\":1500,\"provider\":\"salesforce\"} {\"correlation_id\":\"e7a521c5-fad4-48c2-b880-d572b5d237f4\",\"trace_id\":\"dba432b8-cb4b-4e76-8684-01d96f91e453\"}\n[2026-04-27 10:31:07] local.INFO: [SocialAccountService] Token retrieved {\"socialAccountId\":1500,\"provider\":\"salesforce\"} {\"correlation_id\":\"e7a521c5-fad4-48c2-b880-d572b5d237f4\",\"trace_id\":\"dba432b8-cb4b-4e76-8684-01d96f91e453\"}\n[2026-04-27 10:31:07] local.INFO: [EncryptedTokenManager] Generating access token. {\"mode\":\"legacy\"} {\"correlation_id\":\"e7a521c5-fad4-48c2-b880-d572b5d237f4\",\"trace_id\":\"dba432b8-cb4b-4e76-8684-01d96f91e453\"}\n[2026-04-27 10:31:07] local.INFO: [CrmOwnerResolver] TeamMember found with active crm connection {\"crm_provider\":\"salesforce\",\"crm_owner\":143,\"team_id\":1} {\"correlation_id\":\"e7a521c5-fad4-48c2-b880-d572b5d237f4\",\"trace_id\":\"dba432b8-cb4b-4e76-8684-01d96f91e453\"}\n[2026-04-27 10:31:07] local.INFO: [SocialAccountService] Fetching token {\"socialAccountId\":1194,\"provider\":\"twilio-flex\"} {\"correlation_id\":\"e7a521c5-fad4-48c2-b880-d572b5d237f4\",\"trace_id\":\"dba432b8-cb4b-4e76-8684-01d96f91e453\"}\n[2026-04-27 10:31:07] local.INFO: [SocialAccountService] Token retrieved {\"socialAccountId\":1194,\"provider\":\"twilio-flex\"} {\"correlation_id\":\"e7a521c5-fad4-48c2-b880-d572b5d237f4\",\"trace_id\":\"dba432b8-cb4b-4e76-8684-01d96f91e453\"}\n[2026-04-27 10:31:07] local.INFO: [EncryptedTokenManager] Generating access token. {\"mode\":\"legacy\"} {\"correlation_id\":\"e7a521c5-fad4-48c2-b880-d572b5d237f4\",\"trace_id\":\"dba432b8-cb4b-4e76-8684-01d96f91e453\"}\n[2026-04-27 10:31:07] local.INFO: [SyncActivity] Start {\"import_id\":811739,\"provider\":\"twilio-flex\",\"provider_id\":317,\"team\":\"jiminny\",\"team_id\":1} {\"correlation_id\":\"e7a521c5-fad4-48c2-b880-d572b5d237f4\",\"trace_id\":\"dba432b8-cb4b-4e76-8684-01d96f91e453\"}\n[2026-04-27 10:31:07] local.NOTICE: [TwilioFlex] Calls import start {\"correlation_id\":\"e7a521c5-fad4-48c2-b880-d572b5d237f4\",\"trace_id\":\"dba432b8-cb4b-4e76-8684-01d96f91e453\"}\n[2026-04-27 10:31:08] local.ALERT: [SyncActivity] Failed {\"import_id\":811739,\"provider\":\"twilio-flex\",\"provider_id\":317,\"team\":\"jiminny\",\"team_id\":1,\"reason\":\"[HTTP 401] Unable to fetch page: Authenticate\",\"file\":\"/home/jiminny/vendor/twilio/sdk/src/Twilio/Page.php\",\"line\":60} {\"correlation_id\":\"e7a521c5-fad4-48c2-b880-d572b5d237f4\",\"trace_id\":\"dba432b8-cb4b-4e76-8684-01d96f91e453\"}\n[2026-04-27 10:31:08] local.INFO: [HubSpot Journal Polling] Getting offset from database {\"offset\":\"\",\"jiminny_team_id\":1} {\"correlation_id\":\"1f928e93-b17e-4518-a1b9-a8280d1d95e8\",\"trace_id\":\"ba203ed6-fe98-4fd7-aa62-8de9fb8ffeca\"}\n[2026-04-27 10:31:08] local.INFO: [HubSpot Journal API] Fetching latest journal entry {\"url\":\"https://api.hubapi.com/webhooks/v4/journal/latest\"} {\"correlation_id\":\"1f928e93-b17e-4518-a1b9-a8280d1d95e8\",\"trace_id\":\"ba203ed6-fe98-4fd7-aa62-8de9fb8ffeca\"}\n[2026-04-27 10:31:09] local.INFO: [SocialAccountService] Fetching token {\"socialAccountId\":1500,\"provider\":\"salesforce\"} {\"correlation_id\":\"73848601-79bb-4161-91ae-37acf4385d24\",\"trace_id\":\"dba432b8-cb4b-4e76-8684-01d96f91e453\"}\n[2026-04-27 10:31:09] local.INFO: [SocialAccountService] Token retrieved {\"socialAccountId\":1500,\"provider\":\"salesforce\"} {\"correlation_id\":\"73848601-79bb-4161-91ae-37acf4385d24\",\"trace_id\":\"dba432b8-cb4b-4e76-8684-01d96f91e453\"}\n[2026-04-27 10:31:09] local.INFO: [EncryptedTokenManager] Generating access token. {\"mode\":\"legacy\"} {\"correlation_id\":\"73848601-79bb-4161-91ae-37acf4385d24\",\"trace_id\":\"dba432b8-cb4b-4e76-8684-01d96f91e453\"}\n[2026-04-27 10:31:09] local.INFO: [CrmOwnerResolver] Integration owner matched as CRM Owner {\"crm_provider\":\"salesforce\",\"crm_owner\":143,\"team_id\":1} {\"correlation_id\":\"73848601-79bb-4161-91ae-37acf4385d24\",\"trace_id\":\"dba432b8-cb4b-4e76-8684-01d96f91e453\"}\n[2026-04-27 10:31:09] local.INFO: [SocialAccountService] Fetching token {\"socialAccountId\":1500,\"provider\":\"salesforce\"} {\"correlation_id\":\"73848601-79bb-4161-91ae-37acf4385d24\",\"trace_id\":\"dba432b8-cb4b-4e76-8684-01d96f91e453\"}\n[2026-04-27 10:31:09] local.INFO: [HubSpot Journal Polling] No data {\"correlation_id\":\"1f928e93-b17e-4518-a1b9-a8280d1d95e8\",\"trace_id\":\"ba203ed6-fe98-4fd7-aa62-8de9fb8ffeca\"}\n[2026-04-27 10:31:09] local.INFO: [SocialAccountService] Token retrieved {\"socialAccountId\":1500,\"provider\":\"salesforce\"} {\"correlation_id\":\"73848601-79bb-4161-91ae-37acf4385d24\",\"trace_id\":\"dba432b8-cb4b-4e76-8684-01d96f91e453\"}\n[2026-04-27 10:31:09] local.INFO: [EncryptedTokenManager] Generating access token. {\"mode\":\"legacy\"} {\"correlation_id\":\"73848601-79bb-4161-91ae-37acf4385d24\",\"trace_id\":\"dba432b8-cb4b-4e76-8684-01d96f91e453\"}\n[2026-04-27 10:31:09] local.INFO: [SyncActivity] Start {\"import_id\":811740,\"provider\":\"xant\",\"provider_id\":161,\"team\":\"jiminny\",\"team_id\":1} {\"correlation_id\":\"73848601-79bb-4161-91ae-37acf4385d24\",\"trace_id\":\"dba432b8-cb4b-4e76-8684-01d96f91e453\"}\n[2026-04-27 10:31:09] local.INFO: [Salesforce] Performing query {\"query\":\"\n SELECT Playbooks_Call_Date__c,Playbooks_Call_Recording__c,CreatedDate,TaskSubtype,CallType,CallDurationInSeconds,Id,OwnerId,WhoId,WhatId,Priority,ActivityDate,Subject,Description,Status,Type\n FROM Task\n WHERE IsDeleted = false\n AND LastModifiedDate >= :from\n AND LastModifiedDate <= :to\n ORDER BY LastModifiedDate ASC\n LIMIT :limit\",\"params\":{\"from\":\"2026-04-27T10:14:00Z\",\"to\":\"2026-04-27T10:30:00Z\",\"ownerId\":null,\"subType\":null,\"limit\":5000}} {\"correlation_id\":\"73848601-79bb-4161-91ae-37acf4385d24\",\"trace_id\":\"dba432b8-cb4b-4e76-8684-01d96f91e453\"}\n[2026-04-27 10:31:09] local.INFO: [Salesforce] Sending request {\"endpoint\":\"https://jiminny--stagingenv.sandbox.my.salesforce.com/services/data/v50.0/query/?q=%0A++++++++++++SELECT+Playbooks_Call_Date__c%2CPlaybooks_Call_Recording__c%2CCreatedDate%2CTaskSubtype%2CCallType%2CCallDurationInSeconds%2CId%2COwnerId%2CWhoId%2CWhatId%2CPriority%2CActivityDate%2CSubject%2CDescription%2CStatus%2CType%0A++++++++++++++FROM+Task%0A+++++++++++++WHERE+IsDeleted+%3D+false%0A+++++++++++++++AND+LastModifiedDate+%3E%3D+2026-04-27T10%3A14%3A00Z%0A+++++++++++++++AND+LastModifiedDate+%3C%3D+2026-04-27T10%3A30%3A00Z%0A++++++++++ORDER+BY+LastModifiedDate+ASC%0A+++++++++++++LIMIT+5000 GET\",\"team_id\":1} {\"correlation_id\":\"73848601-79bb-4161-91ae-37acf4385d24\",\"trace_id\":\"dba432b8-cb4b-4e76-8684-01d96f91e453\"}\n[2026-04-27 10:31:09] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage before starting command {\"command\":\"mailbox:batch:fail-stalled\",\"memoryBeforeCommandInMb\":60.0,\"memoryPeakBeforeCommandInMb\":99.727} {\"correlation_id\":\"0d30446d-c469-4ade-80dc-f57f997ea610\",\"trace_id\":\"61eea326-3f40-4a50-a5e6-bddd7fa25d27\"}\n[2026-04-27 10:31:09] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage for command {\"command\":\"mailbox:batch:fail-stalled\",\"memoryBeforeCommandInMb\":60.0,\"memoryAfterCommandInMB\":62.0,\"memoryPeakBeforeCommandInMb\":99.727,\"memoryPeakAfterCommandInMB\":99.727} {\"correlation_id\":\"0d30446d-c469-4ade-80dc-f57f997ea610\",\"trace_id\":\"61eea326-3f40-4a50-a5e6-bddd7fa25d27\"}\n[2026-04-27 10:31:09] local.INFO: [Xant (InsideSales)] No calls found. {\"correlation_id\":\"73848601-79bb-4161-91ae-37acf4385d24\",\"trace_id\":\"dba432b8-cb4b-4e76-8684-01d96f91e453\"}\n[2026-04-27 10:31:09] local.INFO: [SyncActivity] End {\"import_id\":811740,\"provider\":\"xant\",\"provider_id\":161,\"team\":\"jiminny\",\"team_id\":1} {\"correlation_id\":\"73848601-79bb-4161-91ae-37acf4385d24\",\"trace_id\":\"dba432b8-cb4b-4e76-8684-01d96f91e453\"}\n[2026-04-27 10:31:09] local.INFO: [SyncActivity] Memory usage {\"import_id\":811740,\"provider\":\"xant\",\"provider_id\":161,\"team\":\"jiminny\",\"team_id\":1,\"memory_usage\":25149472,\"memory_real_usage\":65011712,\"pid\":13616} {\"correlation_id\":\"73848601-79bb-4161-91ae-37acf4385d24\",\"trace_id\":\"dba432b8-cb4b-4e76-8684-01d96f91e453\"}\n[2026-04-27 10:31:09] local.INFO: [SocialAccountService] Fetching token {\"socialAccountId\":1500,\"provider\":\"salesforce\"} {\"correlation_id\":\"11c977fb-9603-4dde-a4d7-f821d47fcdc9\",\"trace_id\":\"dba432b8-cb4b-4e76-8684-01d96f91e453\"}\n[2026-04-27 10:31:09] local.INFO: [SocialAccountService] Token retrieved {\"socialAccountId\":1500,\"provider\":\"salesforce\"} {\"correlation_id\":\"11c977fb-9603-4dde-a4d7-f821d47fcdc9\",\"trace_id\":\"dba432b8-cb4b-4e76-8684-01d96f91e453\"}\n[2026-04-27 10:31:09] local.INFO: [EncryptedTokenManager] Generating access token. {\"mode\":\"legacy\"} {\"correlation_id\":\"11c977fb-9603-4dde-a4d7-f821d47fcdc9\",\"trace_id\":\"dba432b8-cb4b-4e76-8684-01d96f91e453\"}\n[2026-04-27 10:31:09] local.INFO: [CrmOwnerResolver] Integration owner matched as CRM Owner {\"crm_provider\":\"salesforce\",\"crm_owner\":143,\"team_id\":1} {\"correlation_id\":\"11c977fb-9603-4dde-a4d7-f821d47fcdc9\",\"trace_id\":\"dba432b8-cb4b-4e76-8684-01d96f91e453\"}\n[2026-04-27 10:31:09] local.INFO: [SocialAccountService] Fetching token {\"socialAccountId\":1500,\"provider\":\"salesforce\"} {\"correlation_id\":\"11c977fb-9603-4dde-a4d7-f821d47fcdc9\",\"trace_id\":\"dba432b8-cb4b-4e76-8684-01d96f91e453\"}\n[2026-04-27 10:31:09] local.INFO: [SocialAccountService] Token retrieved {\"socialAccountId\":1500,\"provider\":\"salesforce\"} {\"correlation_id\":\"11c977fb-9603-4dde-a4d7-f821d47fcdc9\",\"trace_id\":\"dba432b8-cb4b-4e76-8684-01d96f91e453\"}\n[2026-04-27 10:31:09] local.INFO: [EncryptedTokenManager] Generating access token. {\"mode\":\"legacy\"} {\"correlation_id\":\"11c977fb-9603-4dde-a4d7-f821d47fcdc9\",\"trace_id\":\"dba432b8-cb4b-4e76-8684-01d96f91e453\"}\n[2026-04-27 10:31:09] local.INFO: [SyncActivity] Start {\"import_id\":811741,\"provider\":\"apollo\",\"provider_id\":441,\"team\":\"jiminny\",\"team_id\":1} {\"correlation_id\":\"11c977fb-9603-4dde-a4d7-f821d47fcdc9\",\"trace_id\":\"dba432b8-cb4b-4e76-8684-01d96f91e453\"}\n[2026-04-27 10:31:09] local.INFO: [Salesforce] Performing query {\"query\":\"\n SELECT AccountId,CreatedDate,TaskSubtype,CallType,Id,OwnerId,WhoId,WhatId,Priority,ActivityDate,Subject,Description,Status,Type\n FROM Task\n WHERE IsDeleted = false\n AND LastModifiedDate >= :from\n AND LastModifiedDate <= :to\n ORDER BY LastModifiedDate ASC\n LIMIT :limit\",\"params\":{\"from\":\"2026-04-27T10:14:00Z\",\"to\":\"2026-04-27T10:30:00Z\",\"ownerId\":null,\"subType\":null,\"limit\":5000}} {\"correlation_id\":\"11c977fb-9603-4dde-a4d7-f821d47fcdc9\",\"trace_id\":\"dba432b8-cb4b-4e76-8684-01d96f91e453\"}\n[2026-04-27 10:31:09] local.INFO: [Salesforce] Sending request {\"endpoint\":\"https://jiminny--stagingenv.sandbox.my.salesforce.com/services/data/v50.0/query/?q=%0A++++++++++++SELECT+AccountId%2CCreatedDate%2CTaskSubtype%2CCallType%2CId%2COwnerId%2CWhoId%2CWhatId%2CPriority%2CActivityDate%2CSubject%2CDescription%2CStatus%2CType%0A++++++++++++++FROM+Task%0A+++++++++++++WHERE+IsDeleted+%3D+false%0A+++++++++++++++AND+LastModifiedDate+%3E%3D+2026-04-27T10%3A14%3A00Z%0A+++++++++++++++AND+LastModifiedDate+%3C%3D+2026-04-27T10%3A30%3A00Z%0A++++++++++ORDER+BY+LastModifiedDate+ASC%0A+++++++++++++LIMIT+5000 GET\",\"team_id\":1} {\"correlation_id\":\"11c977fb-9603-4dde-a4d7-f821d47fcdc9\",\"trace_id\":\"dba432b8-cb4b-4e76-8684-01d96f91e453\"}\n[2026-04-27 10:31:10] local.INFO: [Apollo] No calls found. {\"correlation_id\":\"11c977fb-9603-4dde-a4d7-f821d47fcdc9\",\"trace_id\":\"dba432b8-cb4b-4e76-8684-01d96f91e453\"}\n[2026-04-27 10:31:10] local.INFO: [SyncActivity] End {\"import_id\":811741,\"provider\":\"apollo\",\"provider_id\":441,\"team\":\"jiminny\",\"team_id\":1} {\"correlation_id\":\"11c977fb-9603-4dde-a4d7-f821d47fcdc9\",\"trace_id\":\"dba432b8-cb4b-4e76-8684-01d96f91e453\"}\n[2026-04-27 10:31:10] local.INFO: [SyncActivity] Memory usage {\"import_id\":811741,\"provider\":\"apollo\",\"provider_id\":441,\"team\":\"jiminny\",\"team_id\":1,\"memory_usage\":25527752,\"memory_real_usage\":65011712,\"pid\":13616} {\"correlation_id\":\"11c977fb-9603-4dde-a4d7-f821d47fcdc9\",\"trace_id\":\"dba432b8-cb4b-4e76-8684-01d96f91e453\"}\n[2026-04-27 10:31:10] local.INFO: [SocialAccountService] Fetching token {\"socialAccountId\":1500,\"provider\":\"salesforce\"} {\"correlation_id\":\"856f8092-8c5e-409d-8aa6-ffb8efd8bfa1\",\"trace_id\":\"dba432b8-cb4b-4e76-8684-01d96f91e453\"}\n[2026-04-27 10:31:10] local.INFO: [SocialAccountService] Token retrieved {\"socialAccountId\":1500,\"provider\":\"salesforce\"} {\"correlation_id\":\"856f8092-8c5e-409d-8aa6-ffb8efd8bfa1\",\"trace_id\":\"dba432b8-cb4b-4e76-8684-01d96f91e453\"}\n[2026-04-27 10:31:10] local.INFO: [EncryptedTokenManager] Generating access token. {\"mode\":\"legacy\"} {\"correlation_id\":\"856f8092-8c5e-409d-8aa6-ffb8efd8bfa1\",\"trace_id\":\"dba432b8-cb4b-4e76-8684-01d96f91e453\"}\n[2026-04-27 10:31:10] local.INFO: [CrmOwnerResolver] Integration owner matched as CRM Owner {\"crm_provider\":\"salesforce\",\"crm_owner\":143,\"team_id\":1} {\"correlation_id\":\"856f8092-8c5e-409d-8aa6-ffb8efd8bfa1\",\"trace_id\":\"dba432b8-cb4b-4e76-8684-01d96f91e453\"}\n[2026-04-27 10:31:10] local.INFO: [SocialAccountService] Fetching token {\"socialAccountId\":1500,\"provider\":\"salesforce\"} {\"correlation_id\":\"856f8092-8c5e-409d-8aa6-ffb8efd8bfa1\",\"trace_id\":\"dba432b8-cb4b-4e76-8684-01d96f91e453\"}\n[2026-04-27 10:31:10] local.INFO: [SocialAccountService] Token retrieved {\"socialAccountId\":1500,\"provider\":\"salesforce\"} {\"correlation_id\":\"856f8092-8c5e-409d-8aa6-ffb8efd8bfa1\",\"trace_id\":\"dba432b8-cb4b-4e76-8684-01d96f91e453\"}\n[2026-04-27 10:31:10] local.INFO: [EncryptedTokenManager] Generating access token. {\"mode\":\"legacy\"} {\"correlation_id\":\"856f8092-8c5e-409d-8aa6-ffb8efd8bfa1\",\"trace_id\":\"dba432b8-cb4b-4e76-8684-01d96f91e453\"}\n[2026-04-27 10:31:10] local.INFO: [SyncActivity] Start {\"import_id\":811742,\"provider\":\"groove\",\"provider_id\":228,\"team\":\"jiminny\",\"team_id\":1} {\"correlation_id\":\"856f8092-8c5e-409d-8aa6-ffb8efd8bfa1\",\"trace_id\":\"dba432b8-cb4b-4e76-8684-01d96f91e453\"}\n[2026-04-27 10:31:10] local.INFO: [Salesforce] Performing query {\"query\":\"\n SELECT call_recording_url__c,TaskSubtype,CreatedDate,CallType,CallDurationInSeconds,Id,OwnerId,WhoId,WhatId,Priority,ActivityDate,Subject,Description,Status,Type\n FROM Task\n WHERE IsDeleted = false\n AND LastModifiedDate >= :from\n AND LastModifiedDate <= :to\n ORDER BY LastModifiedDate ASC\n LIMIT :limit\",\"params\":{\"from\":\"2026-04-27T10:14:00Z\",\"to\":\"2026-04-27T10:30:00Z\",\"ownerId\":null,\"subType\":null,\"limit\":5000}} {\"correlation_id\":\"856f8092-8c5e-409d-8aa6-ffb8efd8bfa1\",\"trace_id\":\"dba432b8-cb4b-4e76-8684-01d96f91e453\"}\n[2026-04-27 10:31:10] local.INFO: [Salesforce] Sending request {\"endpoint\":\"https://jiminny--stagingenv.sandbox.my.salesforce.com/services/data/v50.0/query/?q=%0A++++++++++++SELECT+call_recording_url__c%2CTaskSubtype%2CCreatedDate%2CCallType%2CCallDurationInSeconds%2CId%2COwnerId%2CWhoId%2CWhatId%2CPriority%2CActivityDate%2CSubject%2CDescription%2CStatus%2CType%0A++++++++++++++FROM+Task%0A+++++++++++++WHERE+IsDeleted+%3D+false%0A+++++++++++++++AND+LastModifiedDate+%3E%3D+2026-04-27T10%3A14%3A00Z%0A+++++++++++++++AND+LastModifiedDate+%3C%3D+2026-04-27T10%3A30%3A00Z%0A++++++++++ORDER+BY+LastModifiedDate+ASC%0A+++++++++++++LIMIT+5000 GET\",\"team_id\":1} {\"correlation_id\":\"856f8092-8c5e-409d-8aa6-ffb8efd8bfa1\",\"trace_id\":\"dba432b8-cb4b-4e76-8684-01d96f91e453\"}\n[2026-04-27 10:31:10] local.ERROR: [Salesforce] Request exception [400] \nSELECT call_recording_url__c,TaskSubtype\n ^\nERROR at Row:1:Column:8\nNo such column 'call_recording_url__c' on entity 'Task'. If you are attempting to use a custom field, be sure to append the '__c' after the custom field name. Please reference your WSDL or the describe call for the appropriate names. {\"url\":\"https://jiminny--stagingenv.sandbox.my.salesforce.com/services/data/v50.0/query/?q=%0A++++++++++++SELECT+call_recording_url__c%2CTaskSubtype%2CCreatedDate%2CCallType%2CCallDurationInSeconds%2CId%2COwnerId%2CWhoId%2CWhatId%2CPriority%2CActivityDate%2CSubject%2CDescription%2CStatus%2CType%0A++++++++++++++FROM+Task%0A+++++++++++++WHERE+IsDeleted+%3D+false%0A+++++++++++++++AND+LastModifiedDate+%3E%3D+2026-04-27T10%3A14%3A00Z%0A+++++++++++++++AND+LastModifiedDate+%3C%3D+2026-04-27T10%3A30%3A00Z%0A++++++++++ORDER+BY+LastModifiedDate+ASC%0A+++++++++++++LIMIT+5000\",\"data\":{\"headers\":{\"Authorization\":\"Bearer 00D2g0000008hH4!AQEAQC41CLa_xP1bAbi9E2OUbAOpW..Wx961OtqTwJ3oWpOKs6yPdm47B4yKRBHbRmmJ.Lvd34sjr0gnAkV8fLb8IZWzIW93\"}},\"response\":{\"GuzzleHttp\\\\Psr7\\\\Stream\":\"[{\\\"message\\\":\\\"\\\\nSELECT call_recording_url__c,TaskSubtype\\\\n ^\\\\nERROR at Row:1:Column:8\\\\nNo such column 'call_recording_url__c' on entity 'Task'. If you are attempting to use a custom field, be sure to append the '__c' after the custom field name. Please reference your WSDL or the describe call for the appropriate names.\\\",\\\"errorCode\\\":\\\"INVALID_FIELD\\\"}]\"},\"fields\":[]} {\"correlation_id\":\"856f8092-8c5e-409d-8aa6-ffb8efd8bfa1\",\"trace_id\":\"dba432b8-cb4b-4e76-8684-01d96f91e453\"}\n[2026-04-27 10:31:10] local.ALERT: [SyncActivity] Failed {\"import_id\":811742,\"provider\":\"groove\",\"provider_id\":228,\"team\":\"jiminny\",\"team_id\":1,\"reason\":\"\nSELECT call_recording_url__c,TaskSubtype\n ^\nERROR at Row:1:Column:8\nNo such column 'call_recording_url__c' on entity 'Task'. If you are attempting to use a custom field, be sure to append the '__c' after the custom field name. Please reference your WSDL or the describe call for the appropriate names.\",\"file\":\"/home/jiminny/app/Services/Crm/Salesforce/Client.php\",\"line\":564} {\"correlation_id\":\"856f8092-8c5e-409d-8aa6-ffb8efd8bfa1\",\"trace_id\":\"dba432b8-cb4b-4e76-8684-01d96f91e453\"}\n[2026-04-27 10:31:10] local.INFO: [SocialAccountService] Fetching token {\"socialAccountId\":1500,\"provider\":\"salesforce\"} {\"correlation_id\":\"0ec6aef6-345c-4602-b6a1-646b95e481c0\",\"trace_id\":\"dba432b8-cb4b-4e76-8684-01d96f91e453\"}\n[2026-04-27 10:31:10] local.INFO: [SocialAccountService] Token retrieved {\"socialAccountId\":1500,\"provider\":\"salesforce\"} {\"correlation_id\":\"0ec6aef6-345c-4602-b6a1-646b95e481c0\",\"trace_id\":\"dba432b8-cb4b-4e76-8684-01d96f91e453\"}\n[2026-04-27 10:31:10] local.INFO: [EncryptedTokenManager] Generating access token. {\"mode\":\"legacy\"} {\"correlation_id\":\"0ec6aef6-345c-4602-b6a1-646b95e481c0\",\"trace_id\":\"dba432b8-cb4b-4e76-8684-01d96f91e453\"}\n[2026-04-27 10:31:10] local.INFO: [CrmOwnerResolver] Integration owner matched as CRM Owner {\"crm_provider\":\"salesforce\",\"crm_owner\":143,\"team_id\":1} {\"correlation_id\":\"0ec6aef6-345c-4602-b6a1-646b95e481c0\",\"trace_id\":\"dba432b8-cb4b-4e76-8684-01d96f91e453\"}\n[2026-04-27 10:31:10] local.INFO: [SocialAccountService] Fetching token {\"socialAccountId\":1500,\"provider\":\"salesforce\"} {\"correlation_id\":\"0ec6aef6-345c-4602-b6a1-646b95e481c0\",\"trace_id\":\"dba432b8-cb4b-4e76-8684-01d96f91e453\"}\n[2026-04-27 10:31:10] local.INFO: [SocialAccountService] Token retrieved {\"socialAccountId\":1500,\"provider\":\"salesforce\"} {\"correlation_id\":\"0ec6aef6-345c-4602-b6a1-646b95e481c0\",\"trace_id\":\"dba432b8-cb4b-4e76-8684-01d96f91e453\"}\n[2026-04-27 10:31:10] local.INFO: [EncryptedTokenManager] Generating access token. {\"mode\":\"legacy\"} {\"correlation_id\":\"0ec6aef6-345c-4602-b6a1-646b95e481c0\",\"trace_id\":\"dba432b8-cb4b-4e76-8684-01d96f91e453\"}\n[2026-04-27 10:31:10] local.INFO: [SyncActivity] Start {\"import_id\":811743,\"provider\":\"twilio-video\",\"provider_id\":243,\"team\":\"jiminny\",\"team_id\":1} {\"correlation_id\":\"0ec6aef6-345c-4602-b6a1-646b95e481c0\",\"trace_id\":\"dba432b8-cb4b-4e76-8684-01d96f91e453\"}\n[2026-04-27 10:31:10] local.INFO: [Salesforce] Performing query {\"query\":\"SELECT Id,OwnerId,WhoId,WhatId,Priority,ActivityDate,Subject,Description,Status,Type,twilio_call_sid__c,Lead_UUID__c,Opportunity__c\n FROM Task\n WHERE Type = 'Video'\n AND isClosed = true\n AND IsDeleted = false\n AND LastModifiedDate >= :from\n AND twilio_call_sid__c != NULL AND LastModifiedDate <= :to ORDER BY LastModifiedDate ASC\n LIMIT :limit\",\"params\":{\"from\":\"2026-04-27T10:14:00Z\",\"to\":\"2026-04-27T10:30:00Z\",\"ownerId\":null,\"subType\":null,\"limit\":5000}} {\"correlation_id\":\"0ec6aef6-345c-4602-b6a1-646b95e481c0\",\"trace_id\":\"dba432b8-cb4b-4e76-8684-01d96f91e453\"}\n[2026-04-27 10:31:10] local.INFO: [Salesforce] Sending request {\"endpoint\":\"https://jiminny--stagingenv.sandbox.my.salesforce.com/services/data/v50.0/query/?q=SELECT+Id%2COwnerId%2CWhoId%2CWhatId%2CPriority%2CActivityDate%2CSubject%2CDescription%2CStatus%2CType%2Ctwilio_call_sid__c%2CLead_UUID__c%2COpportunity__c%0A++++++++++++++FROM+Task%0A++++++++++++WHERE+Type+%3D+%27Video%27%0A++++++++++++++AND+isClosed+%3D+true%0A++++++++++++++AND+IsDeleted+%3D+false%0A++++++++++++++AND+LastModifiedDate+%3E%3D+2026-04-27T10%3A14%3A00Z%0A++++++++++++++AND+twilio_call_sid__c+%21%3D+NULL+AND+LastModifiedDate+%3C%3D+2026-04-27T10%3A30%3A00Z+ORDER+BY+LastModifiedDate+ASC%0A+++++++++++++LIMIT+5000 GET\",\"team_id\":1} {\"correlation_id\":\"0ec6aef6-345c-4602-b6a1-646b95e481c0\",\"trace_id\":\"dba432b8-cb4b-4e76-8684-01d96f91e453\"}\n[2026-04-27 10:31:11] local.INFO: [Twilio Video] No calls found. {\"correlation_id\":\"0ec6aef6-345c-4602-b6a1-646b95e481c0\",\"trace_id\":\"dba432b8-cb4b-4e76-8684-01d96f91e453\"}\n[2026-04-27 10:31:11] local.INFO: [SyncActivity] End {\"import_id\":811743,\"provider\":\"twilio-video\",\"provider_id\":243,\"team\":\"jiminny\",\"team_id\":1} {\"correlation_id\":\"0ec6aef6-345c-4602-b6a1-646b95e481c0\",\"trace_id\":\"dba432b8-cb4b-4e76-8684-01d96f91e453\"}\n[2026-04-27 10:31:11] local.INFO: [SyncActivity] Memory usage {\"import_id\":811743,\"provider\":\"twilio-video\",\"provider_id\":243,\"team\":\"jiminny\",\"team_id\":1,\"memory_usage\":25679680,\"memory_real_usage\":65011712,\"pid\":13616} {\"correlation_id\":\"0ec6aef6-345c-4602-b6a1-646b95e481c0\",\"trace_id\":\"dba432b8-cb4b-4e76-8684-01d96f91e453\"}\n[2026-04-27 10:31:11] local.INFO: [SocialAccountService] Fetching token {\"socialAccountId\":1499,\"provider\":\"hubspot\"} {\"correlation_id\":\"3b9d7e85-b8c2-4ae2-863e-c343c277afdc\",\"trace_id\":\"dba432b8-cb4b-4e76-8684-01d96f91e453\"}\n[2026-04-27 10:31:11] local.INFO: [SocialAccountService] Token retrieved {\"socialAccountId\":1499,\"provider\":\"hubspot\"} {\"correlation_id\":\"3b9d7e85-b8c2-4ae2-863e-c343c277afdc\",\"trace_id\":\"dba432b8-cb4b-4e76-8684-01d96f91e453\"}\n[2026-04-27 10:31:11] local.INFO: [EncryptedTokenManager] Generating access token. {\"mode\":\"legacy\"} {\"correlation_id\":\"3b9d7e85-b8c2-4ae2-863e-c343c277afdc\",\"trace_id\":\"dba432b8-cb4b-4e76-8684-01d96f91e453\"}\n[2026-04-27 10:31:11] local.INFO: [CrmOwnerResolver] Integration owner matched as CRM Owner {\"crm_provider\":\"hubspot\",\"crm_owner\":89,\"team_id\":2} {\"correlation_id\":\"3b9d7e85-b8c2-4ae2-863e-c343c277afdc\",\"trace_id\":\"dba432b8-cb4b-4e76-8684-01d96f91e453\"}\n[2026-04-27 10:31:11] local.INFO: [SocialAccountService] Fetching token {\"socialAccountId\":408,\"provider\":\"hubspot\"} {\"correlation_id\":\"3b9d7e85-b8c2-4ae2-863e-c343c277afdc\",\"trace_id\":\"dba432b8-cb4b-4e76-8684-01d96f91e453\"}\n[2026-04-27 10:31:11] local.INFO: [SocialAccountService] Token needs refreshing {\"socialAccountId\":408,\"provider\":\"hubspot\"} {\"correlation_id\":\"3b9d7e85-b8c2-4ae2-863e-c343c277afdc\",\"trace_id\":\"dba432b8-cb4b-4e76-8684-01d96f91e453\"}\n[2026-04-27 10:31:11] local.INFO: [EncryptedTokenManager] Generating access token. {\"mode\":\"legacy\"} {\"correlation_id\":\"3b9d7e85-b8c2-4ae2-863e-c343c277afdc\",\"trace_id\":\"dba432b8-cb4b-4e76-8684-01d96f91e453\"}\n[2026-04-27 10:31:11] local.INFO: [SocialAccountService] Refreshing token from provider {\"socialAccountId\":408,\"provider\":\"hubspot\",\"refreshToken\":\"de4e47eb985578f4218833e763e31059e88b562e87e10749b3389be2328f0aa7\",\"state\":\"connected\"} {\"correlation_id\":\"3b9d7e85-b8c2-4ae2-863e-c343c277afdc\",\"trace_id\":\"dba432b8-cb4b-4e76-8684-01d96f91e453\"}\n[2026-04-27 10:31:11] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage before starting command {\"command\":\"crm:bullhorn:ping\",\"memoryBeforeCommandInMb\":60.0,\"memoryPeakBeforeCommandInMb\":99.727} {\"correlation_id\":\"a2085168-092a-44b0-8dd2-c8845bc36f86\",\"trace_id\":\"9445425b-6aaf-4a23-9323-ecf85939f967\"}\n[2026-04-27 10:31:11] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage for command {\"command\":\"crm:bullhorn:ping\",\"memoryBeforeCommandInMb\":60.0,\"memoryAfterCommandInMB\":62.0,\"memoryPeakBeforeCommandInMb\":99.727,\"memoryPeakAfterCommandInMB\":99.727} {\"correlation_id\":\"a2085168-092a-44b0-8dd2-c8845bc36f86\",\"trace_id\":\"9445425b-6aaf-4a23-9323-ecf85939f967\"}\n[2026-04-27 10:31:12] local.INFO: [SocialAccountObserver] Saving model {\"correlation_id\":\"3b9d7e85-b8c2-4ae2-863e-c343c277afdc\",\"trace_id\":\"dba432b8-cb4b-4e76-8684-01d96f91e453\"}","depth":4,"bounds":{"left":0.52859044,"top":0.0726257,"width":0.45977393,"height":0.9273743},"on_screen":true,"value":"[2026-04-27 10:26:25] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage before starting command {\"command\":\"mailbox:sync\",\"memoryBeforeCommandInMb\":60.0,\"memoryPeakBeforeCommandInMb\":99.727} {\"correlation_id\":\"7b1ea2fb-8f36-4897-9970-91b4abba2cc7\",\"trace_id\":\"265be7c0-bf8f-4037-a0d2-57df3ef1d7e1\"}\n[2026-04-27 10:26:25] local.INFO: [EmailSchedule] STARTING Inbox Sync {\"host\":\"docker_lamp_1\"} {\"correlation_id\":\"7b1ea2fb-8f36-4897-9970-91b4abba2cc7\",\"trace_id\":\"265be7c0-bf8f-4037-a0d2-57df3ef1d7e1\"}\n[2026-04-27 10:26:25] local.INFO: [EmailSchedule] FINISHED Inbox Sync {\"host\":\"docker_lamp_1\",\"events\":2} {\"correlation_id\":\"7b1ea2fb-8f36-4897-9970-91b4abba2cc7\",\"trace_id\":\"265be7c0-bf8f-4037-a0d2-57df3ef1d7e1\"}\n[2026-04-27 10:26:25] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage for command {\"command\":\"mailbox:sync\",\"memoryBeforeCommandInMb\":60.0,\"memoryAfterCommandInMB\":62.0,\"memoryPeakBeforeCommandInMb\":99.727,\"memoryPeakAfterCommandInMB\":99.727} {\"correlation_id\":\"7b1ea2fb-8f36-4897-9970-91b4abba2cc7\",\"trace_id\":\"265be7c0-bf8f-4037-a0d2-57df3ef1d7e1\"}\n[2026-04-27 10:26:26] local.INFO: [automated-reports] Started {\"correlation_id\":\"d3f79034-397b-4936-9305-bdd99b23f2dd\",\"trace_id\":\"aa0f8702-73eb-4947-a765-2e89244599c3\"}\n[2026-04-27 10:26:26] local.INFO: [automated-reports] Checking conditions {\"isMonday\":true,\"isWeekend\":false,\"isFirstDayOfMonth\":false,\"currentMonth\":4,\"isQuarterlyMonth\":true} {\"correlation_id\":\"d3f79034-397b-4936-9305-bdd99b23f2dd\",\"trace_id\":\"aa0f8702-73eb-4947-a765-2e89244599c3\"}\n[2026-04-27 10:26:26] local.INFO: [automated-reports] Processing daily reports {\"correlation_id\":\"d3f79034-397b-4936-9305-bdd99b23f2dd\",\"trace_id\":\"aa0f8702-73eb-4947-a765-2e89244599c3\"}\n[2026-04-27 10:26:26] local.INFO: [automated-reports] Found 1 daily reports to process {\"correlation_id\":\"d3f79034-397b-4936-9305-bdd99b23f2dd\",\"trace_id\":\"aa0f8702-73eb-4947-a765-2e89244599c3\"}\n[2026-04-27 10:26:26] local.INFO: [automated-reports] Dispatching Generate Report job for report {\"reportUuid\":\"4f6ca2b5-1993-48aa-99ad-b66f19f15d43\",\"teamId\":1,\"frequency\":\"weekly\",\"type\":\"ask_jiminny\"} {\"correlation_id\":\"d3f79034-397b-4936-9305-bdd99b23f2dd\",\"trace_id\":\"aa0f8702-73eb-4947-a765-2e89244599c3\"}\n[2026-04-27 10:26:26] local.INFO: [automated-reports] Processing weekly reports {\"correlation_id\":\"d3f79034-397b-4936-9305-bdd99b23f2dd\",\"trace_id\":\"aa0f8702-73eb-4947-a765-2e89244599c3\"}\n[2026-04-27 10:26:26] local.INFO: [automated-reports] Found 1 weekly reports to process {\"correlation_id\":\"d3f79034-397b-4936-9305-bdd99b23f2dd\",\"trace_id\":\"aa0f8702-73eb-4947-a765-2e89244599c3\"}\n[2026-04-27 10:26:26] local.INFO: [automated-reports] Dispatching Generate Report job for report {\"reportUuid\":\"4f6ca2b5-1993-48aa-99ad-b66f19f15d43\",\"teamId\":1,\"frequency\":\"weekly\",\"type\":\"ask_jiminny\"} {\"correlation_id\":\"d3f79034-397b-4936-9305-bdd99b23f2dd\",\"trace_id\":\"aa0f8702-73eb-4947-a765-2e89244599c3\"}\n[2026-04-27 10:26:26] local.INFO: [automated-reports] Completed {\"correlation_id\":\"d3f79034-397b-4936-9305-bdd99b23f2dd\",\"trace_id\":\"aa0f8702-73eb-4947-a765-2e89244599c3\"}\n[2026-04-27 10:26:26] local.INFO: [HubSpot Journal Polling] Getting offset from database {\"offset\":\"\",\"jiminny_team_id\":1} {\"correlation_id\":\"b453f0f4-0d2c-491c-a935-14c60265ec18\",\"trace_id\":\"a50202d6-5601-46b4-a02e-cf5ac8d061fa\"}\n[2026-04-27 10:26:26] local.INFO: [HubSpot Journal API] Fetching latest journal entry {\"url\":\"https://api.hubapi.com/webhooks/v4/journal/latest\"} {\"correlation_id\":\"b453f0f4-0d2c-491c-a935-14c60265ec18\",\"trace_id\":\"a50202d6-5601-46b4-a02e-cf5ac8d061fa\"}\n[2026-04-27 10:26:26] local.INFO: [AskJiminnyReport:Generate] Started {\"automatedReportUuid\":\"4f6ca2b5-1993-48aa-99ad-b66f19f15d43\"} {\"correlation_id\":\"c6277ddd-09a6-4b04-8d42-bd395fc07d0a\",\"trace_id\":\"aa0f8702-73eb-4947-a765-2e89244599c3\"}\n[2026-04-27 10:26:26] local.INFO: [HubSpot Journal Polling] No data {\"correlation_id\":\"b453f0f4-0d2c-491c-a935-14c60265ec18\",\"trace_id\":\"a50202d6-5601-46b4-a02e-cf5ac8d061fa\"}\n[2026-04-27 10:26:26] local.WARNING: [HubSpot Journal Polling] Maximum empty results reached, stopping {\"empty_results\":5,\"max_empty_results\":5} {\"correlation_id\":\"b453f0f4-0d2c-491c-a935-14c60265ec18\",\"trace_id\":\"a50202d6-5601-46b4-a02e-cf5ac8d061fa\"}\n[2026-04-27 10:26:26] local.WARNING: [HubSpot Journal Polling] Maximum empty results reached, stopping {\"empty_results\":5,\"max_empty_results\":5} {\"correlation_id\":\"b453f0f4-0d2c-491c-a935-14c60265ec18\",\"trace_id\":\"a50202d6-5601-46b4-a02e-cf5ac8d061fa\"}\n[2026-04-27 10:26:26] local.INFO: [HubSpot Journal Polling] Service ending {\"runtime_seconds\":56,\"total_cycles\":5,\"files_downloaded\":0,\"empty_files\":0,\"other_portal_skipped\":0,\"total_events\":0,\"events_per_file\":0,\"avg_api_ms\":273.7,\"avg_download_ms\":0.0,\"avg_transform_ms\":0.0,\"avg_process_ms\":0.0,\"peak_memory_mb\":99.73} {\"correlation_id\":\"b453f0f4-0d2c-491c-a935-14c60265ec18\",\"trace_id\":\"a50202d6-5601-46b4-a02e-cf5ac8d061fa\"}\n[2026-04-27 10:26:26] local.INFO: [HubSpot Journal Polling] Released polling lock {\"correlation_id\":\"b453f0f4-0d2c-491c-a935-14c60265ec18\",\"trace_id\":\"a50202d6-5601-46b4-a02e-cf5ac8d061fa\"}\n[2026-04-27 10:26:27] local.INFO: [AskJiminnyReport] Fetched activity IDs for saved search {\"saved_search_id\":1977,\"user_id\":143,\"activity_count\":0} {\"correlation_id\":\"c6277ddd-09a6-4b04-8d42-bd395fc07d0a\",\"trace_id\":\"aa0f8702-73eb-4947-a765-2e89244599c3\"}\n[2026-04-27 10:26:27] local.INFO: [AskJiminnyReport:Generate] Fetched activity IDs {\"automatedReportUuid\":\"4f6ca2b5-1993-48aa-99ad-b66f19f15d43\",\"activityCount\":0} {\"correlation_id\":\"c6277ddd-09a6-4b04-8d42-bd395fc07d0a\",\"trace_id\":\"aa0f8702-73eb-4947-a765-2e89244599c3\"}\n[2026-04-27 10:26:27] local.INFO: [AskJiminnyReport:Generate] Not enough activities, skipped {\"automatedReportUuid\":\"4f6ca2b5-1993-48aa-99ad-b66f19f15d43\",\"activityCount\":0} {\"correlation_id\":\"c6277ddd-09a6-4b04-8d42-bd395fc07d0a\",\"trace_id\":\"aa0f8702-73eb-4947-a765-2e89244599c3\"}\n[2026-04-27 10:26:27] local.INFO: [AskJiminnyReport:Generate] Dispatched not-generated notifications {\"automatedReportUuid\":\"4f6ca2b5-1993-48aa-99ad-b66f19f15d43\",\"recipientsCount\":1} {\"correlation_id\":\"c6277ddd-09a6-4b04-8d42-bd395fc07d0a\",\"trace_id\":\"aa0f8702-73eb-4947-a765-2e89244599c3\"}\n[2026-04-27 10:26:27] local.INFO: [AskJiminnyReport:Generate] Started {\"automatedReportUuid\":\"4f6ca2b5-1993-48aa-99ad-b66f19f15d43\"} {\"correlation_id\":\"f1d3e4df-9591-487f-ac2a-a43ddb89a466\",\"trace_id\":\"aa0f8702-73eb-4947-a765-2e89244599c3\"}\n[2026-04-27 10:26:27] local.INFO: [AskJiminnyReport] Fetched activity IDs for saved search {\"saved_search_id\":1977,\"user_id\":143,\"activity_count\":0} {\"correlation_id\":\"f1d3e4df-9591-487f-ac2a-a43ddb89a466\",\"trace_id\":\"aa0f8702-73eb-4947-a765-2e89244599c3\"}\n[2026-04-27 10:26:27] local.INFO: [AskJiminnyReport:Generate] Fetched activity IDs {\"automatedReportUuid\":\"4f6ca2b5-1993-48aa-99ad-b66f19f15d43\",\"activityCount\":0} {\"correlation_id\":\"f1d3e4df-9591-487f-ac2a-a43ddb89a466\",\"trace_id\":\"aa0f8702-73eb-4947-a765-2e89244599c3\"}\n[2026-04-27 10:26:27] local.INFO: [AskJiminnyReport:Generate] Not enough activities, skipped {\"automatedReportUuid\":\"4f6ca2b5-1993-48aa-99ad-b66f19f15d43\",\"activityCount\":0} {\"correlation_id\":\"f1d3e4df-9591-487f-ac2a-a43ddb89a466\",\"trace_id\":\"aa0f8702-73eb-4947-a765-2e89244599c3\"}\n[2026-04-27 10:26:27] local.INFO: [AskJiminnyReport:Generate] Dispatched not-generated notifications {\"automatedReportUuid\":\"4f6ca2b5-1993-48aa-99ad-b66f19f15d43\",\"recipientsCount\":1} {\"correlation_id\":\"f1d3e4df-9591-487f-ac2a-a43ddb89a466\",\"trace_id\":\"aa0f8702-73eb-4947-a765-2e89244599c3\"}\n[2026-04-27 10:26:27] local.INFO: [Send Report Not Generated Mail] Email sent {\"uuid\":\"cd5b5081-22f8-4d1a-9d6f-f82aca1121f6\",\"email\":\"lukas.kovalik@jiminny.com\"} {\"correlation_id\":\"4255983f-07a4-4872-9ea6-5491fa32bdd5\",\"trace_id\":\"aa0f8702-73eb-4947-a765-2e89244599c3\"}\n[2026-04-27 10:26:28] local.INFO: [Sync Mailbox] Sync start {\"inbox_id\":59} {\"correlation_id\":\"280d5bf6-c34c-45ea-bea7-05d9c028e9d3\",\"trace_id\":\"265be7c0-bf8f-4037-a0d2-57df3ef1d7e1\"}\n[2026-04-27 10:26:28] local.INFO: [Inbox service] Skipping METADATA SYNC for inbox 59 due to unauthorized access to the mailbox {\"correlation_id\":\"280d5bf6-c34c-45ea-bea7-05d9c028e9d3\",\"trace_id\":\"265be7c0-bf8f-4037-a0d2-57df3ef1d7e1\"}\n[2026-04-27 10:26:28] local.INFO: [Sync Mailbox] Sync complete {\"inbox_id\":59} {\"correlation_id\":\"280d5bf6-c34c-45ea-bea7-05d9c028e9d3\",\"trace_id\":\"265be7c0-bf8f-4037-a0d2-57df3ef1d7e1\"}\n[2026-04-27 10:26:28] local.INFO: [Sync Mailbox] Sync start {\"inbox_id\":212} {\"correlation_id\":\"d3e3eb3d-5c83-4d6e-9592-aaa38c1843a4\",\"trace_id\":\"265be7c0-bf8f-4037-a0d2-57df3ef1d7e1\"}\n[2026-04-27 10:26:28] local.INFO: [SocialAccountService] Fetching token {\"socialAccountId\":1500,\"provider\":\"salesforce\"} {\"correlation_id\":\"d3e3eb3d-5c83-4d6e-9592-aaa38c1843a4\",\"trace_id\":\"265be7c0-bf8f-4037-a0d2-57df3ef1d7e1\"}\n[2026-04-27 10:26:28] local.INFO: [SocialAccountService] Token retrieved {\"socialAccountId\":1500,\"provider\":\"salesforce\"} {\"correlation_id\":\"d3e3eb3d-5c83-4d6e-9592-aaa38c1843a4\",\"trace_id\":\"265be7c0-bf8f-4037-a0d2-57df3ef1d7e1\"}\n[2026-04-27 10:26:28] local.INFO: [EncryptedTokenManager] Generating access token. {\"mode\":\"legacy\"} {\"correlation_id\":\"d3e3eb3d-5c83-4d6e-9592-aaa38c1843a4\",\"trace_id\":\"265be7c0-bf8f-4037-a0d2-57df3ef1d7e1\"}\n[2026-04-27 10:26:28] local.INFO: [CrmOwnerResolver] Integration owner matched as CRM Owner {\"crm_provider\":\"salesforce\",\"crm_owner\":143,\"team_id\":1} {\"correlation_id\":\"d3e3eb3d-5c83-4d6e-9592-aaa38c1843a4\",\"trace_id\":\"265be7c0-bf8f-4037-a0d2-57df3ef1d7e1\"}\n[2026-04-27 10:26:28] local.INFO: [SocialAccountService] Fetching token {\"socialAccountId\":1354,\"provider\":\"google\"} {\"correlation_id\":\"d3e3eb3d-5c83-4d6e-9592-aaa38c1843a4\",\"trace_id\":\"265be7c0-bf8f-4037-a0d2-57df3ef1d7e1\"}\n[2026-04-27 10:26:28] local.INFO: [SocialAccountService] Token retrieved {\"socialAccountId\":1354,\"provider\":\"google\"} {\"correlation_id\":\"d3e3eb3d-5c83-4d6e-9592-aaa38c1843a4\",\"trace_id\":\"265be7c0-bf8f-4037-a0d2-57df3ef1d7e1\"}\n[2026-04-27 10:26:28] local.INFO: [EncryptedTokenManager] Generating access token. {\"mode\":\"legacy\"} {\"correlation_id\":\"d3e3eb3d-5c83-4d6e-9592-aaa38c1843a4\",\"trace_id\":\"265be7c0-bf8f-4037-a0d2-57df3ef1d7e1\"}\n[2026-04-27 10:26:28] local.INFO: [Gmail] Performing incremental sync for inbox 212 using history ID: @1777284288 {\"correlation_id\":\"d3e3eb3d-5c83-4d6e-9592-aaa38c1843a4\",\"trace_id\":\"265be7c0-bf8f-4037-a0d2-57df3ef1d7e1\"}\n[2026-04-27 10:26:28] local.INFO: [Gmail] imported 7 emails via full sync workflow for inbox 212 {\"correlation_id\":\"d3e3eb3d-5c83-4d6e-9592-aaa38c1843a4\",\"trace_id\":\"265be7c0-bf8f-4037-a0d2-57df3ef1d7e1\"}\n[2026-04-27 10:26:28] local.INFO: [Gmail] seeding inbox 212 with last message time : 2026-04-27 10:24:57 {\"correlation_id\":\"d3e3eb3d-5c83-4d6e-9592-aaa38c1843a4\",\"trace_id\":\"265be7c0-bf8f-4037-a0d2-57df3ef1d7e1\"}\n[2026-04-27 10:26:28] local.INFO: [Sync Mailbox] Sync complete {\"inbox_id\":212} {\"correlation_id\":\"d3e3eb3d-5c83-4d6e-9592-aaa38c1843a4\",\"trace_id\":\"265be7c0-bf8f-4037-a0d2-57df3ef1d7e1\"}\n[2026-04-27 10:27:04] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage before starting command {\"command\":\"meeting-bot:schedule-bot\",\"memoryBeforeCommandInMb\":60.0,\"memoryPeakBeforeCommandInMb\":99.727} {\"correlation_id\":\"f55fbf77-7e5f-4c87-accd-011697ca0fa3\",\"trace_id\":\"9bd1c431-26de-4e31-83d8-3129ed9b3b79\"}\n[2026-04-27 10:27:04] local.INFO: [ScheduleBotCommand] Number of activities to be captured: 0 {\"correlation_id\":\"f55fbf77-7e5f-4c87-accd-011697ca0fa3\",\"trace_id\":\"9bd1c431-26de-4e31-83d8-3129ed9b3b79\"}\n[2026-04-27 10:27:04] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage for command {\"command\":\"meeting-bot:schedule-bot\",\"memoryBeforeCommandInMb\":60.0,\"memoryAfterCommandInMB\":62.0,\"memoryPeakBeforeCommandInMb\":99.727,\"memoryPeakAfterCommandInMB\":99.727} {\"correlation_id\":\"f55fbf77-7e5f-4c87-accd-011697ca0fa3\",\"trace_id\":\"9bd1c431-26de-4e31-83d8-3129ed9b3b79\"}\n[2026-04-27 10:27:05] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage before starting command {\"command\":\"dialers:monitor-activities\",\"memoryBeforeCommandInMb\":60.0,\"memoryPeakBeforeCommandInMb\":99.727} {\"correlation_id\":\"da241053-5f3b-4d02-9504-4ef2987cdbd7\",\"trace_id\":\"364d3fc7-0f11-40c6-8697-d18eff2c4cb6\"}\n[2026-04-27 10:27:05] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage for command {\"command\":\"dialers:monitor-activities\",\"memoryBeforeCommandInMb\":60.0,\"memoryAfterCommandInMB\":62.0,\"memoryPeakBeforeCommandInMb\":99.727,\"memoryPeakAfterCommandInMB\":99.727} {\"correlation_id\":\"da241053-5f3b-4d02-9504-4ef2987cdbd7\",\"trace_id\":\"364d3fc7-0f11-40c6-8697-d18eff2c4cb6\"}\n[2026-04-27 10:27:06] local.NOTICE: Monitoring start {\"correlation_id\":\"ad0d871a-a1fd-4725-b2f7-ac8f0fc1857e\",\"trace_id\":\"cb504e84-d924-43a0-82d5-faf1840134ba\"}\n[2026-04-27 10:27:06] local.NOTICE: Monitoring end {\"correlation_id\":\"ad0d871a-a1fd-4725-b2f7-ac8f0fc1857e\",\"trace_id\":\"cb504e84-d924-43a0-82d5-faf1840134ba\"}\n[2026-04-27 10:27:08] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage before starting command {\"command\":\"mailbox:skip-lists:refresh\",\"memoryBeforeCommandInMb\":60.0,\"memoryPeakBeforeCommandInMb\":99.727} {\"correlation_id\":\"b8652f0d-1cee-44ae-a0ea-3d3137a882c1\",\"trace_id\":\"7fe19940-0813-4199-a2a8-958de09bd0ee\"}\n[2026-04-27 10:27:08] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage for command {\"command\":\"mailbox:skip-lists:refresh\",\"memoryBeforeCommandInMb\":60.0,\"memoryAfterCommandInMB\":62.0,\"memoryPeakBeforeCommandInMb\":99.727,\"memoryPeakAfterCommandInMB\":99.727} {\"correlation_id\":\"b8652f0d-1cee-44ae-a0ea-3d3137a882c1\",\"trace_id\":\"7fe19940-0813-4199-a2a8-958de09bd0ee\"}\n[2026-04-27 10:27:09] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage before starting command {\"command\":\"mailbox:batch:process\",\"memoryBeforeCommandInMb\":60.0,\"memoryPeakBeforeCommandInMb\":99.727} {\"correlation_id\":\"06190462-0f47-403f-8e95-ad5d05bff1fe\",\"trace_id\":\"4aa6bed6-05a1-4661-a9c9-edadc61d84ac\"}\n[2026-04-27 10:27:09] local.INFO: [EmailSchedule] STARTING batch process {\"host\":\"docker_lamp_1\"} {\"correlation_id\":\"06190462-0f47-403f-8e95-ad5d05bff1fe\",\"trace_id\":\"4aa6bed6-05a1-4661-a9c9-edadc61d84ac\"}\n[2026-04-27 10:27:09] local.INFO: [EmailSchedule] FINISHED batch process {\"host\":\"docker_lamp_1\",\"processed\":0} {\"correlation_id\":\"06190462-0f47-403f-8e95-ad5d05bff1fe\",\"trace_id\":\"4aa6bed6-05a1-4661-a9c9-edadc61d84ac\"}\n[2026-04-27 10:27:09] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage for command {\"command\":\"mailbox:batch:process\",\"memoryBeforeCommandInMb\":60.0,\"memoryAfterCommandInMB\":62.0,\"memoryPeakBeforeCommandInMb\":99.727,\"memoryPeakAfterCommandInMB\":99.727} {\"correlation_id\":\"06190462-0f47-403f-8e95-ad5d05bff1fe\",\"trace_id\":\"4aa6bed6-05a1-4661-a9c9-edadc61d84ac\"}\n[2026-04-27 10:27:11] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage before starting command {\"command\":\"mailbox:batch:create\",\"memoryBeforeCommandInMb\":60.0,\"memoryPeakBeforeCommandInMb\":99.727} {\"correlation_id\":\"b561c5d5-e6e3-4091-9517-b0458190a360\",\"trace_id\":\"bf9679fb-b785-40cb-b814-04a1dc385a1c\"}\n[2026-04-27 10:27:11] local.INFO: [EmailSchedule] STARTING batch create {\"host\":\"docker_lamp_1\"} {\"correlation_id\":\"b561c5d5-e6e3-4091-9517-b0458190a360\",\"trace_id\":\"bf9679fb-b785-40cb-b814-04a1dc385a1c\"}\n[2026-04-27 10:27:11] local.INFO: [EmailSchedule] FINISHED batch create {\"host\":\"docker_lamp_1\"} {\"correlation_id\":\"b561c5d5-e6e3-4091-9517-b0458190a360\",\"trace_id\":\"bf9679fb-b785-40cb-b814-04a1dc385a1c\"}\n[2026-04-27 10:27:11] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage for command {\"command\":\"mailbox:batch:create\",\"memoryBeforeCommandInMb\":60.0,\"memoryAfterCommandInMB\":62.0,\"memoryPeakBeforeCommandInMb\":99.727,\"memoryPeakAfterCommandInMB\":99.727} {\"correlation_id\":\"b561c5d5-e6e3-4091-9517-b0458190a360\",\"trace_id\":\"bf9679fb-b785-40cb-b814-04a1dc385a1c\"}\n[2026-04-27 10:27:14] local.INFO: [Jiminny\\Jobs\\Mailbox\\CreateBatches] processed 2 inboxes and created 1 batches {\"userId\":null,\"batchSize\":30,\"maxBatches\":1000} {\"correlation_id\":\"98416296-43c2-4834-927e-4a0e86dc99cf\",\"trace_id\":\"bf9679fb-b785-40cb-b814-04a1dc385a1c\"}\n[2026-04-27 10:28:04] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage before starting command {\"command\":\"meeting-bot:schedule-bot\",\"memoryBeforeCommandInMb\":60.0,\"memoryPeakBeforeCommandInMb\":99.727} {\"correlation_id\":\"5852bcf1-af92-4d24-8a2d-08a9d4a5cfac\",\"trace_id\":\"25af5e8c-a148-493e-ac9f-4d53f1960177\"}\n[2026-04-27 10:28:04] local.INFO: [ScheduleBotCommand] Number of activities to be captured: 0 {\"correlation_id\":\"5852bcf1-af92-4d24-8a2d-08a9d4a5cfac\",\"trace_id\":\"25af5e8c-a148-493e-ac9f-4d53f1960177\"}\n[2026-04-27 10:28:04] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage for command {\"command\":\"meeting-bot:schedule-bot\",\"memoryBeforeCommandInMb\":60.0,\"memoryAfterCommandInMB\":62.0,\"memoryPeakBeforeCommandInMb\":99.727,\"memoryPeakAfterCommandInMB\":99.727} {\"correlation_id\":\"5852bcf1-af92-4d24-8a2d-08a9d4a5cfac\",\"trace_id\":\"25af5e8c-a148-493e-ac9f-4d53f1960177\"}\n[2026-04-27 10:28:06] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage before starting command {\"command\":\"dialers:monitor-activities\",\"memoryBeforeCommandInMb\":60.0,\"memoryPeakBeforeCommandInMb\":99.727} {\"correlation_id\":\"cc1cff1f-71b4-4bb1-800f-66e27c0f2b3a\",\"trace_id\":\"c03103a3-9ab8-480b-9753-7c7c6488f246\"}\n[2026-04-27 10:28:06] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage for command {\"command\":\"dialers:monitor-activities\",\"memoryBeforeCommandInMb\":60.0,\"memoryAfterCommandInMB\":62.0,\"memoryPeakBeforeCommandInMb\":99.727,\"memoryPeakAfterCommandInMB\":99.727} {\"correlation_id\":\"cc1cff1f-71b4-4bb1-800f-66e27c0f2b3a\",\"trace_id\":\"c03103a3-9ab8-480b-9753-7c7c6488f246\"}\n[2026-04-27 10:28:09] local.ERROR: Target class [Jiminny\\Repositories\\AjReportsRepository] does not exist. {\"exception\":\"[object] (Illuminate\\\\Contracts\\\\Container\\\\BindingResolutionException(code: 0): Target class [Jiminny\\\\Repositories\\\\AjReportsRepository] does not exist. at /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php:1124)\n[stacktrace]\n#0 /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php(933): Illuminate\\\\Container\\\\Container->build('Jiminny\\\\\\\\Reposit...')\n#1 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Application.php(1078): Illuminate\\\\Container\\\\Container->resolve('Jiminny\\\\\\\\Reposit...', Array, true)\n#2 /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php(864): Illuminate\\\\Foundation\\\\Application->resolve('Jiminny\\\\\\\\Reposit...', Array)\n#3 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Application.php(1058): Illuminate\\\\Container\\\\Container->make('Jiminny\\\\\\\\Reposit...', Array)\n#4 /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php(1336): Illuminate\\\\Foundation\\\\Application->make('Jiminny\\\\\\\\Reposit...')\n#5 /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php(1237): Illuminate\\\\Container\\\\Container->resolveClass(Object(ReflectionParameter))\n#6 /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php(1162): Illuminate\\\\Container\\\\Container->resolveDependencies(Array)\n#7 /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php(933): Illuminate\\\\Container\\\\Container->build('Jiminny\\\\\\\\Http\\\\\\\\Co...')\n#8 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Application.php(1078): Illuminate\\\\Container\\\\Container->resolve('Jiminny\\\\\\\\Http\\\\\\\\Co...', Array, true)\n#9 /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php(864): Illuminate\\\\Foundation\\\\Application->resolve('Jiminny\\\\\\\\Http\\\\\\\\Co...', Array)\n#10 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Application.php(1058): Illuminate\\\\Container\\\\Container->make('Jiminny\\\\\\\\Http\\\\\\\\Co...', Array)\n#11 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Route.php(286): Illuminate\\\\Foundation\\\\Application->make('Jiminny\\\\\\\\Http\\\\\\\\Co...')\n#12 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Route.php(1133): Illuminate\\\\Routing\\\\Route->getController()\n#13 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Route.php(1062): Illuminate\\\\Routing\\\\Route->controllerMiddleware()\n#14 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Router.php(834): Illuminate\\\\Routing\\\\Route->gatherMiddleware()\n#15 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Router.php(816): Illuminate\\\\Routing\\\\Router->gatherRouteMiddleware(Object(Illuminate\\\\Routing\\\\Route))\n#16 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Router.php(800): Illuminate\\\\Routing\\\\Router->runRouteWithinStack(Object(Illuminate\\\\Routing\\\\Route), Object(Illuminate\\\\Http\\\\Request))\n#17 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Router.php(764): Illuminate\\\\Routing\\\\Router->runRoute(Object(Illuminate\\\\Http\\\\Request), Object(Illuminate\\\\Routing\\\\Route))\n#18 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Router.php(753): Illuminate\\\\Routing\\\\Router->dispatchToRoute(Object(Illuminate\\\\Http\\\\Request))\n#19 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Http/Kernel.php(200): Illuminate\\\\Routing\\\\Router->dispatch(Object(Illuminate\\\\Http\\\\Request))\n#20 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(180): Illuminate\\\\Foundation\\\\Http\\\\Kernel->Illuminate\\\\Foundation\\\\Http\\\\{closure}(Object(Illuminate\\\\Http\\\\Request))\n#21 /home/jiminny/vendor/barryvdh/laravel-debugbar/src/Middleware/InjectDebugbar.php(59): Illuminate\\\\Pipeline\\\\Pipeline->Illuminate\\\\Pipeline\\\\{closure}(Object(Illuminate\\\\Http\\\\Request))\n#22 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(219): Barryvdh\\\\Debugbar\\\\Middleware\\\\InjectDebugbar->handle(Object(Illuminate\\\\Http\\\\Request), Object(Closure))\n#23 /home/jiminny/vendor/laravel/framework/src/Illuminate/Http/Middleware/HandleCors.php(74): Illuminate\\\\Pipeline\\\\Pipeline->Illuminate\\\\Pipeline\\\\{closure}(Object(Illuminate\\\\Http\\\\Request))\n#24 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(219): Illuminate\\\\Http\\\\Middleware\\\\HandleCors->handle(Object(Illuminate\\\\Http\\\\Request), Object(Closure))\n#25 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Http/Middleware/TransformsRequest.php(21): Illuminate\\\\Pipeline\\\\Pipeline->Illuminate\\\\Pipeline\\\\{closure}(Object(Illuminate\\\\Http\\\\Request))\n#26 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Http/Middleware/TrimStrings.php(51): Illuminate\\\\Foundation\\\\Http\\\\Middleware\\\\TransformsRequest->handle(Object(Illuminate\\\\Http\\\\Request), Object(Closure))\n#27 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(219): Illuminate\\\\Foundation\\\\Http\\\\Middleware\\\\TrimStrings->handle(Object(Illuminate\\\\Http\\\\Request), Object(Closure))\n#28 /home/jiminny/vendor/bepsvpt/secure-headers/src/SecureHeadersMiddleware.php(18): Illuminate\\\\Pipeline\\\\Pipeline->Illuminate\\\\Pipeline\\\\{closure}(Object(Illuminate\\\\Http\\\\Request))\n#29 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(219): Bepsvpt\\\\SecureHeaders\\\\SecureHeadersMiddleware->handle(Object(Illuminate\\\\Http\\\\Request), Object(Closure))\n#30 /home/jiminny/app/Http/Middleware/SentryContext.php(60): Illuminate\\\\Pipeline\\\\Pipeline->Illuminate\\\\Pipeline\\\\{closure}(Object(Illuminate\\\\Http\\\\Request))\n#31 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(219): Jiminny\\\\Http\\\\Middleware\\\\SentryContext->handle(Object(Illuminate\\\\Http\\\\Request), Object(Closure))\n#32 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Http/Middleware/PreventRequestsDuringMaintenance.php(109): Illuminate\\\\Pipeline\\\\Pipeline->Illuminate\\\\Pipeline\\\\{closure}(Object(Illuminate\\\\Http\\\\Request))\n#33 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(219): Illuminate\\\\Foundation\\\\Http\\\\Middleware\\\\PreventRequestsDuringMaintenance->handle(Object(Illuminate\\\\Http\\\\Request), Object(Closure))\n#34 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Http/Middleware/InvokeDeferredCallbacks.php(22): Illuminate\\\\Pipeline\\\\Pipeline->Illuminate\\\\Pipeline\\\\{closure}(Object(Illuminate\\\\Http\\\\Request))\n#35 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(219): Illuminate\\\\Foundation\\\\Http\\\\Middleware\\\\InvokeDeferredCallbacks->handle(Object(Illuminate\\\\Http\\\\Request), Object(Closure))\n#36 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(137): Illuminate\\\\Pipeline\\\\Pipeline->Illuminate\\\\Pipeline\\\\{closure}(Object(Illuminate\\\\Http\\\\Request))\n#37 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Http/Kernel.php(175): Illuminate\\\\Pipeline\\\\Pipeline->then(Object(Closure))\n#38 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Http/Kernel.php(144): Illuminate\\\\Foundation\\\\Http\\\\Kernel->sendRequestThroughRouter(Object(Illuminate\\\\Http\\\\Request))\n#39 /home/jiminny/public/index.php(51): Illuminate\\\\Foundation\\\\Http\\\\Kernel->handle(Object(Illuminate\\\\Http\\\\Request))\n#40 {main}\n\n[previous exception] [object] (ReflectionException(code: -1): Class \\\"Jiminny\\\\Repositories\\\\AjReportsRepository\\\" does not exist at /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php:1122)\n[stacktrace]\n#0 /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php(1122): ReflectionClass->__construct('Jiminny\\\\\\\\Reposit...')\n#1 /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php(933): Illuminate\\\\Container\\\\Container->build('Jiminny\\\\\\\\Reposit...')\n#2 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Application.php(1078): Illuminate\\\\Container\\\\Container->resolve('Jiminny\\\\\\\\Reposit...', Array, true)\n#3 /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php(864): Illuminate\\\\Foundation\\\\Application->resolve('Jiminny\\\\\\\\Reposit...', Array)\n#4 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Application.php(1058): Illuminate\\\\Container\\\\Container->make('Jiminny\\\\\\\\Reposit...', Array)\n#5 /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php(1336): Illuminate\\\\Foundation\\\\Application->make('Jiminny\\\\\\\\Reposit...')\n#6 /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php(1237): Illuminate\\\\Container\\\\Container->resolveClass(Object(ReflectionParameter))\n#7 /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php(1162): Illuminate\\\\Container\\\\Container->resolveDependencies(Array)\n#8 /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php(933): Illuminate\\\\Container\\\\Container->build('Jiminny\\\\\\\\Http\\\\\\\\Co...')\n#9 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Application.php(1078): Illuminate\\\\Container\\\\Container->resolve('Jiminny\\\\\\\\Http\\\\\\\\Co...', Array, true)\n#10 /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php(864): Illuminate\\\\Foundation\\\\Application->resolve('Jiminny\\\\\\\\Http\\\\\\\\Co...', Array)\n#11 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Application.php(1058): Illuminate\\\\Container\\\\Container->make('Jiminny\\\\\\\\Http\\\\\\\\Co...', Array)\n#12 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Route.php(286): Illuminate\\\\Foundation\\\\Application->make('Jiminny\\\\\\\\Http\\\\\\\\Co...')\n#13 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Route.php(1133): Illuminate\\\\Routing\\\\Route->getController()\n#14 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Route.php(1062): Illuminate\\\\Routing\\\\Route->controllerMiddleware()\n#15 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Router.php(834): Illuminate\\\\Routing\\\\Route->gatherMiddleware()\n#16 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Router.php(816): Illuminate\\\\Routing\\\\Router->gatherRouteMiddleware(Object(Illuminate\\\\Routing\\\\Route))\n#17 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Router.php(800): Illuminate\\\\Routing\\\\Router->runRouteWithinStack(Object(Illuminate\\\\Routing\\\\Route), Object(Illuminate\\\\Http\\\\Request))\n#18 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Router.php(764): Illuminate\\\\Routing\\\\Router->runRoute(Object(Illuminate\\\\Http\\\\Request), Object(Illuminate\\\\Routing\\\\Route))\n#19 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Router.php(753): Illuminate\\\\Routing\\\\Router->dispatchToRoute(Object(Illuminate\\\\Http\\\\Request))\n#20 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Http/Kernel.php(200): Illuminate\\\\Routing\\\\Router->dispatch(Object(Illuminate\\\\Http\\\\Request))\n#21 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(180): Illuminate\\\\Foundation\\\\Http\\\\Kernel->Illuminate\\\\Foundation\\\\Http\\\\{closure}(Object(Illuminate\\\\Http\\\\Request))\n#22 /home/jiminny/vendor/barryvdh/laravel-debugbar/src/Middleware/InjectDebugbar.php(59): Illuminate\\\\Pipeline\\\\Pipeline->Illuminate\\\\Pipeline\\\\{closure}(Object(Illuminate\\\\Http\\\\Request))\n#23 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(219): Barryvdh\\\\Debugbar\\\\Middleware\\\\InjectDebugbar->handle(Object(Illuminate\\\\Http\\\\Request), Object(Closure))\n#24 /home/jiminny/vendor/laravel/framework/src/Illuminate/Http/Middleware/HandleCors.php(74): Illuminate\\\\Pipeline\\\\Pipeline->Illuminate\\\\Pipeline\\\\{closure}(Object(Illuminate\\\\Http\\\\Request))\n#25 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(219): Illuminate\\\\Http\\\\Middleware\\\\HandleCors->handle(Object(Illuminate\\\\Http\\\\Request), Object(Closure))\n#26 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Http/Middleware/TransformsRequest.php(21): Illuminate\\\\Pipeline\\\\Pipeline->Illuminate\\\\Pipeline\\\\{closure}(Object(Illuminate\\\\Http\\\\Request))\n#27 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Http/Middleware/TrimStrings.php(51): Illuminate\\\\Foundation\\\\Http\\\\Middleware\\\\TransformsRequest->handle(Object(Illuminate\\\\Http\\\\Request), Object(Closure))\n#28 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(219): Illuminate\\\\Foundation\\\\Http\\\\Middleware\\\\TrimStrings->handle(Object(Illuminate\\\\Http\\\\Request), Object(Closure))\n#29 /home/jiminny/vendor/bepsvpt/secure-headers/src/SecureHeadersMiddleware.php(18): Illuminate\\\\Pipeline\\\\Pipeline->Illuminate\\\\Pipeline\\\\{closure}(Object(Illuminate\\\\Http\\\\Request))\n#30 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(219): Bepsvpt\\\\SecureHeaders\\\\SecureHeadersMiddleware->handle(Object(Illuminate\\\\Http\\\\Request), Object(Closure))\n#31 /home/jiminny/app/Http/Middleware/SentryContext.php(60): Illuminate\\\\Pipeline\\\\Pipeline->Illuminate\\\\Pipeline\\\\{closure}(Object(Illuminate\\\\Http\\\\Request))\n#32 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(219): Jiminny\\\\Http\\\\Middleware\\\\SentryContext->handle(Object(Illuminate\\\\Http\\\\Request), Object(Closure))\n#33 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Http/Middleware/PreventRequestsDuringMaintenance.php(109): Illuminate\\\\Pipeline\\\\Pipeline->Illuminate\\\\Pipeline\\\\{closure}(Object(Illuminate\\\\Http\\\\Request))\n#34 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(219): Illuminate\\\\Foundation\\\\Http\\\\Middleware\\\\PreventRequestsDuringMaintenance->handle(Object(Illuminate\\\\Http\\\\Request), Object(Closure))\n#35 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Http/Middleware/InvokeDeferredCallbacks.php(22): Illuminate\\\\Pipeline\\\\Pipeline->Illuminate\\\\Pipeline\\\\{closure}(Object(Illuminate\\\\Http\\\\Request))\n#36 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(219): Illuminate\\\\Foundation\\\\Http\\\\Middleware\\\\InvokeDeferredCallbacks->handle(Object(Illuminate\\\\Http\\\\Request), Object(Closure))\n#37 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(137): Illuminate\\\\Pipeline\\\\Pipeline->Illuminate\\\\Pipeline\\\\{closure}(Object(Illuminate\\\\Http\\\\Request))\n#38 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Http/Kernel.php(175): Illuminate\\\\Pipeline\\\\Pipeline->then(Object(Closure))\n#39 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Http/Kernel.php(144): Illuminate\\\\Foundation\\\\Http\\\\Kernel->sendRequestThroughRouter(Object(Illuminate\\\\Http\\\\Request))\n#40 /home/jiminny/public/index.php(51): Illuminate\\\\Foundation\\\\Http\\\\Kernel->handle(Object(Illuminate\\\\Http\\\\Request))\n#41 {main}\n\"} {\"correlation_id\":\"dfad8817-1dde-4622-8320-f5fc1616a8fe\",\"trace_id\":\"d21232b2-60fb-4ca0-838c-ba42e61593f9\"}\n[2026-04-27 10:28:09] local.NOTICE: Monitoring start {\"correlation_id\":\"93744c67-4444-41ff-aaad-6aa9cafe294e\",\"trace_id\":\"80e396b0-b1ac-4ec8-838c-00021f98072c\"}\n[2026-04-27 10:28:09] local.NOTICE: Monitoring end {\"correlation_id\":\"93744c67-4444-41ff-aaad-6aa9cafe294e\",\"trace_id\":\"80e396b0-b1ac-4ec8-838c-00021f98072c\"}\n[2026-04-27 10:28:10] local.ERROR: Target class [Jiminny\\Repositories\\AjReportsRepository] does not exist. {\"exception\":\"[object] (Illuminate\\\\Contracts\\\\Container\\\\BindingResolutionException(code: 0): Target class [Jiminny\\\\Repositories\\\\AjReportsRepository] does not exist. at /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php:1124)\n[stacktrace]\n#0 /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php(933): Illuminate\\\\Container\\\\Container->build('Jiminny\\\\\\\\Reposit...')\n#1 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Application.php(1078): Illuminate\\\\Container\\\\Container->resolve('Jiminny\\\\\\\\Reposit...', Array, true)\n#2 /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php(864): Illuminate\\\\Foundation\\\\Application->resolve('Jiminny\\\\\\\\Reposit...', Array)\n#3 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Application.php(1058): Illuminate\\\\Container\\\\Container->make('Jiminny\\\\\\\\Reposit...', Array)\n#4 /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php(1336): Illuminate\\\\Foundation\\\\Application->make('Jiminny\\\\\\\\Reposit...')\n#5 /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php(1237): Illuminate\\\\Container\\\\Container->resolveClass(Object(ReflectionParameter))\n#6 /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php(1162): Illuminate\\\\Container\\\\Container->resolveDependencies(Array)\n#7 /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php(933): Illuminate\\\\Container\\\\Container->build('Jiminny\\\\\\\\Http\\\\\\\\Co...')\n#8 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Application.php(1078): Illuminate\\\\Container\\\\Container->resolve('Jiminny\\\\\\\\Http\\\\\\\\Co...', Array, true)\n#9 /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php(864): Illuminate\\\\Foundation\\\\Application->resolve('Jiminny\\\\\\\\Http\\\\\\\\Co...', Array)\n#10 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Application.php(1058): Illuminate\\\\Container\\\\Container->make('Jiminny\\\\\\\\Http\\\\\\\\Co...', Array)\n#11 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Route.php(286): Illuminate\\\\Foundation\\\\Application->make('Jiminny\\\\\\\\Http\\\\\\\\Co...')\n#12 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Route.php(1133): Illuminate\\\\Routing\\\\Route->getController()\n#13 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Route.php(1062): Illuminate\\\\Routing\\\\Route->controllerMiddleware()\n#14 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Router.php(834): Illuminate\\\\Routing\\\\Route->gatherMiddleware()\n#15 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Router.php(816): Illuminate\\\\Routing\\\\Router->gatherRouteMiddleware(Object(Illuminate\\\\Routing\\\\Route))\n#16 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Router.php(800): Illuminate\\\\Routing\\\\Router->runRouteWithinStack(Object(Illuminate\\\\Routing\\\\Route), Object(Illuminate\\\\Http\\\\Request))\n#17 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Router.php(764): Illuminate\\\\Routing\\\\Router->runRoute(Object(Illuminate\\\\Http\\\\Request), Object(Illuminate\\\\Routing\\\\Route))\n#18 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Router.php(753): Illuminate\\\\Routing\\\\Router->dispatchToRoute(Object(Illuminate\\\\Http\\\\Request))\n#19 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Http/Kernel.php(200): Illuminate\\\\Routing\\\\Router->dispatch(Object(Illuminate\\\\Http\\\\Request))\n#20 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(180): Illuminate\\\\Foundation\\\\Http\\\\Kernel->Illuminate\\\\Foundation\\\\Http\\\\{closure}(Object(Illuminate\\\\Http\\\\Request))\n#21 /home/jiminny/vendor/barryvdh/laravel-debugbar/src/Middleware/InjectDebugbar.php(59): Illuminate\\\\Pipeline\\\\Pipeline->Illuminate\\\\Pipeline\\\\{closure}(Object(Illuminate\\\\Http\\\\Request))\n#22 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(219): Barryvdh\\\\Debugbar\\\\Middleware\\\\InjectDebugbar->handle(Object(Illuminate\\\\Http\\\\Request), Object(Closure))\n#23 /home/jiminny/vendor/laravel/framework/src/Illuminate/Http/Middleware/HandleCors.php(74): Illuminate\\\\Pipeline\\\\Pipeline->Illuminate\\\\Pipeline\\\\{closure}(Object(Illuminate\\\\Http\\\\Request))\n#24 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(219): Illuminate\\\\Http\\\\Middleware\\\\HandleCors->handle(Object(Illuminate\\\\Http\\\\Request), Object(Closure))\n#25 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Http/Middleware/TransformsRequest.php(21): Illuminate\\\\Pipeline\\\\Pipeline->Illuminate\\\\Pipeline\\\\{closure}(Object(Illuminate\\\\Http\\\\Request))\n#26 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Http/Middleware/TrimStrings.php(51): Illuminate\\\\Foundation\\\\Http\\\\Middleware\\\\TransformsRequest->handle(Object(Illuminate\\\\Http\\\\Request), Object(Closure))\n#27 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(219): Illuminate\\\\Foundation\\\\Http\\\\Middleware\\\\TrimStrings->handle(Object(Illuminate\\\\Http\\\\Request), Object(Closure))\n#28 /home/jiminny/vendor/bepsvpt/secure-headers/src/SecureHeadersMiddleware.php(18): Illuminate\\\\Pipeline\\\\Pipeline->Illuminate\\\\Pipeline\\\\{closure}(Object(Illuminate\\\\Http\\\\Request))\n#29 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(219): Bepsvpt\\\\SecureHeaders\\\\SecureHeadersMiddleware->handle(Object(Illuminate\\\\Http\\\\Request), Object(Closure))\n#30 /home/jiminny/app/Http/Middleware/SentryContext.php(60): Illuminate\\\\Pipeline\\\\Pipeline->Illuminate\\\\Pipeline\\\\{closure}(Object(Illuminate\\\\Http\\\\Request))\n#31 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(219): Jiminny\\\\Http\\\\Middleware\\\\SentryContext->handle(Object(Illuminate\\\\Http\\\\Request), Object(Closure))\n#32 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Http/Middleware/PreventRequestsDuringMaintenance.php(109): Illuminate\\\\Pipeline\\\\Pipeline->Illuminate\\\\Pipeline\\\\{closure}(Object(Illuminate\\\\Http\\\\Request))\n#33 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(219): Illuminate\\\\Foundation\\\\Http\\\\Middleware\\\\PreventRequestsDuringMaintenance->handle(Object(Illuminate\\\\Http\\\\Request), Object(Closure))\n#34 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Http/Middleware/InvokeDeferredCallbacks.php(22): Illuminate\\\\Pipeline\\\\Pipeline->Illuminate\\\\Pipeline\\\\{closure}(Object(Illuminate\\\\Http\\\\Request))\n#35 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(219): Illuminate\\\\Foundation\\\\Http\\\\Middleware\\\\InvokeDeferredCallbacks->handle(Object(Illuminate\\\\Http\\\\Request), Object(Closure))\n#36 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(137): Illuminate\\\\Pipeline\\\\Pipeline->Illuminate\\\\Pipeline\\\\{closure}(Object(Illuminate\\\\Http\\\\Request))\n#37 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Http/Kernel.php(175): Illuminate\\\\Pipeline\\\\Pipeline->then(Object(Closure))\n#38 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Http/Kernel.php(144): Illuminate\\\\Foundation\\\\Http\\\\Kernel->sendRequestThroughRouter(Object(Illuminate\\\\Http\\\\Request))\n#39 /home/jiminny/public/index.php(51): Illuminate\\\\Foundation\\\\Http\\\\Kernel->handle(Object(Illuminate\\\\Http\\\\Request))\n#40 {main}\n\n[previous exception] [object] (ReflectionException(code: -1): Class \\\"Jiminny\\\\Repositories\\\\AjReportsRepository\\\" does not exist at /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php:1122)\n[stacktrace]\n#0 /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php(1122): ReflectionClass->__construct('Jiminny\\\\\\\\Reposit...')\n#1 /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php(933): Illuminate\\\\Container\\\\Container->build('Jiminny\\\\\\\\Reposit...')\n#2 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Application.php(1078): Illuminate\\\\Container\\\\Container->resolve('Jiminny\\\\\\\\Reposit...', Array, true)\n#3 /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php(864): Illuminate\\\\Foundation\\\\Application->resolve('Jiminny\\\\\\\\Reposit...', Array)\n#4 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Application.php(1058): Illuminate\\\\Container\\\\Container->make('Jiminny\\\\\\\\Reposit...', Array)\n#5 /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php(1336): Illuminate\\\\Foundation\\\\Application->make('Jiminny\\\\\\\\Reposit...')\n#6 /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php(1237): Illuminate\\\\Container\\\\Container->resolveClass(Object(ReflectionParameter))\n#7 /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php(1162): Illuminate\\\\Container\\\\Container->resolveDependencies(Array)\n#8 /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php(933): Illuminate\\\\Container\\\\Container->build('Jiminny\\\\\\\\Http\\\\\\\\Co...')\n#9 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Application.php(1078): Illuminate\\\\Container\\\\Container->resolve('Jiminny\\\\\\\\Http\\\\\\\\Co...', Array, true)\n#10 /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php(864): Illuminate\\\\Foundation\\\\Application->resolve('Jiminny\\\\\\\\Http\\\\\\\\Co...', Array)\n#11 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Application.php(1058): Illuminate\\\\Container\\\\Container->make('Jiminny\\\\\\\\Http\\\\\\\\Co...', Array)\n#12 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Route.php(286): Illuminate\\\\Foundation\\\\Application->make('Jiminny\\\\\\\\Http\\\\\\\\Co...')\n#13 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Route.php(1133): Illuminate\\\\Routing\\\\Route->getController()\n#14 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Route.php(1062): Illuminate\\\\Routing\\\\Route->controllerMiddleware()\n#15 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Router.php(834): Illuminate\\\\Routing\\\\Route->gatherMiddleware()\n#16 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Router.php(816): Illuminate\\\\Routing\\\\Router->gatherRouteMiddleware(Object(Illuminate\\\\Routing\\\\Route))\n#17 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Router.php(800): Illuminate\\\\Routing\\\\Router->runRouteWithinStack(Object(Illuminate\\\\Routing\\\\Route), Object(Illuminate\\\\Http\\\\Request))\n#18 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Router.php(764): Illuminate\\\\Routing\\\\Router->runRoute(Object(Illuminate\\\\Http\\\\Request), Object(Illuminate\\\\Routing\\\\Route))\n#19 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Router.php(753): Illuminate\\\\Routing\\\\Router->dispatchToRoute(Object(Illuminate\\\\Http\\\\Request))\n#20 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Http/Kernel.php(200): Illuminate\\\\Routing\\\\Router->dispatch(Object(Illuminate\\\\Http\\\\Request))\n#21 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(180): Illuminate\\\\Foundation\\\\Http\\\\Kernel->Illuminate\\\\Foundation\\\\Http\\\\{closure}(Object(Illuminate\\\\Http\\\\Request))\n#22 /home/jiminny/vendor/barryvdh/laravel-debugbar/src/Middleware/InjectDebugbar.php(59): Illuminate\\\\Pipeline\\\\Pipeline->Illuminate\\\\Pipeline\\\\{closure}(Object(Illuminate\\\\Http\\\\Request))\n#23 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(219): Barryvdh\\\\Debugbar\\\\Middleware\\\\InjectDebugbar->handle(Object(Illuminate\\\\Http\\\\Request), Object(Closure))\n#24 /home/jiminny/vendor/laravel/framework/src/Illuminate/Http/Middleware/HandleCors.php(74): Illuminate\\\\Pipeline\\\\Pipeline->Illuminate\\\\Pipeline\\\\{closure}(Object(Illuminate\\\\Http\\\\Request))\n#25 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(219): Illuminate\\\\Http\\\\Middleware\\\\HandleCors->handle(Object(Illuminate\\\\Http\\\\Request), Object(Closure))\n#26 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Http/Middleware/TransformsRequest.php(21): Illuminate\\\\Pipeline\\\\Pipeline->Illuminate\\\\Pipeline\\\\{closure}(Object(Illuminate\\\\Http\\\\Request))\n#27 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Http/Middleware/TrimStrings.php(51): Illuminate\\\\Foundation\\\\Http\\\\Middleware\\\\TransformsRequest->handle(Object(Illuminate\\\\Http\\\\Request), Object(Closure))\n#28 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(219): Illuminate\\\\Foundation\\\\Http\\\\Middleware\\\\TrimStrings->handle(Object(Illuminate\\\\Http\\\\Request), Object(Closure))\n#29 /home/jiminny/vendor/bepsvpt/secure-headers/src/SecureHeadersMiddleware.php(18): Illuminate\\\\Pipeline\\\\Pipeline->Illuminate\\\\Pipeline\\\\{closure}(Object(Illuminate\\\\Http\\\\Request))\n#30 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(219): Bepsvpt\\\\SecureHeaders\\\\SecureHeadersMiddleware->handle(Object(Illuminate\\\\Http\\\\Request), Object(Closure))\n#31 /home/jiminny/app/Http/Middleware/SentryContext.php(60): Illuminate\\\\Pipeline\\\\Pipeline->Illuminate\\\\Pipeline\\\\{closure}(Object(Illuminate\\\\Http\\\\Request))\n#32 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(219): Jiminny\\\\Http\\\\Middleware\\\\SentryContext->handle(Object(Illuminate\\\\Http\\\\Request), Object(Closure))\n#33 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Http/Middleware/PreventRequestsDuringMaintenance.php(109): Illuminate\\\\Pipeline\\\\Pipeline->Illuminate\\\\Pipeline\\\\{closure}(Object(Illuminate\\\\Http\\\\Request))\n#34 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(219): Illuminate\\\\Foundation\\\\Http\\\\Middleware\\\\PreventRequestsDuringMaintenance->handle(Object(Illuminate\\\\Http\\\\Request), Object(Closure))\n#35 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Http/Middleware/InvokeDeferredCallbacks.php(22): Illuminate\\\\Pipeline\\\\Pipeline->Illuminate\\\\Pipeline\\\\{closure}(Object(Illuminate\\\\Http\\\\Request))\n#36 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(219): Illuminate\\\\Foundation\\\\Http\\\\Middleware\\\\InvokeDeferredCallbacks->handle(Object(Illuminate\\\\Http\\\\Request), Object(Closure))\n#37 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(137): Illuminate\\\\Pipeline\\\\Pipeline->Illuminate\\\\Pipeline\\\\{closure}(Object(Illuminate\\\\Http\\\\Request))\n#38 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Http/Kernel.php(175): Illuminate\\\\Pipeline\\\\Pipeline->then(Object(Closure))\n#39 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Http/Kernel.php(144): Illuminate\\\\Foundation\\\\Http\\\\Kernel->sendRequestThroughRouter(Object(Illuminate\\\\Http\\\\Request))\n#40 /home/jiminny/public/index.php(51): Illuminate\\\\Foundation\\\\Http\\\\Kernel->handle(Object(Illuminate\\\\Http\\\\Request))\n#41 {main}\n\"} {\"correlation_id\":\"de768781-5c05-418c-baf3-79f77a8c8694\",\"trace_id\":\"fec74062-3f6d-4cfc-968a-1bb285496db3\"}\n[2026-04-27 10:28:12] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage before starting command {\"command\":\"mailbox:skip-lists:refresh\",\"memoryBeforeCommandInMb\":60.0,\"memoryPeakBeforeCommandInMb\":99.727} {\"correlation_id\":\"907d15ed-8c84-41a1-a473-590eebb55dbc\",\"trace_id\":\"ec7bd353-516e-491d-89dd-de44136c1672\"}\n[2026-04-27 10:28:12] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage for command {\"command\":\"mailbox:skip-lists:refresh\",\"memoryBeforeCommandInMb\":60.0,\"memoryAfterCommandInMB\":62.0,\"memoryPeakBeforeCommandInMb\":99.727,\"memoryPeakAfterCommandInMB\":99.727} {\"correlation_id\":\"907d15ed-8c84-41a1-a473-590eebb55dbc\",\"trace_id\":\"ec7bd353-516e-491d-89dd-de44136c1672\"}\n[2026-04-27 10:28:13] local.ERROR: Target class [Jiminny\\Repositories\\AjReportsRepository] does not exist. {\"exception\":\"[object] (Illuminate\\\\Contracts\\\\Container\\\\BindingResolutionException(code: 0): Target class [Jiminny\\\\Repositories\\\\AjReportsRepository] does not exist. at /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php:1124)\n[stacktrace]\n#0 /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php(933): Illuminate\\\\Container\\\\Container->build('Jiminny\\\\\\\\Reposit...')\n#1 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Application.php(1078): Illuminate\\\\Container\\\\Container->resolve('Jiminny\\\\\\\\Reposit...', Array, true)\n#2 /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php(864): Illuminate\\\\Foundation\\\\Application->resolve('Jiminny\\\\\\\\Reposit...', Array)\n#3 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Application.php(1058): Illuminate\\\\Container\\\\Container->make('Jiminny\\\\\\\\Reposit...', Array)\n#4 /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php(1336): Illuminate\\\\Foundation\\\\Application->make('Jiminny\\\\\\\\Reposit...')\n#5 /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php(1237): Illuminate\\\\Container\\\\Container->resolveClass(Object(ReflectionParameter))\n#6 /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php(1162): Illuminate\\\\Container\\\\Container->resolveDependencies(Array)\n#7 /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php(933): Illuminate\\\\Container\\\\Container->build('Jiminny\\\\\\\\Http\\\\\\\\Co...')\n#8 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Application.php(1078): Illuminate\\\\Container\\\\Container->resolve('Jiminny\\\\\\\\Http\\\\\\\\Co...', Array, true)\n#9 /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php(864): Illuminate\\\\Foundation\\\\Application->resolve('Jiminny\\\\\\\\Http\\\\\\\\Co...', Array)\n#10 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Application.php(1058): Illuminate\\\\Container\\\\Container->make('Jiminny\\\\\\\\Http\\\\\\\\Co...', Array)\n#11 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Route.php(286): Illuminate\\\\Foundation\\\\Application->make('Jiminny\\\\\\\\Http\\\\\\\\Co...')\n#12 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Route.php(1133): Illuminate\\\\Routing\\\\Route->getController()\n#13 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Route.php(1062): Illuminate\\\\Routing\\\\Route->controllerMiddleware()\n#14 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Router.php(834): Illuminate\\\\Routing\\\\Route->gatherMiddleware()\n#15 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Router.php(816): Illuminate\\\\Routing\\\\Router->gatherRouteMiddleware(Object(Illuminate\\\\Routing\\\\Route))\n#16 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Router.php(800): Illuminate\\\\Routing\\\\Router->runRouteWithinStack(Object(Illuminate\\\\Routing\\\\Route), Object(Illuminate\\\\Http\\\\Request))\n#17 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Router.php(764): Illuminate\\\\Routing\\\\Router->runRoute(Object(Illuminate\\\\Http\\\\Request), Object(Illuminate\\\\Routing\\\\Route))\n#18 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Router.php(753): Illuminate\\\\Routing\\\\Router->dispatchToRoute(Object(Illuminate\\\\Http\\\\Request))\n#19 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Http/Kernel.php(200): Illuminate\\\\Routing\\\\Router->dispatch(Object(Illuminate\\\\Http\\\\Request))\n#20 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(180): Illuminate\\\\Foundation\\\\Http\\\\Kernel->Illuminate\\\\Foundation\\\\Http\\\\{closure}(Object(Illuminate\\\\Http\\\\Request))\n#21 /home/jiminny/vendor/barryvdh/laravel-debugbar/src/Middleware/InjectDebugbar.php(59): Illuminate\\\\Pipeline\\\\Pipeline->Illuminate\\\\Pipeline\\\\{closure}(Object(Illuminate\\\\Http\\\\Request))\n#22 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(219): Barryvdh\\\\Debugbar\\\\Middleware\\\\InjectDebugbar->handle(Object(Illuminate\\\\Http\\\\Request), Object(Closure))\n#23 /home/jiminny/vendor/laravel/framework/src/Illuminate/Http/Middleware/HandleCors.php(74): Illuminate\\\\Pipeline\\\\Pipeline->Illuminate\\\\Pipeline\\\\{closure}(Object(Illuminate\\\\Http\\\\Request))\n#24 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(219): Illuminate\\\\Http\\\\Middleware\\\\HandleCors->handle(Object(Illuminate\\\\Http\\\\Request), Object(Closure))\n#25 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Http/Middleware/TransformsRequest.php(21): Illuminate\\\\Pipeline\\\\Pipeline->Illuminate\\\\Pipeline\\\\{closure}(Object(Illuminate\\\\Http\\\\Request))\n#26 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Http/Middleware/TrimStrings.php(51): Illuminate\\\\Foundation\\\\Http\\\\Middleware\\\\TransformsRequest->handle(Object(Illuminate\\\\Http\\\\Request), Object(Closure))\n#27 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(219): Illuminate\\\\Foundation\\\\Http\\\\Middleware\\\\TrimStrings->handle(Object(Illuminate\\\\Http\\\\Request), Object(Closure))\n#28 /home/jiminny/vendor/bepsvpt/secure-headers/src/SecureHeadersMiddleware.php(18): Illuminate\\\\Pipeline\\\\Pipeline->Illuminate\\\\Pipeline\\\\{closure}(Object(Illuminate\\\\Http\\\\Request))\n#29 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(219): Bepsvpt\\\\SecureHeaders\\\\SecureHeadersMiddleware->handle(Object(Illuminate\\\\Http\\\\Request), Object(Closure))\n#30 /home/jiminny/app/Http/Middleware/SentryContext.php(60): Illuminate\\\\Pipeline\\\\Pipeline->Illuminate\\\\Pipeline\\\\{closure}(Object(Illuminate\\\\Http\\\\Request))\n#31 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(219): Jiminny\\\\Http\\\\Middleware\\\\SentryContext->handle(Object(Illuminate\\\\Http\\\\Request), Object(Closure))\n#32 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Http/Middleware/PreventRequestsDuringMaintenance.php(109): Illuminate\\\\Pipeline\\\\Pipeline->Illuminate\\\\Pipeline\\\\{closure}(Object(Illuminate\\\\Http\\\\Request))\n#33 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(219): Illuminate\\\\Foundation\\\\Http\\\\Middleware\\\\PreventRequestsDuringMaintenance->handle(Object(Illuminate\\\\Http\\\\Request), Object(Closure))\n#34 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Http/Middleware/InvokeDeferredCallbacks.php(22): Illuminate\\\\Pipeline\\\\Pipeline->Illuminate\\\\Pipeline\\\\{closure}(Object(Illuminate\\\\Http\\\\Request))\n#35 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(219): Illuminate\\\\Foundation\\\\Http\\\\Middleware\\\\InvokeDeferredCallbacks->handle(Object(Illuminate\\\\Http\\\\Request), Object(Closure))\n#36 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(137): Illuminate\\\\Pipeline\\\\Pipeline->Illuminate\\\\Pipeline\\\\{closure}(Object(Illuminate\\\\Http\\\\Request))\n#37 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Http/Kernel.php(175): Illuminate\\\\Pipeline\\\\Pipeline->then(Object(Closure))\n#38 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Http/Kernel.php(144): Illuminate\\\\Foundation\\\\Http\\\\Kernel->sendRequestThroughRouter(Object(Illuminate\\\\Http\\\\Request))\n#39 /home/jiminny/public/index.php(51): Illuminate\\\\Foundation\\\\Http\\\\Kernel->handle(Object(Illuminate\\\\Http\\\\Request))\n#40 {main}\n\n[previous exception] [object] (ReflectionException(code: -1): Class \\\"Jiminny\\\\Repositories\\\\AjReportsRepository\\\" does not exist at /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php:1122)\n[stacktrace]\n#0 /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php(1122): ReflectionClass->__construct('Jiminny\\\\\\\\Reposit...')\n#1 /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php(933): Illuminate\\\\Container\\\\Container->build('Jiminny\\\\\\\\Reposit...')\n#2 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Application.php(1078): Illuminate\\\\Container\\\\Container->resolve('Jiminny\\\\\\\\Reposit...', Array, true)\n#3 /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php(864): Illuminate\\\\Foundation\\\\Application->resolve('Jiminny\\\\\\\\Reposit...', Array)\n#4 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Application.php(1058): Illuminate\\\\Container\\\\Container->make('Jiminny\\\\\\\\Reposit...', Array)\n#5 /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php(1336): Illuminate\\\\Foundation\\\\Application->make('Jiminny\\\\\\\\Reposit...')\n#6 /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php(1237): Illuminate\\\\Container\\\\Container->resolveClass(Object(ReflectionParameter))\n#7 /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php(1162): Illuminate\\\\Container\\\\Container->resolveDependencies(Array)\n#8 /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php(933): Illuminate\\\\Container\\\\Container->build('Jiminny\\\\\\\\Http\\\\\\\\Co...')\n#9 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Application.php(1078): Illuminate\\\\Container\\\\Container->resolve('Jiminny\\\\\\\\Http\\\\\\\\Co...', Array, true)\n#10 /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php(864): Illuminate\\\\Foundation\\\\Application->resolve('Jiminny\\\\\\\\Http\\\\\\\\Co...', Array)\n#11 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Application.php(1058): Illuminate\\\\Container\\\\Container->make('Jiminny\\\\\\\\Http\\\\\\\\Co...', Array)\n#12 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Route.php(286): Illuminate\\\\Foundation\\\\Application->make('Jiminny\\\\\\\\Http\\\\\\\\Co...')\n#13 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Route.php(1133): Illuminate\\\\Routing\\\\Route->getController()\n#14 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Route.php(1062): Illuminate\\\\Routing\\\\Route->controllerMiddleware()\n#15 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Router.php(834): Illuminate\\\\Routing\\\\Route->gatherMiddleware()\n#16 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Router.php(816): Illuminate\\\\Routing\\\\Router->gatherRouteMiddleware(Object(Illuminate\\\\Routing\\\\Route))\n#17 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Router.php(800): Illuminate\\\\Routing\\\\Router->runRouteWithinStack(Object(Illuminate\\\\Routing\\\\Route), Object(Illuminate\\\\Http\\\\Request))\n#18 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Router.php(764): Illuminate\\\\Routing\\\\Router->runRoute(Object(Illuminate\\\\Http\\\\Request), Object(Illuminate\\\\Routing\\\\Route))\n#19 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Router.php(753): Illuminate\\\\Routing\\\\Router->dispatchToRoute(Object(Illuminate\\\\Http\\\\Request))\n#20 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Http/Kernel.php(200): Illuminate\\\\Routing\\\\Router->dispatch(Object(Illuminate\\\\Http\\\\Request))\n#21 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(180): Illuminate\\\\Foundation\\\\Http\\\\Kernel->Illuminate\\\\Foundation\\\\Http\\\\{closure}(Object(Illuminate\\\\Http\\\\Request))\n#22 /home/jiminny/vendor/barryvdh/laravel-debugbar/src/Middleware/InjectDebugbar.php(59): Illuminate\\\\Pipeline\\\\Pipeline->Illuminate\\\\Pipeline\\\\{closure}(Object(Illuminate\\\\Http\\\\Request))\n#23 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(219): Barryvdh\\\\Debugbar\\\\Middleware\\\\InjectDebugbar->handle(Object(Illuminate\\\\Http\\\\Request), Object(Closure))\n#24 /home/jiminny/vendor/laravel/framework/src/Illuminate/Http/Middleware/HandleCors.php(74): Illuminate\\\\Pipeline\\\\Pipeline->Illuminate\\\\Pipeline\\\\{closure}(Object(Illuminate\\\\Http\\\\Request))\n#25 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(219): Illuminate\\\\Http\\\\Middleware\\\\HandleCors->handle(Object(Illuminate\\\\Http\\\\Request), Object(Closure))\n#26 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Http/Middleware/TransformsRequest.php(21): Illuminate\\\\Pipeline\\\\Pipeline->Illuminate\\\\Pipeline\\\\{closure}(Object(Illuminate\\\\Http\\\\Request))\n#27 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Http/Middleware/TrimStrings.php(51): Illuminate\\\\Foundation\\\\Http\\\\Middleware\\\\TransformsRequest->handle(Object(Illuminate\\\\Http\\\\Request), Object(Closure))\n#28 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(219): Illuminate\\\\Foundation\\\\Http\\\\Middleware\\\\TrimStrings->handle(Object(Illuminate\\\\Http\\\\Request), Object(Closure))\n#29 /home/jiminny/vendor/bepsvpt/secure-headers/src/SecureHeadersMiddleware.php(18): Illuminate\\\\Pipeline\\\\Pipeline->Illuminate\\\\Pipeline\\\\{closure}(Object(Illuminate\\\\Http\\\\Request))\n#30 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(219): Bepsvpt\\\\SecureHeaders\\\\SecureHeadersMiddleware->handle(Object(Illuminate\\\\Http\\\\Request), Object(Closure))\n#31 /home/jiminny/app/Http/Middleware/SentryContext.php(60): Illuminate\\\\Pipeline\\\\Pipeline->Illuminate\\\\Pipeline\\\\{closure}(Object(Illuminate\\\\Http\\\\Request))\n#32 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(219): Jiminny\\\\Http\\\\Middleware\\\\SentryContext->handle(Object(Illuminate\\\\Http\\\\Request), Object(Closure))\n#33 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Http/Middleware/PreventRequestsDuringMaintenance.php(109): Illuminate\\\\Pipeline\\\\Pipeline->Illuminate\\\\Pipeline\\\\{closure}(Object(Illuminate\\\\Http\\\\Request))\n#34 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(219): Illuminate\\\\Foundation\\\\Http\\\\Middleware\\\\PreventRequestsDuringMaintenance->handle(Object(Illuminate\\\\Http\\\\Request), Object(Closure))\n#35 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Http/Middleware/InvokeDeferredCallbacks.php(22): Illuminate\\\\Pipeline\\\\Pipeline->Illuminate\\\\Pipeline\\\\{closure}(Object(Illuminate\\\\Http\\\\Request))\n#36 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(219): Illuminate\\\\Foundation\\\\Http\\\\Middleware\\\\InvokeDeferredCallbacks->handle(Object(Illuminate\\\\Http\\\\Request), Object(Closure))\n#37 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(137): Illuminate\\\\Pipeline\\\\Pipeline->Illuminate\\\\Pipeline\\\\{closure}(Object(Illuminate\\\\Http\\\\Request))\n#38 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Http/Kernel.php(175): Illuminate\\\\Pipeline\\\\Pipeline->then(Object(Closure))\n#39 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Http/Kernel.php(144): Illuminate\\\\Foundation\\\\Http\\\\Kernel->sendRequestThroughRouter(Object(Illuminate\\\\Http\\\\Request))\n#40 /home/jiminny/public/index.php(51): Illuminate\\\\Foundation\\\\Http\\\\Kernel->handle(Object(Illuminate\\\\Http\\\\Request))\n#41 {main}\n\"} {\"correlation_id\":\"d8709bb9-0f8c-4798-a8c7-e68e24ab6265\",\"trace_id\":\"c5bea1cc-53fe-4bda-a8e2-bec002030bc2\"}\n[2026-04-27 10:28:14] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage before starting command {\"command\":\"mailbox:batch:process\",\"memoryBeforeCommandInMb\":60.0,\"memoryPeakBeforeCommandInMb\":99.727} {\"correlation_id\":\"5fb3c8c8-3160-4964-ac56-02d6e2cb998c\",\"trace_id\":\"3a6d5e64-ecf2-4700-98cd-3edd56a1dc34\"}\n[2026-04-27 10:28:14] local.INFO: [EmailSchedule] STARTING batch process {\"host\":\"docker_lamp_1\"} {\"correlation_id\":\"5fb3c8c8-3160-4964-ac56-02d6e2cb998c\",\"trace_id\":\"3a6d5e64-ecf2-4700-98cd-3edd56a1dc34\"}\n[2026-04-27 10:28:14] local.INFO: Processing email batch 98446 for inbox 212 {\"correlation_id\":\"5fb3c8c8-3160-4964-ac56-02d6e2cb998c\",\"trace_id\":\"3a6d5e64-ecf2-4700-98cd-3edd56a1dc34\"}\n[2026-04-27 10:28:14] local.INFO: [SocialAccountService] Fetching token {\"socialAccountId\":1500,\"provider\":\"salesforce\"} {\"correlation_id\":\"5fb3c8c8-3160-4964-ac56-02d6e2cb998c\",\"trace_id\":\"3a6d5e64-ecf2-4700-98cd-3edd56a1dc34\"}\n[2026-04-27 10:28:14] local.INFO: [SocialAccountService] Token retrieved {\"socialAccountId\":1500,\"provider\":\"salesforce\"} {\"correlation_id\":\"5fb3c8c8-3160-4964-ac56-02d6e2cb998c\",\"trace_id\":\"3a6d5e64-ecf2-4700-98cd-3edd56a1dc34\"}\n[2026-04-27 10:28:14] local.INFO: [EncryptedTokenManager] Generating access token. {\"mode\":\"legacy\"} {\"correlation_id\":\"5fb3c8c8-3160-4964-ac56-02d6e2cb998c\",\"trace_id\":\"3a6d5e64-ecf2-4700-98cd-3edd56a1dc34\"}\n[2026-04-27 10:28:14] local.INFO: [CrmOwnerResolver] Integration owner matched as CRM Owner {\"crm_provider\":\"salesforce\",\"crm_owner\":143,\"team_id\":1} {\"correlation_id\":\"5fb3c8c8-3160-4964-ac56-02d6e2cb998c\",\"trace_id\":\"3a6d5e64-ecf2-4700-98cd-3edd56a1dc34\"}\n[2026-04-27 10:28:14] local.INFO: [SocialAccountService] Fetching token {\"socialAccountId\":1354,\"provider\":\"google\"} {\"correlation_id\":\"5fb3c8c8-3160-4964-ac56-02d6e2cb998c\",\"trace_id\":\"3a6d5e64-ecf2-4700-98cd-3edd56a1dc34\"}\n[2026-04-27 10:28:14] local.INFO: [SocialAccountService] Token retrieved {\"socialAccountId\":1354,\"provider\":\"google\"} {\"correlation_id\":\"5fb3c8c8-3160-4964-ac56-02d6e2cb998c\",\"trace_id\":\"3a6d5e64-ecf2-4700-98cd-3edd56a1dc34\"}\n[2026-04-27 10:28:14] local.INFO: [EncryptedTokenManager] Generating access token. {\"mode\":\"legacy\"} {\"correlation_id\":\"5fb3c8c8-3160-4964-ac56-02d6e2cb998c\",\"trace_id\":\"3a6d5e64-ecf2-4700-98cd-3edd56a1dc34\"}\n[2026-04-27 10:28:15] local.INFO: Processing an email from inbox batch {\"batch\":98446,\"inbox_id\":212,\"email\":\"lukas.kovalik@jiminny.com\",\"email_id\":\"19dce788ac964bbf\",\"from\":\"Nikolay Yankov <notifications@github.com>\",\"to\":\"\\\"jiminny/app\\\" <app@noreply.github.com>\",\"cc\":\"Push <push@noreply.github.com>\"} {\"correlation_id\":\"5fb3c8c8-3160-4964-ac56-02d6e2cb998c\",\"trace_id\":\"3a6d5e64-ecf2-4700-98cd-3edd56a1dc34\"}\n[2026-04-27 10:28:15] local.INFO: [SocialAccountService] Fetching token {\"socialAccountId\":1500,\"provider\":\"salesforce\"} {\"correlation_id\":\"5fb3c8c8-3160-4964-ac56-02d6e2cb998c\",\"trace_id\":\"3a6d5e64-ecf2-4700-98cd-3edd56a1dc34\"}\n[2026-04-27 10:28:15] local.INFO: [SocialAccountService] Token retrieved {\"socialAccountId\":1500,\"provider\":\"salesforce\"} {\"correlation_id\":\"5fb3c8c8-3160-4964-ac56-02d6e2cb998c\",\"trace_id\":\"3a6d5e64-ecf2-4700-98cd-3edd56a1dc34\"}\n[2026-04-27 10:28:15] local.INFO: [EncryptedTokenManager] Generating access token. {\"mode\":\"legacy\"} {\"correlation_id\":\"5fb3c8c8-3160-4964-ac56-02d6e2cb998c\",\"trace_id\":\"3a6d5e64-ecf2-4700-98cd-3edd56a1dc34\"}\n[2026-04-27 10:28:15] local.INFO: [CrmOwnerResolver] Integration owner matched as CRM Owner {\"crm_provider\":\"salesforce\",\"crm_owner\":143,\"team_id\":1} {\"correlation_id\":\"5fb3c8c8-3160-4964-ac56-02d6e2cb998c\",\"trace_id\":\"3a6d5e64-ecf2-4700-98cd-3edd56a1dc34\"}\n[2026-04-27 10:28:15] local.INFO: [EmailImport\\ParticipantsResolver] The sender email is blacklisted, skipping {\"email\":\"notifications@github.com\",\"inbox_id\":212,\"message_provider_id\":\"19dce788ac964bbf\",\"team_id\":1} {\"correlation_id\":\"5fb3c8c8-3160-4964-ac56-02d6e2cb998c\",\"trace_id\":\"3a6d5e64-ecf2-4700-98cd-3edd56a1dc34\"}\n[2026-04-27 10:28:15] local.INFO: [EmailImport\\ParticipantsValidator] Email participants are less than 2 {\"inbox_id\":212,\"message_provider_id\":\"19dce788ac964bbf\",\"message_id\":\"<jiminny/app/pull/11998/before/2b8a97d6be8b053441d5a845b72dd04803cbb53c/after/8a68a94d39cc76ae546e19f354e8b0dabd577314@github.com>\"} {\"correlation_id\":\"5fb3c8c8-3160-4964-ac56-02d6e2cb998c\",\"trace_id\":\"3a6d5e64-ecf2-4700-98cd-3edd56a1dc34\"}\n[2026-04-27 10:28:15] local.INFO: Processing an email from inbox batch {\"batch\":98446,\"inbox_id\":212,\"email\":\"lukas.kovalik@jiminny.com\",\"email_id\":\"19dce7883f2fea19\",\"from\":\"The Jiminny Team <no-reply@dev.jiminny.com>\",\"to\":\"lukas.kovalik@jiminny.com\",\"cc\":null} {\"correlation_id\":\"5fb3c8c8-3160-4964-ac56-02d6e2cb998c\",\"trace_id\":\"3a6d5e64-ecf2-4700-98cd-3edd56a1dc34\"}\n[2026-04-27 10:28:15] local.INFO: [SocialAccountService] Fetching token {\"socialAccountId\":1500,\"provider\":\"salesforce\"} {\"correlation_id\":\"5fb3c8c8-3160-4964-ac56-02d6e2cb998c\",\"trace_id\":\"3a6d5e64-ecf2-4700-98cd-3edd56a1dc34\"}\n[2026-04-27 10:28:15] local.INFO: [SocialAccountService] Token retrieved {\"socialAccountId\":1500,\"provider\":\"salesforce\"} {\"correlation_id\":\"5fb3c8c8-3160-4964-ac56-02d6e2cb998c\",\"trace_id\":\"3a6d5e64-ecf2-4700-98cd-3edd56a1dc34\"}\n[2026-04-27 10:28:15] local.INFO: [EncryptedTokenManager] Generating access token. {\"mode\":\"legacy\"} {\"correlation_id\":\"5fb3c8c8-3160-4964-ac56-02d6e2cb998c\",\"trace_id\":\"3a6d5e64-ecf2-4700-98cd-3edd56a1dc34\"}\n[2026-04-27 10:28:15] local.INFO: [CrmOwnerResolver] Integration owner matched as CRM Owner {\"crm_provider\":\"salesforce\",\"crm_owner\":143,\"team_id\":1} {\"correlation_id\":\"5fb3c8c8-3160-4964-ac56-02d6e2cb998c\",\"trace_id\":\"3a6d5e64-ecf2-4700-98cd-3edd56a1dc34\"}\n[2026-04-27 10:28:15] local.INFO: [EmailImport\\ParticipantsResolver] The sender email is blacklisted, skipping {\"email\":\"no-reply@dev.jiminny.com\",\"inbox_id\":212,\"message_provider_id\":\"19dce7883f2fea19\",\"team_id\":1} {\"correlation_id\":\"5fb3c8c8-3160-4964-ac56-02d6e2cb998c\",\"trace_id\":\"3a6d5e64-ecf2-4700-98cd-3edd56a1dc34\"}\n[2026-04-27 10:28:15] local.INFO: [EmailImport\\ParticipantsValidator] Email participants are less than 2 {\"inbox_id\":212,\"message_provider_id\":\"19dce7883f2fea19\",\"message_id\":\"<d70689e8-7b14-42ac-a3bf-5f1e81da943e@mtasv.net>\"} {\"correlation_id\":\"5fb3c8c8-3160-4964-ac56-02d6e2cb998c\",\"trace_id\":\"3a6d5e64-ecf2-4700-98cd-3edd56a1dc34\"}\n[2026-04-27 10:28:15] local.INFO: Processing an email from inbox batch {\"batch\":98446,\"inbox_id\":212,\"email\":\"lukas.kovalik@jiminny.com\",\"email_id\":\"19dce761cb049fba\",\"from\":\"The Jiminny Team <no-reply@dev.jiminny.com>\",\"to\":\"lukas.kovalik@jiminny.com\",\"cc\":null} {\"correlation_id\":\"5fb3c8c8-3160-4964-ac56-02d6e2cb998c\",\"trace_id\":\"3a6d5e64-ecf2-4700-98cd-3edd56a1dc34\"}\n[2026-04-27 10:28:15] local.INFO: [SocialAccountService] Fetching token {\"socialAccountId\":1500,\"provider\":\"salesforce\"} {\"correlation_id\":\"5fb3c8c8-3160-4964-ac56-02d6e2cb998c\",\"trace_id\":\"3a6d5e64-ecf2-4700-98cd-3edd56a1dc34\"}\n[2026-04-27 10:28:15] local.INFO: [SocialAccountService] Token retrieved {\"socialAccountId\":1500,\"provider\":\"salesforce\"} {\"correlation_id\":\"5fb3c8c8-3160-4964-ac56-02d6e2cb998c\",\"trace_id\":\"3a6d5e64-ecf2-4700-98cd-3edd56a1dc34\"}\n[2026-04-27 10:28:15] local.INFO: [EncryptedTokenManager] Generating access token. {\"mode\":\"legacy\"} {\"correlation_id\":\"5fb3c8c8-3160-4964-ac56-02d6e2cb998c\",\"trace_id\":\"3a6d5e64-ecf2-4700-98cd-3edd56a1dc34\"}\n[2026-04-27 10:28:15] local.INFO: [CrmOwnerResolver] Integration owner matched as CRM Owner {\"crm_provider\":\"salesforce\",\"crm_owner\":143,\"team_id\":1} {\"correlation_id\":\"5fb3c8c8-3160-4964-ac56-02d6e2cb998c\",\"trace_id\":\"3a6d5e64-ecf2-4700-98cd-3edd56a1dc34\"}\n[2026-04-27 10:28:15] local.INFO: [EmailImport\\ParticipantsResolver] The sender email is blacklisted, skipping {\"email\":\"no-reply@dev.jiminny.com\",\"inbox_id\":212,\"message_provider_id\":\"19dce761cb049fba\",\"team_id\":1} {\"correlation_id\":\"5fb3c8c8-3160-4964-ac56-02d6e2cb998c\",\"trace_id\":\"3a6d5e64-ecf2-4700-98cd-3edd56a1dc34\"}\n[2026-04-27 10:28:15] local.INFO: [EmailImport\\ParticipantsValidator] Email participants are less than 2 {\"inbox_id\":212,\"message_provider_id\":\"19dce761cb049fba\",\"message_id\":\"<017f0e66-9989-446e-9788-7ae83a99137e@mtasv.net>\"} {\"correlation_id\":\"5fb3c8c8-3160-4964-ac56-02d6e2cb998c\",\"trace_id\":\"3a6d5e64-ecf2-4700-98cd-3edd56a1dc34\"}\n[2026-04-27 10:28:15] local.INFO: Processing an email from inbox batch {\"batch\":98446,\"inbox_id\":212,\"email\":\"lukas.kovalik@jiminny.com\",\"email_id\":\"19dce74ed398d2c1\",\"from\":\"\\\"sonarqubecloud[bot]\\\" <notifications@github.com>\",\"to\":\"\\\"jiminny/app\\\" <app@noreply.github.com>\",\"cc\":\"Lukas Kovalik <kovaliklukas@gmail.com>, Author <author@noreply.github.com>\"} {\"correlation_id\":\"5fb3c8c8-3160-4964-ac56-02d6e2cb998c\",\"trace_id\":\"3a6d5e64-ecf2-4700-98cd-3edd56a1dc34\"}\n[2026-04-27 10:28:15] local.INFO: [SocialAccountService] Fetching token {\"socialAccountId\":1500,\"provider\":\"salesforce\"} {\"correlation_id\":\"5fb3c8c8-3160-4964-ac56-02d6e2cb998c\",\"trace_id\":\"3a6d5e64-ecf2-4700-98cd-3edd56a1dc34\"}\n[2026-04-27 10:28:15] local.INFO: [SocialAccountService] Token retrieved {\"socialAccountId\":1500,\"provider\":\"salesforce\"} {\"correlation_id\":\"5fb3c8c8-3160-4964-ac56-02d6e2cb998c\",\"trace_id\":\"3a6d5e64-ecf2-4700-98cd-3edd56a1dc34\"}\n[2026-04-27 10:28:15] local.INFO: [EncryptedTokenManager] Generating access token. {\"mode\":\"legacy\"} {\"correlation_id\":\"5fb3c8c8-3160-4964-ac56-02d6e2cb998c\",\"trace_id\":\"3a6d5e64-ecf2-4700-98cd-3edd56a1dc34\"}\n[2026-04-27 10:28:15] local.INFO: [CrmOwnerResolver] Integration owner matched as CRM Owner {\"crm_provider\":\"salesforce\",\"crm_owner\":143,\"team_id\":1} {\"correlation_id\":\"5fb3c8c8-3160-4964-ac56-02d6e2cb998c\",\"trace_id\":\"3a6d5e64-ecf2-4700-98cd-3edd56a1dc34\"}\n[2026-04-27 10:28:15] local.INFO: [EmailImport\\ParticipantsResolver] The sender email is blacklisted, skipping {\"email\":\"notifications@github.com\",\"inbox_id\":212,\"message_provider_id\":\"19dce74ed398d2c1\",\"team_id\":1} {\"correlation_id\":\"5fb3c8c8-3160-4964-ac56-02d6e2cb998c\",\"trace_id\":\"3a6d5e64-ecf2-4700-98cd-3edd56a1dc34\"}\n[2026-04-27 10:28:15] local.INFO: [EmailImport\\ParticipantsValidator] Email participants are less than 2 {\"inbox_id\":212,\"message_provider_id\":\"19dce74ed398d2c1\",\"message_id\":\"<jiminny/app/pull/12016/c4326142661@github.com>\"} {\"correlation_id\":\"5fb3c8c8-3160-4964-ac56-02d6e2cb998c\",\"trace_id\":\"3a6d5e64-ecf2-4700-98cd-3edd56a1dc34\"}\n[2026-04-27 10:28:15] local.INFO: Processing an email from inbox batch {\"batch\":98446,\"inbox_id\":212,\"email\":\"lukas.kovalik@jiminny.com\",\"email_id\":\"19dce713dbecf41f\",\"from\":\"\\\"sonarqubecloud[bot]\\\" <notifications@github.com>\",\"to\":\"\\\"jiminny/infrastructure\\\" <infrastructure@noreply.github.com>\",\"cc\":\"Subscribed <subscribed@noreply.github.com>\"} {\"correlation_id\":\"5fb3c8c8-3160-4964-ac56-02d6e2cb998c\",\"trace_id\":\"3a6d5e64-ecf2-4700-98cd-3edd56a1dc34\"}\n[2026-04-27 10:28:15] local.INFO: [SocialAccountService] Fetching token {\"socialAccountId\":1500,\"provider\":\"salesforce\"} {\"correlation_id\":\"5fb3c8c8-3160-4964-ac56-02d6e2cb998c\",\"trace_id\":\"3a6d5e64-ecf2-4700-98cd-3edd56a1dc34\"}\n[2026-04-27 10:28:15] local.INFO: [SocialAccountService] Token retrieved {\"socialAccountId\":1500,\"provider\":\"salesforce\"} {\"correlation_id\":\"5fb3c8c8-3160-4964-ac56-02d6e2cb998c\",\"trace_id\":\"3a6d5e64-ecf2-4700-98cd-3edd56a1dc34\"}\n[2026-04-27 10:28:15] local.INFO: [EncryptedTokenManager] Generating access token. {\"mode\":\"legacy\"} {\"correlation_id\":\"5fb3c8c8-3160-4964-ac56-02d6e2cb998c\",\"trace_id\":\"3a6d5e64-ecf2-4700-98cd-3edd56a1dc34\"}\n[2026-04-27 10:28:15] local.INFO: [CrmOwnerResolver] Integration owner matched as CRM Owner {\"crm_provider\":\"salesforce\",\"crm_owner\":143,\"team_id\":1} {\"correlation_id\":\"5fb3c8c8-3160-4964-ac56-02d6e2cb998c\",\"trace_id\":\"3a6d5e64-ecf2-4700-98cd-3edd56a1dc34\"}\n[2026-04-27 10:28:15] local.INFO: [EmailImport\\ParticipantsResolver] The sender email is blacklisted, skipping {\"email\":\"notifications@github.com\",\"inbox_id\":212,\"message_provider_id\":\"19dce713dbecf41f\",\"team_id\":1} {\"correlation_id\":\"5fb3c8c8-3160-4964-ac56-02d6e2cb998c\",\"trace_id\":\"3a6d5e64-ecf2-4700-98cd-3edd56a1dc34\"}\n[2026-04-27 10:28:15] local.INFO: [EmailImport\\ParticipantsValidator] Email participants are less than 2 {\"inbox_id\":212,\"message_provider_id\":\"19dce713dbecf41f\",\"message_id\":\"<jiminny/infrastructure/pull/725/c4326108250@github.com>\"} {\"correlation_id\":\"5fb3c8c8-3160-4964-ac56-02d6e2cb998c\",\"trace_id\":\"3a6d5e64-ecf2-4700-98cd-3edd56a1dc34\"}\n[2026-04-27 10:28:15] local.INFO: Processing an email from inbox batch {\"batch\":98446,\"inbox_id\":212,\"email\":\"lukas.kovalik@jiminny.com\",\"email_id\":\"19dce708ed8b5acb\",\"from\":\"Veselin Kulov <notifications@github.com>\",\"to\":\"\\\"jiminny/infrastructure\\\" <infrastructure@noreply.github.com>\",\"cc\":\"Push <push@noreply.github.com>\"} {\"correlation_id\":\"5fb3c8c8-3160-4964-ac56-02d6e2cb998c\",\"trace_id\":\"3a6d5e64-ecf2-4700-98cd-3edd56a1dc34\"}\n[2026-04-27 10:28:15] local.INFO: [SocialAccountService] Fetching token {\"socialAccountId\":1500,\"provider\":\"salesforce\"} {\"correlation_id\":\"5fb3c8c8-3160-4964-ac56-02d6e2cb998c\",\"trace_id\":\"3a6d5e64-ecf2-4700-98cd-3edd56a1dc34\"}\n[2026-04-27 10:28:15] local.INFO: [SocialAccountService] Token retrieved {\"socialAccountId\":1500,\"provider\":\"salesforce\"} {\"correlation_id\":\"5fb3c8c8-3160-4964-ac56-02d6e2cb998c\",\"trace_id\":\"3a6d5e64-ecf2-4700-98cd-3edd56a1dc34\"}\n[2026-04-27 10:28:15] local.INFO: [EncryptedTokenManager] Generating access token. {\"mode\":\"legacy\"} {\"correlation_id\":\"5fb3c8c8-3160-4964-ac56-02d6e2cb998c\",\"trace_id\":\"3a6d5e64-ecf2-4700-98cd-3edd56a1dc34\"}\n[2026-04-27 10:28:15] local.INFO: [CrmOwnerResolver] Integration owner matched as CRM Owner {\"crm_provider\":\"salesforce\",\"crm_owner\":143,\"team_id\":1} {\"correlation_id\":\"5fb3c8c8-3160-4964-ac56-02d6e2cb998c\",\"trace_id\":\"3a6d5e64-ecf2-4700-98cd-3edd56a1dc34\"}\n[2026-04-27 10:28:15] local.INFO: [EmailImport\\ParticipantsResolver] The sender email is blacklisted, skipping {\"email\":\"notifications@github.com\",\"inbox_id\":212,\"message_provider_id\":\"19dce708ed8b5acb\",\"team_id\":1} {\"correlation_id\":\"5fb3c8c8-3160-4964-ac56-02d6e2cb998c\",\"trace_id\":\"3a6d5e64-ecf2-4700-98cd-3edd56a1dc34\"}\n[2026-04-27 10:28:15] local.INFO: [EmailImport\\ParticipantsValidator] Email participants are less than 2 {\"inbox_id\":212,\"message_provider_id\":\"19dce708ed8b5acb\",\"message_id\":\"<jiminny/infrastructure/pull/725/before/186952cc584428fcca17aa364f6cc535d65e804a/after/078246a06b25fb93ad61c7f14472ecb755483f4d@github.com>\"} {\"correlation_id\":\"5fb3c8c8-3160-4964-ac56-02d6e2cb998c\",\"trace_id\":\"3a6d5e64-ecf2-4700-98cd-3edd56a1dc34\"}\n[2026-04-27 10:28:15] local.INFO: Processing an email from inbox batch {\"batch\":98446,\"inbox_id\":212,\"email\":\"lukas.kovalik@jiminny.com\",\"email_id\":\"19dce6eea657f2fa\",\"from\":\"\\\"sonarqubecloud[bot]\\\" <notifications@github.com>\",\"to\":\"\\\"jiminny/app\\\" <app@noreply.github.com>\",\"cc\":\"Subscribed <subscribed@noreply.github.com>\"} {\"correlation_id\":\"5fb3c8c8-3160-4964-ac56-02d6e2cb998c\",\"trace_id\":\"3a6d5e64-ecf2-4700-98cd-3edd56a1dc34\"}\n[2026-04-27 10:28:15] local.INFO: [SocialAccountService] Fetching token {\"socialAccountId\":1500,\"provider\":\"salesforce\"} {\"correlation_id\":\"5fb3c8c8-3160-4964-ac56-02d6e2cb998c\",\"trace_id\":\"3a6d5e64-ecf2-4700-98cd-3edd56a1dc34\"}\n[2026-04-27 10:28:15] local.INFO: [SocialAccountService] Token retrieved {\"socialAccountId\":1500,\"provider\":\"salesforce\"} {\"correlation_id\":\"5fb3c8c8-3160-4964-ac56-02d6e2cb998c\",\"trace_id\":\"3a6d5e64-ecf2-4700-98cd-3edd56a1dc34\"}\n[2026-04-27 10:28:15] local.INFO: [EncryptedTokenManager] Generating access token. {\"mode\":\"legacy\"} {\"correlation_id\":\"5fb3c8c8-3160-4964-ac56-02d6e2cb998c\",\"trace_id\":\"3a6d5e64-ecf2-4700-98cd-3edd56a1dc34\"}\n[2026-04-27 10:28:15] local.INFO: [CrmOwnerResolver] Integration owner matched as CRM Owner {\"crm_provider\":\"salesforce\",\"crm_owner\":143,\"team_id\":1} {\"correlation_id\":\"5fb3c8c8-3160-4964-ac56-02d6e2cb998c\",\"trace_id\":\"3a6d5e64-ecf2-4700-98cd-3edd56a1dc34\"}\n[2026-04-27 10:28:15] local.INFO: [EmailImport\\ParticipantsResolver] The sender email is blacklisted, skipping {\"email\":\"notifications@github.com\",\"inbox_id\":212,\"message_provider_id\":\"19dce6eea657f2fa\",\"team_id\":1} {\"correlation_id\":\"5fb3c8c8-3160-4964-ac56-02d6e2cb998c\",\"trace_id\":\"3a6d5e64-ecf2-4700-98cd-3edd56a1dc34\"}\n[2026-04-27 10:28:15] local.INFO: [EmailImport\\ParticipantsValidator] Email participants are less than 2 {\"inbox_id\":212,\"message_provider_id\":\"19dce6eea657f2fa\",\"message_id\":\"<jiminny/app/pull/11998/c4326088534@github.com>\"} {\"correlation_id\":\"5fb3c8c8-3160-4964-ac56-02d6e2cb998c\",\"trace_id\":\"3a6d5e64-ecf2-4700-98cd-3edd56a1dc34\"}\n[2026-04-27 10:28:15] local.INFO: Deleting successfully processed batch 98446 for inbox 212 {\"correlation_id\":\"5fb3c8c8-3160-4964-ac56-02d6e2cb998c\",\"trace_id\":\"3a6d5e64-ecf2-4700-98cd-3edd56a1dc34\"}\n[2026-04-27 10:28:15] local.INFO: [EmailSchedule] FINISHED batch process {\"host\":\"docker_lamp_1\",\"processed\":1} {\"correlation_id\":\"5fb3c8c8-3160-4964-ac56-02d6e2cb998c\",\"trace_id\":\"3a6d5e64-ecf2-4700-98cd-3edd56a1dc34\"}\n[2026-04-27 10:28:15] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage for command {\"command\":\"mailbox:batch:process\",\"memoryBeforeCommandInMb\":60.0,\"memoryAfterCommandInMB\":62.0,\"memoryPeakBeforeCommandInMb\":99.727,\"memoryPeakAfterCommandInMB\":99.727} {\"correlation_id\":\"5fb3c8c8-3160-4964-ac56-02d6e2cb998c\",\"trace_id\":\"3a6d5e64-ecf2-4700-98cd-3edd56a1dc34\"}\n[2026-04-27 10:28:16] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage before starting command {\"command\":\"conference:monitor:count\",\"memoryBeforeCommandInMb\":60.0,\"memoryPeakBeforeCommandInMb\":99.727} {\"correlation_id\":\"ef5b3e1a-a06a-4527-9120-b2adfe4e1846\",\"trace_id\":\"d932d64a-3f63-4aca-8abf-bb13e0892cba\"}\n[2026-04-27 10:28:16] local.INFO: Running conference:monitor:count command for activities in (2026-04-27 10:26:00, 2026-04-27 10:28:00] {\"correlation_id\":\"ef5b3e1a-a06a-4527-9120-b2adfe4e1846\",\"trace_id\":\"d932d64a-3f63-4aca-8abf-bb13e0892cba\"}\n[2026-04-27 10:28:16] local.INFO: [conference:monitor:count] No activities found in (2026-04-27 10:26:00, 2026-04-27 10:28:00] {\"correlation_id\":\"ef5b3e1a-a06a-4527-9120-b2adfe4e1846\",\"trace_id\":\"d932d64a-3f63-4aca-8abf-bb13e0892cba\"}\n[2026-04-27 10:28:16] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage for command {\"command\":\"conference:monitor:count\",\"memoryBeforeCommandInMb\":60.0,\"memoryAfterCommandInMB\":62.0,\"memoryPeakBeforeCommandInMb\":99.727,\"memoryPeakAfterCommandInMB\":99.727} {\"correlation_id\":\"ef5b3e1a-a06a-4527-9120-b2adfe4e1846\",\"trace_id\":\"d932d64a-3f63-4aca-8abf-bb13e0892cba\"}\n[2026-04-27 10:28:17] local.ERROR: Target class [Jiminny\\Repositories\\AjReportsRepository] does not exist. {\"exception\":\"[object] (Illuminate\\\\Contracts\\\\Container\\\\BindingResolutionException(code: 0): Target class [Jiminny\\\\Repositories\\\\AjReportsRepository] does not exist. at /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php:1124)\n[stacktrace]\n#0 /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php(933): Illuminate\\\\Container\\\\Container->build('Jiminny\\\\\\\\Reposit...')\n#1 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Application.php(1078): Illuminate\\\\Container\\\\Container->resolve('Jiminny\\\\\\\\Reposit...', Array, true)\n#2 /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php(864): Illuminate\\\\Foundation\\\\Application->resolve('Jiminny\\\\\\\\Reposit...', Array)\n#3 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Application.php(1058): Illuminate\\\\Container\\\\Container->make('Jiminny\\\\\\\\Reposit...', Array)\n#4 /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php(1336): Illuminate\\\\Foundation\\\\Application->make('Jiminny\\\\\\\\Reposit...')\n#5 /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php(1237): Illuminate\\\\Container\\\\Container->resolveClass(Object(ReflectionParameter))\n#6 /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php(1162): Illuminate\\\\Container\\\\Container->resolveDependencies(Array)\n#7 /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php(933): Illuminate\\\\Container\\\\Container->build('Jiminny\\\\\\\\Http\\\\\\\\Co...')\n#8 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Application.php(1078): Illuminate\\\\Container\\\\Container->resolve('Jiminny\\\\\\\\Http\\\\\\\\Co...', Array, true)\n#9 /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php(864): Illuminate\\\\Foundation\\\\Application->resolve('Jiminny\\\\\\\\Http\\\\\\\\Co...', Array)\n#10 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Application.php(1058): Illuminate\\\\Container\\\\Container->make('Jiminny\\\\\\\\Http\\\\\\\\Co...', Array)\n#11 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Route.php(286): Illuminate\\\\Foundation\\\\Application->make('Jiminny\\\\\\\\Http\\\\\\\\Co...')\n#12 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Route.php(1133): Illuminate\\\\Routing\\\\Route->getController()\n#13 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Route.php(1062): Illuminate\\\\Routing\\\\Route->controllerMiddleware()\n#14 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Router.php(834): Illuminate\\\\Routing\\\\Route->gatherMiddleware()\n#15 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Router.php(816): Illuminate\\\\Routing\\\\Router->gatherRouteMiddleware(Object(Illuminate\\\\Routing\\\\Route))\n#16 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Router.php(800): Illuminate\\\\Routing\\\\Router->runRouteWithinStack(Object(Illuminate\\\\Routing\\\\Route), Object(Illuminate\\\\Http\\\\Request))\n#17 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Router.php(764): Illuminate\\\\Routing\\\\Router->runRoute(Object(Illuminate\\\\Http\\\\Request), Object(Illuminate\\\\Routing\\\\Route))\n#18 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Router.php(753): Illuminate\\\\Routing\\\\Router->dispatchToRoute(Object(Illuminate\\\\Http\\\\Request))\n#19 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Http/Kernel.php(200): Illuminate\\\\Routing\\\\Router->dispatch(Object(Illuminate\\\\Http\\\\Request))\n#20 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(180): Illuminate\\\\Foundation\\\\Http\\\\Kernel->Illuminate\\\\Foundation\\\\Http\\\\{closure}(Object(Illuminate\\\\Http\\\\Request))\n#21 /home/jiminny/vendor/barryvdh/laravel-debugbar/src/Middleware/InjectDebugbar.php(59): Illuminate\\\\Pipeline\\\\Pipeline->Illuminate\\\\Pipeline\\\\{closure}(Object(Illuminate\\\\Http\\\\Request))\n#22 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(219): Barryvdh\\\\Debugbar\\\\Middleware\\\\InjectDebugbar->handle(Object(Illuminate\\\\Http\\\\Request), Object(Closure))\n#23 /home/jiminny/vendor/laravel/framework/src/Illuminate/Http/Middleware/HandleCors.php(74): Illuminate\\\\Pipeline\\\\Pipeline->Illuminate\\\\Pipeline\\\\{closure}(Object(Illuminate\\\\Http\\\\Request))\n#24 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(219): Illuminate\\\\Http\\\\Middleware\\\\HandleCors->handle(Object(Illuminate\\\\Http\\\\Request), Object(Closure))\n#25 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Http/Middleware/TransformsRequest.php(21): Illuminate\\\\Pipeline\\\\Pipeline->Illuminate\\\\Pipeline\\\\{closure}(Object(Illuminate\\\\Http\\\\Request))\n#26 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Http/Middleware/TrimStrings.php(51): Illuminate\\\\Foundation\\\\Http\\\\Middleware\\\\TransformsRequest->handle(Object(Illuminate\\\\Http\\\\Request), Object(Closure))\n#27 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(219): Illuminate\\\\Foundation\\\\Http\\\\Middleware\\\\TrimStrings->handle(Object(Illuminate\\\\Http\\\\Request), Object(Closure))\n#28 /home/jiminny/vendor/bepsvpt/secure-headers/src/SecureHeadersMiddleware.php(18): Illuminate\\\\Pipeline\\\\Pipeline->Illuminate\\\\Pipeline\\\\{closure}(Object(Illuminate\\\\Http\\\\Request))\n#29 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(219): Bepsvpt\\\\SecureHeaders\\\\SecureHeadersMiddleware->handle(Object(Illuminate\\\\Http\\\\Request), Object(Closure))\n#30 /home/jiminny/app/Http/Middleware/SentryContext.php(60): Illuminate\\\\Pipeline\\\\Pipeline->Illuminate\\\\Pipeline\\\\{closure}(Object(Illuminate\\\\Http\\\\Request))\n#31 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(219): Jiminny\\\\Http\\\\Middleware\\\\SentryContext->handle(Object(Illuminate\\\\Http\\\\Request), Object(Closure))\n#32 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Http/Middleware/PreventRequestsDuringMaintenance.php(109): Illuminate\\\\Pipeline\\\\Pipeline->Illuminate\\\\Pipeline\\\\{closure}(Object(Illuminate\\\\Http\\\\Request))\n#33 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(219): Illuminate\\\\Foundation\\\\Http\\\\Middleware\\\\PreventRequestsDuringMaintenance->handle(Object(Illuminate\\\\Http\\\\Request), Object(Closure))\n#34 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Http/Middleware/InvokeDeferredCallbacks.php(22): Illuminate\\\\Pipeline\\\\Pipeline->Illuminate\\\\Pipeline\\\\{closure}(Object(Illuminate\\\\Http\\\\Request))\n#35 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(219): Illuminate\\\\Foundation\\\\Http\\\\Middleware\\\\InvokeDeferredCallbacks->handle(Object(Illuminate\\\\Http\\\\Request), Object(Closure))\n#36 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(137): Illuminate\\\\Pipeline\\\\Pipeline->Illuminate\\\\Pipeline\\\\{closure}(Object(Illuminate\\\\Http\\\\Request))\n#37 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Http/Kernel.php(175): Illuminate\\\\Pipeline\\\\Pipeline->then(Object(Closure))\n#38 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Http/Kernel.php(144): Illuminate\\\\Foundation\\\\Http\\\\Kernel->sendRequestThroughRouter(Object(Illuminate\\\\Http\\\\Request))\n#39 /home/jiminny/public/index.php(51): Illuminate\\\\Foundation\\\\Http\\\\Kernel->handle(Object(Illuminate\\\\Http\\\\Request))\n#40 {main}\n\n[previous exception] [object] (ReflectionException(code: -1): Class \\\"Jiminny\\\\Repositories\\\\AjReportsRepository\\\" does not exist at /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php:1122)\n[stacktrace]\n#0 /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php(1122): ReflectionClass->__construct('Jiminny\\\\\\\\Reposit...')\n#1 /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php(933): Illuminate\\\\Container\\\\Container->build('Jiminny\\\\\\\\Reposit...')\n#2 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Application.php(1078): Illuminate\\\\Container\\\\Container->resolve('Jiminny\\\\\\\\Reposit...', Array, true)\n#3 /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php(864): Illuminate\\\\Foundation\\\\Application->resolve('Jiminny\\\\\\\\Reposit...', Array)\n#4 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Application.php(1058): Illuminate\\\\Container\\\\Container->make('Jiminny\\\\\\\\Reposit...', Array)\n#5 /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php(1336): Illuminate\\\\Foundation\\\\Application->make('Jiminny\\\\\\\\Reposit...')\n#6 /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php(1237): Illuminate\\\\Container\\\\Container->resolveClass(Object(ReflectionParameter))\n#7 /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php(1162): Illuminate\\\\Container\\\\Container->resolveDependencies(Array)\n#8 /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php(933): Illuminate\\\\Container\\\\Container->build('Jiminny\\\\\\\\Http\\\\\\\\Co...')\n#9 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Application.php(1078): Illuminate\\\\Container\\\\Container->resolve('Jiminny\\\\\\\\Http\\\\\\\\Co...', Array, true)\n#10 /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php(864): Illuminate\\\\Foundation\\\\Application->resolve('Jiminny\\\\\\\\Http\\\\\\\\Co...', Array)\n#11 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Application.php(1058): Illuminate\\\\Container\\\\Container->make('Jiminny\\\\\\\\Http\\\\\\\\Co...', Array)\n#12 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Route.php(286): Illuminate\\\\Foundation\\\\Application->make('Jiminny\\\\\\\\Http\\\\\\\\Co...')\n#13 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Route.php(1133): Illuminate\\\\Routing\\\\Route->getController()\n#14 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Route.php(1062): Illuminate\\\\Routing\\\\Route->controllerMiddleware()\n#15 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Router.php(834): Illuminate\\\\Routing\\\\Route->gatherMiddleware()\n#16 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Router.php(816): Illuminate\\\\Routing\\\\Router->gatherRouteMiddleware(Object(Illuminate\\\\Routing\\\\Route))\n#17 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Router.php(800): Illuminate\\\\Routing\\\\Router->runRouteWithinStack(Object(Illuminate\\\\Routing\\\\Route), Object(Illuminate\\\\Http\\\\Request))\n#18 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Router.php(764): Illuminate\\\\Routing\\\\Router->runRoute(Object(Illuminate\\\\Http\\\\Request), Object(Illuminate\\\\Routing\\\\Route))\n#19 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Router.php(753): Illuminate\\\\Routing\\\\Router->dispatchToRoute(Object(Illuminate\\\\Http\\\\Request))\n#20 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Http/Kernel.php(200): Illuminate\\\\Routing\\\\Router->dispatch(Object(Illuminate\\\\Http\\\\Request))\n#21 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(180): Illuminate\\\\Foundation\\\\Http\\\\Kernel->Illuminate\\\\Foundation\\\\Http\\\\{closure}(Object(Illuminate\\\\Http\\\\Request))\n#22 /home/jiminny/vendor/barryvdh/laravel-debugbar/src/Middleware/InjectDebugbar.php(59): Illuminate\\\\Pipeline\\\\Pipeline->Illuminate\\\\Pipeline\\\\{closure}(Object(Illuminate\\\\Http\\\\Request))\n#23 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(219): Barryvdh\\\\Debugbar\\\\Middleware\\\\InjectDebugbar->handle(Object(Illuminate\\\\Http\\\\Request), Object(Closure))\n#24 /home/jiminny/vendor/laravel/framework/src/Illuminate/Http/Middleware/HandleCors.php(74): Illuminate\\\\Pipeline\\\\Pipeline->Illuminate\\\\Pipeline\\\\{closure}(Object(Illuminate\\\\Http\\\\Request))\n#25 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(219): Illuminate\\\\Http\\\\Middleware\\\\HandleCors->handle(Object(Illuminate\\\\Http\\\\Request), Object(Closure))\n#26 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Http/Middleware/TransformsRequest.php(21): Illuminate\\\\Pipeline\\\\Pipeline->Illuminate\\\\Pipeline\\\\{closure}(Object(Illuminate\\\\Http\\\\Request))\n#27 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Http/Middleware/TrimStrings.php(51): Illuminate\\\\Foundation\\\\Http\\\\Middleware\\\\TransformsRequest->handle(Object(Illuminate\\\\Http\\\\Request), Object(Closure))\n#28 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(219): Illuminate\\\\Foundation\\\\Http\\\\Middleware\\\\TrimStrings->handle(Object(Illuminate\\\\Http\\\\Request), Object(Closure))\n#29 /home/jiminny/vendor/bepsvpt/secure-headers/src/SecureHeadersMiddleware.php(18): Illuminate\\\\Pipeline\\\\Pipeline->Illuminate\\\\Pipeline\\\\{closure}(Object(Illuminate\\\\Http\\\\Request))\n#30 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(219): Bepsvpt\\\\SecureHeaders\\\\SecureHeadersMiddleware->handle(Object(Illuminate\\\\Http\\\\Request), Object(Closure))\n#31 /home/jiminny/app/Http/Middleware/SentryContext.php(60): Illuminate\\\\Pipeline\\\\Pipeline->Illuminate\\\\Pipeline\\\\{closure}(Object(Illuminate\\\\Http\\\\Request))\n#32 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(219): Jiminny\\\\Http\\\\Middleware\\\\SentryContext->handle(Object(Illuminate\\\\Http\\\\Request), Object(Closure))\n#33 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Http/Middleware/PreventRequestsDuringMaintenance.php(109): Illuminate\\\\Pipeline\\\\Pipeline->Illuminate\\\\Pipeline\\\\{closure}(Object(Illuminate\\\\Http\\\\Request))\n#34 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(219): Illuminate\\\\Foundation\\\\Http\\\\Middleware\\\\PreventRequestsDuringMaintenance->handle(Object(Illuminate\\\\Http\\\\Request), Object(Closure))\n#35 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Http/Middleware/InvokeDeferredCallbacks.php(22): Illuminate\\\\Pipeline\\\\Pipeline->Illuminate\\\\Pipeline\\\\{closure}(Object(Illuminate\\\\Http\\\\Request))\n#36 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(219): Illuminate\\\\Foundation\\\\Http\\\\Middleware\\\\InvokeDeferredCallbacks->handle(Object(Illuminate\\\\Http\\\\Request), Object(Closure))\n#37 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(137): Illuminate\\\\Pipeline\\\\Pipeline->Illuminate\\\\Pipeline\\\\{closure}(Object(Illuminate\\\\Http\\\\Request))\n#38 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Http/Kernel.php(175): Illuminate\\\\Pipeline\\\\Pipeline->then(Object(Closure))\n#39 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Http/Kernel.php(144): Illuminate\\\\Foundation\\\\Http\\\\Kernel->sendRequestThroughRouter(Object(Illuminate\\\\Http\\\\Request))\n#40 /home/jiminny/public/index.php(51): Illuminate\\\\Foundation\\\\Http\\\\Kernel->handle(Object(Illuminate\\\\Http\\\\Request))\n#41 {main}\n\"} {\"correlation_id\":\"46b082fc-5a28-4cc4-8bd1-c670f8662e7f\",\"trace_id\":\"8ae7dc26-61c7-42e4-8617-0b85f53e54c1\"}\n[2026-04-27 10:28:19] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage before starting command {\"command\":\"calendar:sync\",\"memoryBeforeCommandInMb\":60.0,\"memoryPeakBeforeCommandInMb\":99.727} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:19] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage before starting command {\"command\":\"mailbox:batch:retry-failed\",\"memoryBeforeCommandInMb\":60.0,\"memoryPeakBeforeCommandInMb\":99.727} {\"correlation_id\":\"e155562a-84a1-45f5-8897-1779f96886f6\",\"trace_id\":\"12141267-1032-479b-aab5-e17e133007d9\"}\n[2026-04-27 10:28:19] local.NOTICE: Calendar sync start {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:19] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage for command {\"command\":\"mailbox:batch:retry-failed\",\"memoryBeforeCommandInMb\":60.0,\"memoryAfterCommandInMB\":62.0,\"memoryPeakBeforeCommandInMb\":99.727,\"memoryPeakAfterCommandInMB\":99.727} {\"correlation_id\":\"e155562a-84a1-45f5-8897-1779f96886f6\",\"trace_id\":\"12141267-1032-479b-aab5-e17e133007d9\"}\n[2026-04-27 10:28:19] local.INFO: [SocialAccountService] Fetching token {\"socialAccountId\":1393,\"provider\":\"google\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:19] local.INFO: [SocialAccountService] Token needs refreshing {\"socialAccountId\":1393,\"provider\":\"google\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:19] local.INFO: [EncryptedTokenManager] Generating access token. {\"mode\":\"legacy\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:19] local.INFO: [SocialAccountService] Refreshing token from provider {\"socialAccountId\":1393,\"provider\":\"google\",\"refreshToken\":\"5aa7e2d96b53201cd16fca5d2e4ef3ad03320971fc064781d18aee3ae7b99fbf\",\"state\":\"full-refresh\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:20] local.ERROR: [SocialAccountService] Failed to refresh token {\"socialAccountId\":1393,\"provider\":\"google\",\"responseBody\":{\"error\":\"invalid_grant\",\"error_description\":\"Account has been deleted\"}} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:20] local.INFO: [SocialAccountObserver] Saving model {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:20] local.ERROR: [SocialAccountService] Failed to refresh token {\"socialAccountId\":1393,\"provider\":\"google\",\"reason\":\"Flow refresh required.\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:20] local.INFO: [SocialAccountService] Fetching token {\"socialAccountId\":1387,\"provider\":\"google\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:20] local.INFO: [SocialAccountService] Token needs refreshing {\"socialAccountId\":1387,\"provider\":\"google\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:20] local.INFO: [EncryptedTokenManager] Generating access token. {\"mode\":\"legacy\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:20] local.INFO: [SocialAccountService] Refreshing token from provider {\"socialAccountId\":1387,\"provider\":\"google\",\"refreshToken\":\"8157ac6de94842937194009e9c50e459253600f799dacf6a40755ffdbeb5bba6\",\"state\":\"full-refresh\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:21] local.ERROR: [SocialAccountService] Failed to refresh token {\"socialAccountId\":1387,\"provider\":\"google\",\"responseBody\":{\"error\":\"invalid_grant\",\"error_description\":\"Account has been deleted\"}} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:21] local.INFO: [SocialAccountObserver] Saving model {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:21] local.ERROR: [SocialAccountService] Failed to refresh token {\"socialAccountId\":1387,\"provider\":\"google\",\"reason\":\"Flow refresh required.\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:21] local.INFO: [SocialAccountService] Fetching token {\"socialAccountId\":1348,\"provider\":\"google\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:21] local.INFO: [SocialAccountService] Token needs refreshing {\"socialAccountId\":1348,\"provider\":\"google\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:21] local.INFO: [EncryptedTokenManager] Generating access token. {\"mode\":\"legacy\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:21] local.INFO: [SocialAccountService] Refreshing token from provider {\"socialAccountId\":1348,\"provider\":\"google\",\"refreshToken\":\"9e7d13d3032d0cb1b79d8e95aef01383e8e91eb52ff8ee960c8a0b6b95cd8c73\",\"state\":\"full-refresh\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:21] local.ERROR: [SocialAccountService] Failed to refresh token {\"socialAccountId\":1348,\"provider\":\"google\",\"responseBody\":{\"error\":\"invalid_grant\",\"error_description\":\"Bad Request\"}} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:21] local.INFO: [SocialAccountObserver] Saving model {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:21] local.ERROR: [SocialAccountService] Failed to refresh token {\"socialAccountId\":1348,\"provider\":\"google\",\"reason\":\"Flow refresh required.\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:21] local.INFO: [SocialAccountService] Fetching token {\"socialAccountId\":1361,\"provider\":\"google\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:21] local.INFO: [SocialAccountService] Token needs refreshing {\"socialAccountId\":1361,\"provider\":\"google\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:21] local.INFO: [EncryptedTokenManager] Generating access token. {\"mode\":\"legacy\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:21] local.INFO: [SocialAccountService] Refreshing token from provider {\"socialAccountId\":1361,\"provider\":\"google\",\"refreshToken\":\"6c843da199c2b9907445329304fcc4ec5057a4ee748d8299641764395c08e1fd\",\"state\":\"full-refresh\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:22] local.ERROR: [SocialAccountService] Failed to refresh token {\"socialAccountId\":1361,\"provider\":\"google\",\"responseBody\":{\"error\":\"invalid_grant\",\"error_description\":\"Account has been deleted\"}} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:22] local.INFO: [SocialAccountObserver] Saving model {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:22] local.ERROR: [SocialAccountService] Failed to refresh token {\"socialAccountId\":1361,\"provider\":\"google\",\"reason\":\"Flow refresh required.\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:22] local.INFO: [SocialAccountService] Fetching token {\"socialAccountId\":1310,\"provider\":\"google\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:22] local.INFO: [SocialAccountService] Token needs refreshing {\"socialAccountId\":1310,\"provider\":\"google\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:22] local.INFO: [EncryptedTokenManager] Generating access token. {\"mode\":\"legacy\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:22] local.INFO: [SocialAccountService] Refreshing token from provider {\"socialAccountId\":1310,\"provider\":\"google\",\"refreshToken\":\"e34818922c2830a660813a63f6169a4a9a992ae2cccd7dc8dd7796cfdb470ef1\",\"state\":\"full-refresh\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:23] local.ERROR: [SocialAccountService] Failed to refresh token {\"socialAccountId\":1310,\"provider\":\"google\",\"responseBody\":{\"error\":\"invalid_grant\",\"error_description\":\"Bad Request\"}} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:23] local.INFO: [SocialAccountObserver] Saving model {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:23] local.ERROR: [SocialAccountService] Failed to refresh token {\"socialAccountId\":1310,\"provider\":\"google\",\"reason\":\"Flow refresh required.\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:23] local.INFO: [SocialAccountService] Fetching token {\"socialAccountId\":1333,\"provider\":\"google\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:23] local.INFO: [SocialAccountService] Token needs refreshing {\"socialAccountId\":1333,\"provider\":\"google\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:23] local.INFO: [EncryptedTokenManager] Generating access token. {\"mode\":\"legacy\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:23] local.INFO: [SocialAccountService] Refreshing token from provider {\"socialAccountId\":1333,\"provider\":\"google\",\"refreshToken\":\"6c902986546d8e8da1dc539b046cdc1d458f519acc972e5b5f1d6a1a295165e0\",\"state\":\"full-refresh\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:23] local.ERROR: [SocialAccountService] Failed to refresh token {\"socialAccountId\":1333,\"provider\":\"google\",\"responseBody\":{\"error\":\"unauthorized_client\",\"error_description\":\"Unauthorized\"}} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:23] local.INFO: [SocialAccountObserver] Saving model {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:23] local.ERROR: [SocialAccountService] Failed to refresh token {\"socialAccountId\":1333,\"provider\":\"google\",\"reason\":\"Flow refresh required.\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:24] local.INFO: [SocialAccountService] Fetching token {\"socialAccountId\":1368,\"provider\":\"google\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:24] local.INFO: [SocialAccountService] Token needs refreshing {\"socialAccountId\":1368,\"provider\":\"google\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:24] local.INFO: [EncryptedTokenManager] Generating access token. {\"mode\":\"legacy\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:24] local.INFO: [SocialAccountService] Refreshing token from provider {\"socialAccountId\":1368,\"provider\":\"google\",\"refreshToken\":\"d2f128898ff8543bd16b69cfae37896ab85119b0f5ed2b431d739593bb600333\",\"state\":\"full-refresh\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:24] local.ERROR: [SocialAccountService] Failed to refresh token {\"socialAccountId\":1368,\"provider\":\"google\",\"responseBody\":{\"error\":\"invalid_grant\",\"error_description\":\"Bad Request\"}} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:24] local.INFO: [SocialAccountObserver] Saving model {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:24] local.ERROR: [SocialAccountService] Failed to refresh token {\"socialAccountId\":1368,\"provider\":\"google\",\"reason\":\"Flow refresh required.\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:24] local.INFO: [SocialAccountService] Fetching token {\"socialAccountId\":1365,\"provider\":\"google\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:24] local.INFO: [SocialAccountService] Token needs refreshing {\"socialAccountId\":1365,\"provider\":\"google\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:24] local.INFO: [EncryptedTokenManager] Generating access token. {\"mode\":\"legacy\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:24] local.INFO: [SocialAccountService] Refreshing token from provider {\"socialAccountId\":1365,\"provider\":\"google\",\"refreshToken\":\"7676e4a9afcd082b413248ab5ec6e487021fec6a9bdf315860a59cefad9caad8\",\"state\":\"full-refresh\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:25] local.ERROR: [SocialAccountService] Failed to refresh token {\"socialAccountId\":1365,\"provider\":\"google\",\"responseBody\":{\"error\":\"unauthorized_client\",\"error_description\":\"Unauthorized\"}} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:25] local.INFO: [SocialAccountObserver] Saving model {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:25] local.ERROR: [SocialAccountService] Failed to refresh token {\"socialAccountId\":1365,\"provider\":\"google\",\"reason\":\"Flow refresh required.\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:25] local.INFO: [SocialAccountService] Fetching token {\"socialAccountId\":1364,\"provider\":\"google\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:25] local.INFO: [SocialAccountService] Token needs refreshing {\"socialAccountId\":1364,\"provider\":\"google\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:25] local.INFO: [EncryptedTokenManager] Generating access token. {\"mode\":\"legacy\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:25] local.INFO: [SocialAccountService] Refreshing token from provider {\"socialAccountId\":1364,\"provider\":\"google\",\"refreshToken\":\"dd5882ebce76e645292ce33ae74238abbb77c0a4ecc6a2bfe723cad82e72ba8e\",\"state\":\"full-refresh\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:25] local.ERROR: [SocialAccountService] Failed to refresh token {\"socialAccountId\":1364,\"provider\":\"google\",\"responseBody\":{\"error\":\"unauthorized_client\",\"error_description\":\"Unauthorized\"}} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:25] local.INFO: [SocialAccountObserver] Saving model {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:25] local.ERROR: [SocialAccountService] Failed to refresh token {\"socialAccountId\":1364,\"provider\":\"google\",\"reason\":\"Flow refresh required.\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:25] local.INFO: [SocialAccountService] Fetching token {\"socialAccountId\":1370,\"provider\":\"office\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:25] local.INFO: [SocialAccountService] Token needs refreshing {\"socialAccountId\":1370,\"provider\":\"office\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:25] local.INFO: [EncryptedTokenManager] Generating access token. {\"mode\":\"legacy\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:25] local.INFO: [SocialAccountService] Refreshing token from provider {\"socialAccountId\":1370,\"provider\":\"office\",\"refreshToken\":\"b7ee8035306d0043cea6e00e7c4fe14f745e44074a1194db62a31cdf8b70af3e\",\"state\":\"full-refresh\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:26] local.ERROR: [SocialAccountService] Failed to refresh token {\"socialAccountId\":1370,\"provider\":\"office\",\"responseBody\":\"{\\\"error\\\":\\\"invalid_client\\\",\\\"error_description\\\":\\\"AADSTS7000215: Invalid client secret provided. Ensure the secret being sent in the request is the client secret value, not the client secret ID, for a secret added to app 'bbcbb2ef-6200-4fae-82bd-d81f5dd738da'. Trace ID: 2be54cfa-1691-4388-a284-754fdf414400 Correlation ID: 9a934049-899b-45c5-99a5-15c9c470d8a2 Timestamp: 2026-04-27 10:28:26Z\\\",\\\"error_codes\\\":[7000215],\\\"timestamp\\\":\\\"2026-04-27 10:28:26Z\\\",\\\"trace_id\\\":\\\"2be54cfa-1691-4388-a284-754fdf414400\\\",\\\"correlation_id\\\":\\\"9a934049-899b-45c5-99a5-15c9c470d8a2\\\",\\\"error_uri\\\":\\\"https://login.microsoftonline.com/error?code=7000215\\\"}\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:26] local.INFO: [SocialAccountObserver] Saving model {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:26] local.ERROR: [SocialAccountService] Failed to refresh token {\"socialAccountId\":1370,\"provider\":\"office\",\"reason\":\"Flow refresh required.\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:26] local.INFO: [SocialAccountService] Fetching token {\"socialAccountId\":1202,\"provider\":\"office\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:26] local.INFO: [SocialAccountService] Token needs refreshing {\"socialAccountId\":1202,\"provider\":\"office\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:26] local.INFO: [EncryptedTokenManager] Generating access token. {\"mode\":\"legacy\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:26] local.INFO: [SocialAccountService] Refreshing token from provider {\"socialAccountId\":1202,\"provider\":\"office\",\"refreshToken\":\"b458799ccc29b21a6e2eb5260fdb63e49ccba21bf942a3973fb63799bd7f0afe\",\"state\":\"full-refresh\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:26] local.ERROR: [SocialAccountService] Failed to refresh token {\"socialAccountId\":1202,\"provider\":\"office\",\"responseBody\":\"{\\\"error\\\":\\\"invalid_client\\\",\\\"error_description\\\":\\\"AADSTS7000215: Invalid client secret provided. Ensure the secret being sent in the request is the client secret value, not the client secret ID, for a secret added to app 'bbcbb2ef-6200-4fae-82bd-d81f5dd738da'. Trace ID: 27729e9a-0943-41bd-9752-4ec9a8444500 Correlation ID: 824a9761-9387-4c53-ae31-5c242b1d4298 Timestamp: 2026-04-27 10:28:26Z\\\",\\\"error_codes\\\":[7000215],\\\"timestamp\\\":\\\"2026-04-27 10:28:26Z\\\",\\\"trace_id\\\":\\\"27729e9a-0943-41bd-9752-4ec9a8444500\\\",\\\"correlation_id\\\":\\\"824a9761-9387-4c53-ae31-5c242b1d4298\\\",\\\"error_uri\\\":\\\"https://login.microsoftonline.com/error?code=7000215\\\"}\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:26] local.INFO: [SocialAccountObserver] Saving model {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:26] local.ERROR: [SocialAccountService] Failed to refresh token {\"socialAccountId\":1202,\"provider\":\"office\",\"reason\":\"Flow refresh required.\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:26] local.INFO: [SocialAccountService] Fetching token {\"socialAccountId\":1502,\"provider\":\"google\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:26] local.INFO: [SocialAccountService] Token needs refreshing {\"socialAccountId\":1502,\"provider\":\"google\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:26] local.INFO: [EncryptedTokenManager] Generating access token. {\"mode\":\"legacy\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:26] local.INFO: [SocialAccountService] Refreshing token from provider {\"socialAccountId\":1502,\"provider\":\"google\",\"refreshToken\":\"d417c92ebaa137295a04675f715d0511ae8acac9d779b102eac50d6300116d3e\",\"state\":\"connected\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:27] local.INFO: [SocialAccountObserver] Saving model {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:27] local.INFO: [SocialAccountObserver] Access token was modified, encrypting {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:27] local.INFO: [SocialAccountService] Token refreshed {\"socialAccountId\":1502,\"provider\":\"google\",\"state\":\"connected\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:27] local.INFO: Calendar sync job dispatched {\"calendar_id\":501} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:27] local.INFO: [SocialAccountService] Fetching token {\"socialAccountId\":1300,\"provider\":\"google\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:27] local.INFO: [SocialAccountService] Token needs refreshing {\"socialAccountId\":1300,\"provider\":\"google\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:27] local.INFO: [EncryptedTokenManager] Generating access token. {\"mode\":\"legacy\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:27] local.INFO: [SocialAccountService] Refreshing token from provider {\"socialAccountId\":1300,\"provider\":\"google\",\"refreshToken\":\"4b811db0725fd9602a95943519a7da935e2a5065da7d9ebfcb170752e3e1ddb8\",\"state\":\"full-refresh\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:27] local.ERROR: [SocialAccountService] Failed to refresh token {\"socialAccountId\":1300,\"provider\":\"google\",\"responseBody\":{\"error\":\"invalid_grant\",\"error_description\":\"Account has been deleted\"}} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:27] local.INFO: [SocialAccountObserver] Saving model {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:27] local.ERROR: [SocialAccountService] Failed to refresh token {\"socialAccountId\":1300,\"provider\":\"google\",\"reason\":\"Flow refresh required.\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:27] local.INFO: [SocialAccountService] Fetching token {\"socialAccountId\":1409,\"provider\":\"google\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:27] local.INFO: [SocialAccountService] Token needs refreshing {\"socialAccountId\":1409,\"provider\":\"google\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:27] local.INFO: [EncryptedTokenManager] Generating access token. {\"mode\":\"legacy\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:27] local.INFO: [SocialAccountService] Refreshing token from provider {\"socialAccountId\":1409,\"provider\":\"google\",\"refreshToken\":\"e2a3f2d06894894eed1ee87d9db1ace77d4d42ee6e1288a8940ad2c10333b0c4\",\"state\":\"full-refresh\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:28] local.ERROR: [SocialAccountService] Failed to refresh token {\"socialAccountId\":1409,\"provider\":\"google\",\"responseBody\":{\"error\":\"invalid_grant\",\"error_description\":\"Bad Request\"}} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:28] local.INFO: [SocialAccountObserver] Saving model {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:28] local.ERROR: [SocialAccountService] Failed to refresh token {\"socialAccountId\":1409,\"provider\":\"google\",\"reason\":\"Flow refresh required.\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:28] local.INFO: [SocialAccountService] Fetching token {\"socialAccountId\":1352,\"provider\":\"google\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:28] local.INFO: [SocialAccountService] Token needs refreshing {\"socialAccountId\":1352,\"provider\":\"google\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:28] local.INFO: [EncryptedTokenManager] Generating access token. {\"mode\":\"legacy\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:28] local.INFO: [SocialAccountService] Refreshing token from provider {\"socialAccountId\":1352,\"provider\":\"google\",\"refreshToken\":\"dd4b16b00fdc1216da6b717c02338c073636e29162826b2de6db3f064fc029eb\",\"state\":\"full-refresh\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:28] local.ERROR: [SocialAccountService] Failed to refresh token {\"socialAccountId\":1352,\"provider\":\"google\",\"responseBody\":{\"error\":\"unauthorized_client\",\"error_description\":\"Unauthorized\"}} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:28] local.INFO: [SocialAccountObserver] Saving model {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:28] local.ERROR: [SocialAccountService] Failed to refresh token {\"socialAccountId\":1352,\"provider\":\"google\",\"reason\":\"Flow refresh required.\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:28] local.INFO: [SocialAccountService] Fetching token {\"socialAccountId\":1296,\"provider\":\"office\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:28] local.INFO: [SocialAccountService] Token needs refreshing {\"socialAccountId\":1296,\"provider\":\"office\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:28] local.INFO: [EncryptedTokenManager] Generating access token. {\"mode\":\"legacy\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:28] local.INFO: [SocialAccountService] Refreshing token from provider {\"socialAccountId\":1296,\"provider\":\"office\",\"refreshToken\":\"011ae723c9d800c674e0b4be76f49fc046dac7d501b66c59ef0d9549cfa56ae5\",\"state\":\"full-refresh\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:28] local.ERROR: [SocialAccountService] Failed to refresh token {\"socialAccountId\":1296,\"provider\":\"office\",\"responseBody\":\"{\\\"error\\\":\\\"invalid_client\\\",\\\"error_description\\\":\\\"AADSTS7000215: Invalid client secret provided. Ensure the secret being sent in the request is the client secret value, not the client secret ID, for a secret added to app 'bbcbb2ef-6200-4fae-82bd-d81f5dd738da'. Trace ID: 73b326f0-9fcd-4ff5-ad61-edd6a0504500 Correlation ID: 6bd8fef3-b24c-46d8-8bba-a9b2ab41c108 Timestamp: 2026-04-27 10:28:28Z\\\",\\\"error_codes\\\":[7000215],\\\"timestamp\\\":\\\"2026-04-27 10:28:28Z\\\",\\\"trace_id\\\":\\\"73b326f0-9fcd-4ff5-ad61-edd6a0504500\\\",\\\"correlation_id\\\":\\\"6bd8fef3-b24c-46d8-8bba-a9b2ab41c108\\\",\\\"error_uri\\\":\\\"https://login.microsoftonline.com/error?code=7000215\\\"}\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:28] local.INFO: [SocialAccountObserver] Saving model {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:28] local.ERROR: [SocialAccountService] Failed to refresh token {\"socialAccountId\":1296,\"provider\":\"office\",\"reason\":\"Flow refresh required.\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:28] local.INFO: [SocialAccountService] Fetching token {\"socialAccountId\":391,\"provider\":\"office\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:28] local.INFO: [SocialAccountService] Token needs refreshing {\"socialAccountId\":391,\"provider\":\"office\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:28] local.INFO: [EncryptedTokenManager] Generating access token. {\"mode\":\"legacy\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:28] local.INFO: [SocialAccountService] Refreshing token from provider {\"socialAccountId\":391,\"provider\":\"office\",\"refreshToken\":\"00045eebae0f39b34887c6d53f92ae78064f7145e1f4b67754aebd03cfb2d881\",\"state\":\"full-refresh\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:28] local.INFO: [SocialAccountService] Fetching token {\"socialAccountId\":1502,\"provider\":\"google\"} {\"correlation_id\":\"8a9488be-81de-40f5-8554-2503eec42566\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:28] local.INFO: [SocialAccountService] Token retrieved {\"socialAccountId\":1502,\"provider\":\"google\"} {\"correlation_id\":\"8a9488be-81de-40f5-8554-2503eec42566\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:28] local.INFO: [EncryptedTokenManager] Generating access token. {\"mode\":\"legacy\"} {\"correlation_id\":\"8a9488be-81de-40f5-8554-2503eec42566\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:29] local.INFO: [Calendar] Processing sync {\"calendarId\":\"a33076c1-8d97-431a-99f0-85c9524e118b\",\"from\":null,\"to\":null,\"delta\":\"CIiFh8TP44kDEIiFh8TP44kDGAUgkZvkzgIokZvkzgI=\",\"last_sync\":\"2024-12-09 07:12:53\",\"dateMode\":\"daily\"} {\"correlation_id\":\"8a9488be-81de-40f5-8554-2503eec42566\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:29] local.INFO: [CrmOwnerResolver] Integration owner matched as CRM Owner {\"crm_provider\":\"integration-app\",\"crm_owner\":1695,\"team_id\":3143} {\"correlation_id\":\"8a9488be-81de-40f5-8554-2503eec42566\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:29] local.INFO: [SocialAccountService] Fetching token {\"socialAccountId\":1502,\"provider\":\"google\"} {\"correlation_id\":\"8a9488be-81de-40f5-8554-2503eec42566\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:29] local.INFO: [SocialAccountService] Token retrieved {\"socialAccountId\":1502,\"provider\":\"google\"} {\"correlation_id\":\"8a9488be-81de-40f5-8554-2503eec42566\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:29] local.INFO: [EncryptedTokenManager] Generating access token. {\"mode\":\"legacy\"} {\"correlation_id\":\"8a9488be-81de-40f5-8554-2503eec42566\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:29] local.ERROR: [SocialAccountService] Failed to refresh token {\"socialAccountId\":391,\"provider\":\"office\",\"responseBody\":\"{\\\"error\\\":\\\"invalid_client\\\",\\\"error_description\\\":\\\"AADSTS7000215: Invalid client secret provided. Ensure the secret being sent in the request is the client secret value, not the client secret ID, for a secret added to app 'bbcbb2ef-6200-4fae-82bd-d81f5dd738da'. Trace ID: c5f1db6f-22fb-41cc-bd89-c46497634200 Correlation ID: 7b4df0cc-bf50-4017-9b1a-e25ffb6fe9bf Timestamp: 2026-04-27 10:28:29Z\\\",\\\"error_codes\\\":[7000215],\\\"timestamp\\\":\\\"2026-04-27 10:28:29Z\\\",\\\"trace_id\\\":\\\"c5f1db6f-22fb-41cc-bd89-c46497634200\\\",\\\"correlation_id\\\":\\\"7b4df0cc-bf50-4017-9b1a-e25ffb6fe9bf\\\",\\\"error_uri\\\":\\\"https://login.microsoftonline.com/error?code=7000215\\\"}\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:29] local.INFO: [SocialAccountObserver] Saving model {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:29] local.ERROR: [SocialAccountService] Failed to refresh token {\"socialAccountId\":391,\"provider\":\"office\",\"reason\":\"Flow refresh required.\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:29] local.INFO: [SocialAccountService] Fetching token {\"socialAccountId\":1271,\"provider\":\"office\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:29] local.INFO: [SocialAccountService] Token needs refreshing {\"socialAccountId\":1271,\"provider\":\"office\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:29] local.INFO: [EncryptedTokenManager] Generating access token. {\"mode\":\"legacy\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:29] local.INFO: [SocialAccountService] Refreshing token from provider {\"socialAccountId\":1271,\"provider\":\"office\",\"refreshToken\":\"118cde2c06993147b07ccaec4cbcd5026a819dea6c71081166a492933e392afb\",\"state\":\"full-refresh\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:29] local.INFO: [Google Calendar] Failed to watch channel for calendar {\"calendarId\":\"a33076c1-8d97-431a-99f0-85c9524e118b\",\"code\":400,\"reason\":\"{\n \\\"error\\\": {\n \\\"errors\\\": [\n {\n \\\"domain\\\": \\\"global\\\",\n \\\"reason\\\": \\\"push.webhookUrlNotHttps\\\",\n \\\"message\\\": \\\"WebHook callback must be HTTPS: /webhook/calendar/google?resourceType=event\\\"\n }\n ],\n \\\"code\\\": 400,\n \\\"message\\\": \\\"WebHook callback must be HTTPS: /webhook/calendar/google?resourceType=event\\\"\n }\n}\"} {\"correlation_id\":\"8a9488be-81de-40f5-8554-2503eec42566\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:29] local.WARNING: [Calendar] Sync failed {\"calendarId\":\"a33076c1-8d97-431a-99f0-85c9524e118b\",\"code\":400,\"reason\":\"{\n \\\"error\\\": {\n \\\"errors\\\": [\n {\n \\\"domain\\\": \\\"global\\\",\n \\\"reason\\\": \\\"push.webhookUrlNotHttps\\\",\n \\\"message\\\": \\\"WebHook callback must be HTTPS: /webhook/calendar/google?resourceType=event\\\"\n }\n ],\n \\\"code\\\": 400,\n \\\"message\\\": \\\"WebHook callback must be HTTPS: /webhook/calendar/google?resourceType=event\\\"\n }\n}\"} {\"correlation_id\":\"8a9488be-81de-40f5-8554-2503eec42566\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:29] local.ERROR: [SocialAccountService] Failed to refresh token {\"socialAccountId\":1271,\"provider\":\"office\",\"responseBody\":\"{\\\"error\\\":\\\"invalid_client\\\",\\\"error_description\\\":\\\"AADSTS7000215: Invalid client secret provided. Ensure the secret being sent in the request is the client secret value, not the client secret ID, for a secret added to app 'bbcbb2ef-6200-4fae-82bd-d81f5dd738da'. Trace ID: 8d6e82ba-88c3-4ec9-95ee-a4a905803900 Correlation ID: 6ede51d5-704d-4e24-9fb7-88f69f3da5db Timestamp: 2026-04-27 10:28:29Z\\\",\\\"error_codes\\\":[7000215],\\\"timestamp\\\":\\\"2026-04-27 10:28:29Z\\\",\\\"trace_id\\\":\\\"8d6e82ba-88c3-4ec9-95ee-a4a905803900\\\",\\\"correlation_id\\\":\\\"6ede51d5-704d-4e24-9fb7-88f69f3da5db\\\",\\\"error_uri\\\":\\\"https://login.microsoftonline.com/error?code=7000215\\\"}\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:30] local.INFO: [SocialAccountObserver] Saving model {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:30] local.ERROR: [SocialAccountService] Failed to refresh token {\"socialAccountId\":1271,\"provider\":\"office\",\"reason\":\"Flow refresh required.\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:30] local.INFO: [SocialAccountService] Fetching token {\"socialAccountId\":1351,\"provider\":\"google\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:30] local.INFO: [SocialAccountService] Token needs refreshing {\"socialAccountId\":1351,\"provider\":\"google\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:30] local.INFO: [EncryptedTokenManager] Generating access token. {\"mode\":\"legacy\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:30] local.INFO: [SocialAccountService] Refreshing token from provider {\"socialAccountId\":1351,\"provider\":\"google\",\"refreshToken\":\"4271d15b9e60a606439caddc68337f783e472c85b03dacff14d1b6dfded9051c\",\"state\":\"full-refresh\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:30] local.ERROR: [SocialAccountService] Failed to refresh token {\"socialAccountId\":1351,\"provider\":\"google\",\"responseBody\":{\"error\":\"invalid_grant\",\"error_description\":\"Token has been expired or revoked.\"}} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:30] local.INFO: [SocialAccountObserver] Saving model {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:30] local.ERROR: [SocialAccountService] Failed to refresh token {\"socialAccountId\":1351,\"provider\":\"google\",\"reason\":\"Flow refresh required.\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:30] local.INFO: [SocialAccountService] Fetching token {\"socialAccountId\":1366,\"provider\":\"google\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:30] local.INFO: [SocialAccountService] Token needs refreshing {\"socialAccountId\":1366,\"provider\":\"google\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:30] local.INFO: [EncryptedTokenManager] Generating access token. {\"mode\":\"legacy\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:30] local.INFO: [SocialAccountService] Refreshing token from provider {\"socialAccountId\":1366,\"provider\":\"google\",\"refreshToken\":\"ae21385059b2eebfd43f68aecd56eccd702a1aabb6598f1f7ab594ed8af491b4\",\"state\":\"full-refresh\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:30] local.ERROR: [SocialAccountService] Failed to refresh token {\"socialAccountId\":1366,\"provider\":\"google\",\"responseBody\":{\"error\":\"invalid_grant\",\"error_description\":\"Bad Request\"}} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:30] local.INFO: [SocialAccountObserver] Saving model {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:30] local.ERROR: [SocialAccountService] Failed to refresh token {\"socialAccountId\":1366,\"provider\":\"google\",\"reason\":\"Flow refresh required.\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:30] local.INFO: [SocialAccountService] Fetching token {\"socialAccountId\":1115,\"provider\":\"google\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:30] local.INFO: [SocialAccountService] Token needs refreshing {\"socialAccountId\":1115,\"provider\":\"google\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:30] local.INFO: [EncryptedTokenManager] Generating access token. {\"mode\":\"legacy\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:30] local.INFO: [SocialAccountService] Refreshing token from provider {\"socialAccountId\":1115,\"provider\":\"google\",\"refreshToken\":\"356b60f12e262a5e24d3042386ef47d6a6cfe3074c242f4426edcec8646192b1\",\"state\":\"connected\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:30] local.INFO: [SocialAccountObserver] Saving model {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:30] local.INFO: [SocialAccountObserver] Access token was modified, encrypting {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:31] local.INFO: [SocialAccountService] Token refreshed {\"socialAccountId\":1115,\"provider\":\"google\",\"state\":\"connected\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:31] local.INFO: Calendar sync job dispatched {\"calendar_id\":378} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:31] local.INFO: [SocialAccountService] Fetching token {\"socialAccountId\":1421,\"provider\":\"office\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:31] local.INFO: [SocialAccountService] Token needs refreshing {\"socialAccountId\":1421,\"provider\":\"office\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:31] local.INFO: [EncryptedTokenManager] Generating access token. {\"mode\":\"legacy\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:31] local.INFO: [SocialAccountService] Refreshing token from provider {\"socialAccountId\":1421,\"provider\":\"office\",\"refreshToken\":\"9e3e287aa0cc900726e3d4eead2600fb64aa34b437790f6a1fd8048306cb2db9\",\"state\":\"connected\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:31] local.INFO: [SocialAccountService] Fetching token {\"socialAccountId\":1115,\"provider\":\"google\"} {\"correlation_id\":\"8fdf411d-8a4f-46c1-a46a-cc84f3c2f2b1\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:31] local.INFO: [SocialAccountService] Token retrieved {\"socialAccountId\":1115,\"provider\":\"google\"} {\"correlation_id\":\"8fdf411d-8a4f-46c1-a46a-cc84f3c2f2b1\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:31] local.INFO: [EncryptedTokenManager] Generating access token. {\"mode\":\"legacy\"} {\"correlation_id\":\"8fdf411d-8a4f-46c1-a46a-cc84f3c2f2b1\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:31] local.INFO: [Calendar] Processing sync {\"calendarId\":\"2676cb6d-f86c-427e-bf78-591e388e3c1e\",\"from\":null,\"to\":null,\"delta\":\"CJ_x49O3jpIDEJ_x49O3jpIDGAUgw67KlwMow67KlwM=\",\"last_sync\":\"2026-01-19 07:48:40\",\"dateMode\":\"daily\"} {\"correlation_id\":\"8fdf411d-8a4f-46c1-a46a-cc84f3c2f2b1\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:31] local.WARNING: [Pipedrive] Account not connected for user {\"userId\":\"e6538737-e7b4-455f-a37a-3e79b665a220\",\"account\":{\"Jiminny\\\\Models\\\\SocialAccount\":{\"id\":1116,\"sociable_id\":241,\"provider_user_id\":\"19555731\",\"expires\":1775683749,\"refresh_token_expires\":null,\"provider\":\"pipedrive\",\"state\":\"full-refresh\",\"auth_scope\":\"base,deals:full,activities:full,contacts:full,search:read\",\"retry_after\":null,\"created_at\":\"2023-09-08 09:44:29\",\"updated_at\":\"2026-04-08 22:58:34\"}}} {\"correlation_id\":\"8fdf411d-8a4f-46c1-a46a-cc84f3c2f2b1\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:31] local.INFO: [CrmOwnerResolver] Integration owner is not connected, attempting team members {\"crm_provider\":\"pipedrive\",\"crm_owner\":241,\"team_id\":19} {\"correlation_id\":\"8fdf411d-8a4f-46c1-a46a-cc84f3c2f2b1\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:31] local.INFO: [CrmOwnerResolver] No team members found with active crm connection {\"crm_provider\":\"pipedrive\",\"team_id\":19} {\"correlation_id\":\"8fdf411d-8a4f-46c1-a46a-cc84f3c2f2b1\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:31] local.INFO: [CrmOwnerResolver] No team member found with active crm connection {\"crm_provider\":\"pipedrive\",\"team_id\":19} {\"correlation_id\":\"8fdf411d-8a4f-46c1-a46a-cc84f3c2f2b1\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:31] local.WARNING: [Calendar] CRM disconnected for user so events will not be matched {\"provider\":\"pipedrive\",\"user_id\":241,\"message\":\"Your Pipedrive account has become disconnected. Please login to Jiminny to reconnect.\"} {\"correlation_id\":\"8fdf411d-8a4f-46c1-a46a-cc84f3c2f2b1\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:31] local.INFO: [SocialAccountService] Fetching token {\"socialAccountId\":1115,\"provider\":\"google\"} {\"correlation_id\":\"8fdf411d-8a4f-46c1-a46a-cc84f3c2f2b1\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:31] local.INFO: [SocialAccountService] Token retrieved {\"socialAccountId\":1115,\"provider\":\"google\"} {\"correlation_id\":\"8fdf411d-8a4f-46c1-a46a-cc84f3c2f2b1\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:31] local.INFO: [EncryptedTokenManager] Generating access token. {\"mode\":\"legacy\"} {\"correlation_id\":\"8fdf411d-8a4f-46c1-a46a-cc84f3c2f2b1\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:31] local.INFO: [Google Calendar] Failed to watch channel for calendar {\"calendarId\":\"2676cb6d-f86c-427e-bf78-591e388e3c1e\",\"code\":400,\"reason\":\"{\n \\\"error\\\": {\n \\\"errors\\\": [\n {\n \\\"domain\\\": \\\"global\\\",\n \\\"reason\\\": \\\"push.webhookUrlNotHttps\\\",\n \\\"message\\\": \\\"WebHook callback must be HTTPS: /webhook/calendar/google?resourceType=event\\\"\n }\n ],\n \\\"code\\\": 400,\n \\\"message\\\": \\\"WebHook callback must be HTTPS: /webhook/calendar/google?resourceType=event\\\"\n }\n}\"} {\"correlation_id\":\"8fdf411d-8a4f-46c1-a46a-cc84f3c2f2b1\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:31] local.WARNING: [Calendar] Sync failed {\"calendarId\":\"2676cb6d-f86c-427e-bf78-591e388e3c1e\",\"code\":400,\"reason\":\"{\n \\\"error\\\": {\n \\\"errors\\\": [\n {\n \\\"domain\\\": \\\"global\\\",\n \\\"reason\\\": \\\"push.webhookUrlNotHttps\\\",\n \\\"message\\\": \\\"WebHook callback must be HTTPS: /webhook/calendar/google?resourceType=event\\\"\n }\n ],\n \\\"code\\\": 400,\n \\\"message\\\": \\\"WebHook callback must be HTTPS: /webhook/calendar/google?resourceType=event\\\"\n }\n}\"} {\"correlation_id\":\"8fdf411d-8a4f-46c1-a46a-cc84f3c2f2b1\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:32] local.INFO: [SocialAccountObserver] Saving model {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:32] local.INFO: [SocialAccountObserver] Access token was modified, encrypting {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:32] local.INFO: [SocialAccountObserver] Refresh token was modified, encrypting {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:32] local.INFO: [SocialAccountService] Token refreshed {\"socialAccountId\":1421,\"provider\":\"office\",\"state\":\"connected\"} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:32] local.INFO: Calendar sync job dispatched {\"calendar_id\":504} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:32] local.NOTICE: Calendar sync end {\"retrieved_calendars\":31,\"processed_calendars\":3} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:32] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage for command {\"command\":\"calendar:sync\",\"memoryBeforeCommandInMb\":60.0,\"memoryAfterCommandInMB\":62.0,\"memoryPeakBeforeCommandInMb\":99.727,\"memoryPeakAfterCommandInMB\":99.727} {\"correlation_id\":\"4a0d5722-0aa0-4768-bf49-106fc050bf48\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:33] local.INFO: [SocialAccountService] Fetching token {\"socialAccountId\":1421,\"provider\":\"office\"} {\"correlation_id\":\"52b2ff86-82c7-4ed0-b528-35d7d00d69f0\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:33] local.INFO: [SocialAccountService] Token retrieved {\"socialAccountId\":1421,\"provider\":\"office\"} {\"correlation_id\":\"52b2ff86-82c7-4ed0-b528-35d7d00d69f0\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:33] local.INFO: [EncryptedTokenManager] Generating access token. {\"mode\":\"legacy\"} {\"correlation_id\":\"52b2ff86-82c7-4ed0-b528-35d7d00d69f0\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:34] local.INFO: [Calendar] Processing sync {\"calendarId\":\"9e8b1a2c-1a8f-42bd-b161-810fc0baf540\",\"from\":null,\"to\":null,\"delta\":\"R0usmcdvmMuZCBYV0hguCMx0nGVsTQEodM41jSlu-5eY2JUTYHZXE-bDro2tnepp8q_Vq3ITNS8CkncjGjcBfaHkza05dCTIlaXcSIoPNGOl9vNckYy_ZhVgE1SWvgRcCeVT-SyogdB9hP-oaWbH9r51OQrrzAwmalK5aCksyUVh9jG_A-mGVzRPnUB4SKa77o5P659lpx1egON48YU2tQ.D2oErKma-mQrBdXxAKdH7beUkp6HS2PXUWYLCgvbP2c\",\"last_sync\":\"2026-04-27 06:28:41\",\"dateMode\":\"daily\"} {\"correlation_id\":\"52b2ff86-82c7-4ed0-b528-35d7d00d69f0\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:34] local.INFO: [SocialAccountService] Fetching token {\"socialAccountId\":1499,\"provider\":\"hubspot\"} {\"correlation_id\":\"52b2ff86-82c7-4ed0-b528-35d7d00d69f0\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:34] local.INFO: [SocialAccountService] Token retrieved {\"socialAccountId\":1499,\"provider\":\"hubspot\"} {\"correlation_id\":\"52b2ff86-82c7-4ed0-b528-35d7d00d69f0\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:34] local.INFO: [EncryptedTokenManager] Generating access token. {\"mode\":\"legacy\"} {\"correlation_id\":\"52b2ff86-82c7-4ed0-b528-35d7d00d69f0\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:34] local.INFO: [CrmOwnerResolver] Integration owner matched as CRM Owner {\"crm_provider\":\"hubspot\",\"crm_owner\":89,\"team_id\":2} {\"correlation_id\":\"52b2ff86-82c7-4ed0-b528-35d7d00d69f0\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:28:34] local.INFO: [MS Office Calendar] Skipping delta sync for daily mode {\"calendarId\":\"9e8b1a2c-1a8f-42bd-b161-810fc0baf540\"} {\"correlation_id\":\"52b2ff86-82c7-4ed0-b528-35d7d00d69f0\",\"trace_id\":\"bc31793c-511e-425e-b60b-476026a43831\"}\n[2026-04-27 10:29:06] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage before starting command {\"command\":\"meeting-bot:schedule-bot\",\"memoryBeforeCommandInMb\":60.0,\"memoryPeakBeforeCommandInMb\":99.727} {\"correlation_id\":\"7e95c05b-50c8-4e0b-a01c-efeed6af544c\",\"trace_id\":\"07db4d4f-d6e7-44d9-8e54-4f038ea69afb\"}\n[2026-04-27 10:29:06] local.INFO: [ScheduleBotCommand] Number of activities to be captured: 0 {\"correlation_id\":\"7e95c05b-50c8-4e0b-a01c-efeed6af544c\",\"trace_id\":\"07db4d4f-d6e7-44d9-8e54-4f038ea69afb\"}\n[2026-04-27 10:29:06] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage for command {\"command\":\"meeting-bot:schedule-bot\",\"memoryBeforeCommandInMb\":60.0,\"memoryAfterCommandInMB\":62.0,\"memoryPeakBeforeCommandInMb\":99.727,\"memoryPeakAfterCommandInMB\":99.727} {\"correlation_id\":\"7e95c05b-50c8-4e0b-a01c-efeed6af544c\",\"trace_id\":\"07db4d4f-d6e7-44d9-8e54-4f038ea69afb\"}\n[2026-04-27 10:29:09] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage before starting command {\"command\":\"dialers:monitor-activities\",\"memoryBeforeCommandInMb\":60.0,\"memoryPeakBeforeCommandInMb\":99.727} {\"correlation_id\":\"e6e7fbf8-6e80-4529-858e-a4e95d0d3805\",\"trace_id\":\"f91a115a-9fdc-46ce-b9f2-13a1e6d0e325\"}\n[2026-04-27 10:29:09] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage for command {\"command\":\"dialers:monitor-activities\",\"memoryBeforeCommandInMb\":60.0,\"memoryAfterCommandInMB\":62.0,\"memoryPeakBeforeCommandInMb\":99.727,\"memoryPeakAfterCommandInMB\":99.727} {\"correlation_id\":\"e6e7fbf8-6e80-4529-858e-a4e95d0d3805\",\"trace_id\":\"f91a115a-9fdc-46ce-b9f2-13a1e6d0e325\"}\n[2026-04-27 10:29:10] local.NOTICE: Monitoring start {\"correlation_id\":\"211e5e36-1c6c-4198-b928-d2f1525ab0ea\",\"trace_id\":\"c26e980d-6e32-426d-9667-f1d902432b8d\"}\n[2026-04-27 10:29:10] local.NOTICE: Monitoring end {\"correlation_id\":\"211e5e36-1c6c-4198-b928-d2f1525ab0ea\",\"trace_id\":\"c26e980d-6e32-426d-9667-f1d902432b8d\"}\n[2026-04-27 10:29:12] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage before starting command {\"command\":\"mailbox:skip-lists:refresh\",\"memoryBeforeCommandInMb\":60.0,\"memoryPeakBeforeCommandInMb\":99.727} {\"correlation_id\":\"684bdf68-76ee-42dd-9d97-62f414a33b1b\",\"trace_id\":\"284c98ef-5b7f-4572-996c-72c5bc2995f9\"}\n[2026-04-27 10:29:13] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage for command {\"command\":\"mailbox:skip-lists:refresh\",\"memoryBeforeCommandInMb\":60.0,\"memoryAfterCommandInMB\":62.0,\"memoryPeakBeforeCommandInMb\":99.727,\"memoryPeakAfterCommandInMB\":99.727} {\"correlation_id\":\"684bdf68-76ee-42dd-9d97-62f414a33b1b\",\"trace_id\":\"284c98ef-5b7f-4572-996c-72c5bc2995f9\"}\n[2026-04-27 10:29:18] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage before starting command {\"command\":\"mailbox:batch:process\",\"memoryBeforeCommandInMb\":60.0,\"memoryPeakBeforeCommandInMb\":99.727} {\"correlation_id\":\"6e74b148-ae54-42ff-ab72-2ded18d10ff4\",\"trace_id\":\"d05bc9a0-450c-45b2-be3b-225a551ed909\"}\n[2026-04-27 10:29:18] local.INFO: [EmailSchedule] STARTING batch process {\"host\":\"docker_lamp_1\"} {\"correlation_id\":\"6e74b148-ae54-42ff-ab72-2ded18d10ff4\",\"trace_id\":\"d05bc9a0-450c-45b2-be3b-225a551ed909\"}\n[2026-04-27 10:29:18] local.INFO: [EmailSchedule] FINISHED batch process {\"host\":\"docker_lamp_1\",\"processed\":0} {\"correlation_id\":\"6e74b148-ae54-42ff-ab72-2ded18d10ff4\",\"trace_id\":\"d05bc9a0-450c-45b2-be3b-225a551ed909\"}\n[2026-04-27 10:29:18] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage for command {\"command\":\"mailbox:batch:process\",\"memoryBeforeCommandInMb\":60.0,\"memoryAfterCommandInMB\":62.0,\"memoryPeakBeforeCommandInMb\":99.727,\"memoryPeakAfterCommandInMB\":99.727} {\"correlation_id\":\"6e74b148-ae54-42ff-ab72-2ded18d10ff4\",\"trace_id\":\"d05bc9a0-450c-45b2-be3b-225a551ed909\"}\n[2026-04-27 10:30:08] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage before starting command {\"command\":\"meeting-bot:schedule-bot\",\"memoryBeforeCommandInMb\":60.0,\"memoryPeakBeforeCommandInMb\":99.727} {\"correlation_id\":\"24b807e0-b413-4ecc-a89b-365280be88ac\",\"trace_id\":\"542ac70c-6756-4409-8224-0844a7576d9c\"}\n[2026-04-27 10:30:08] local.INFO: [ScheduleBotCommand] Number of activities to be captured: 0 {\"correlation_id\":\"24b807e0-b413-4ecc-a89b-365280be88ac\",\"trace_id\":\"542ac70c-6756-4409-8224-0844a7576d9c\"}\n[2026-04-27 10:30:08] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage for command {\"command\":\"meeting-bot:schedule-bot\",\"memoryBeforeCommandInMb\":60.0,\"memoryAfterCommandInMB\":62.0,\"memoryPeakBeforeCommandInMb\":99.727,\"memoryPeakAfterCommandInMB\":99.727} {\"correlation_id\":\"24b807e0-b413-4ecc-a89b-365280be88ac\",\"trace_id\":\"542ac70c-6756-4409-8224-0844a7576d9c\"}\n[2026-04-27 10:30:10] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage before starting command {\"command\":\"dialers:monitor-activities\",\"memoryBeforeCommandInMb\":60.0,\"memoryPeakBeforeCommandInMb\":99.727} {\"correlation_id\":\"2711ca04-b4a7-4f3a-b0b2-5826fea4da3b\",\"trace_id\":\"2613ffca-f181-4d91-ad55-ec1bdd97ee7e\"}\n[2026-04-27 10:30:10] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage for command {\"command\":\"dialers:monitor-activities\",\"memoryBeforeCommandInMb\":60.0,\"memoryAfterCommandInMB\":62.0,\"memoryPeakBeforeCommandInMb\":99.727,\"memoryPeakAfterCommandInMB\":99.727} {\"correlation_id\":\"2711ca04-b4a7-4f3a-b0b2-5826fea4da3b\",\"trace_id\":\"2613ffca-f181-4d91-ad55-ec1bdd97ee7e\"}\n[2026-04-27 10:30:17] local.NOTICE: Monitoring start {\"correlation_id\":\"e1537475-8481-4144-b9f5-596ea8034b8a\",\"trace_id\":\"f5561d0a-0cb7-4c72-8bde-cce617183529\"}\n[2026-04-27 10:30:17] local.NOTICE: Monitoring end {\"correlation_id\":\"e1537475-8481-4144-b9f5-596ea8034b8a\",\"trace_id\":\"f5561d0a-0cb7-4c72-8bde-cce617183529\"}\n[2026-04-27 10:30:21] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage before starting command {\"command\":\"mailbox:skip-lists:refresh\",\"memoryBeforeCommandInMb\":60.0,\"memoryPeakBeforeCommandInMb\":99.727} {\"correlation_id\":\"8a911b60-16a0-416e-934e-3b7e0280e452\",\"trace_id\":\"3c2bbdd6-985d-4624-b42b-fd9cc2b4756f\"}\n[2026-04-27 10:30:21] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage for command {\"command\":\"mailbox:skip-lists:refresh\",\"memoryBeforeCommandInMb\":60.0,\"memoryAfterCommandInMB\":62.0,\"memoryPeakBeforeCommandInMb\":99.727,\"memoryPeakAfterCommandInMB\":99.727} {\"correlation_id\":\"8a911b60-16a0-416e-934e-3b7e0280e452\",\"trace_id\":\"3c2bbdd6-985d-4624-b42b-fd9cc2b4756f\"}\n[2026-04-27 10:30:25] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage before starting command {\"command\":\"mailbox:batch:process\",\"memoryBeforeCommandInMb\":60.0,\"memoryPeakBeforeCommandInMb\":99.727} {\"correlation_id\":\"f62efaa6-201f-43b2-bf45-9619982dffb1\",\"trace_id\":\"f1ad3d60-af4e-4427-9d77-6aa71cfed172\"}\n[2026-04-27 10:30:25] local.INFO: [EmailSchedule] STARTING batch process {\"host\":\"docker_lamp_1\"} {\"correlation_id\":\"f62efaa6-201f-43b2-bf45-9619982dffb1\",\"trace_id\":\"f1ad3d60-af4e-4427-9d77-6aa71cfed172\"}\n[2026-04-27 10:30:25] local.INFO: [EmailSchedule] FINISHED batch process {\"host\":\"docker_lamp_1\",\"processed\":0} {\"correlation_id\":\"f62efaa6-201f-43b2-bf45-9619982dffb1\",\"trace_id\":\"f1ad3d60-af4e-4427-9d77-6aa71cfed172\"}\n[2026-04-27 10:30:25] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage for command {\"command\":\"mailbox:batch:process\",\"memoryBeforeCommandInMb\":60.0,\"memoryAfterCommandInMB\":62.0,\"memoryPeakBeforeCommandInMb\":99.727,\"memoryPeakAfterCommandInMB\":99.727} {\"correlation_id\":\"f62efaa6-201f-43b2-bf45-9619982dffb1\",\"trace_id\":\"f1ad3d60-af4e-4427-9d77-6aa71cfed172\"}\n[2026-04-27 10:30:30] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage before starting command {\"command\":\"conference:monitor:count\",\"memoryBeforeCommandInMb\":60.0,\"memoryPeakBeforeCommandInMb\":99.727} {\"correlation_id\":\"cf6dc132-ecb8-4efc-bead-06bf1c96cae6\",\"trace_id\":\"9e2b073b-940d-498a-babe-115f633dd954\"}\n[2026-04-27 10:30:30] local.INFO: Running conference:monitor:count command for activities in (2026-04-27 10:28:00, 2026-04-27 10:30:00] {\"correlation_id\":\"cf6dc132-ecb8-4efc-bead-06bf1c96cae6\",\"trace_id\":\"9e2b073b-940d-498a-babe-115f633dd954\"}\n[2026-04-27 10:30:30] local.INFO: [conference:monitor:count] No activities found in (2026-04-27 10:28:00, 2026-04-27 10:30:00] {\"correlation_id\":\"cf6dc132-ecb8-4efc-bead-06bf1c96cae6\",\"trace_id\":\"9e2b073b-940d-498a-babe-115f633dd954\"}\n[2026-04-27 10:30:30] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage for command {\"command\":\"conference:monitor:count\",\"memoryBeforeCommandInMb\":60.0,\"memoryAfterCommandInMB\":62.0,\"memoryPeakBeforeCommandInMb\":99.727,\"memoryPeakAfterCommandInMB\":99.727} {\"correlation_id\":\"cf6dc132-ecb8-4efc-bead-06bf1c96cae6\",\"trace_id\":\"9e2b073b-940d-498a-babe-115f633dd954\"}\n[2026-04-27 10:30:32] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage before starting command {\"command\":\"activity:purge-stale\",\"memoryBeforeCommandInMb\":60.0,\"memoryPeakBeforeCommandInMb\":99.727} {\"correlation_id\":\"f455260d-d1c1-456f-8937-d4bb026d9078\",\"trace_id\":\"f8c2e585-0137-4103-9e16-1b4ce05df2ef\"}\n[2026-04-27 10:30:32] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage for command {\"command\":\"activity:purge-stale\",\"memoryBeforeCommandInMb\":60.0,\"memoryAfterCommandInMB\":62.0,\"memoryPeakBeforeCommandInMb\":99.727,\"memoryPeakAfterCommandInMB\":99.727} {\"correlation_id\":\"f455260d-d1c1-456f-8937-d4bb026d9078\",\"trace_id\":\"f8c2e585-0137-4103-9e16-1b4ce05df2ef\"}\n[2026-04-27 10:30:35] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage before starting command {\"command\":\"mailbox:text-relay:sync\",\"memoryBeforeCommandInMb\":60.0,\"memoryPeakBeforeCommandInMb\":99.727} {\"correlation_id\":\"be7d3c6a-664a-42e8-a4b3-cbd021296404\",\"trace_id\":\"0b250827-4c02-4237-8b34-e5ddd004eba1\"}\n[2026-04-27 10:30:35] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage for command {\"command\":\"mailbox:text-relay:sync\",\"memoryBeforeCommandInMb\":60.0,\"memoryAfterCommandInMB\":62.0,\"memoryPeakBeforeCommandInMb\":99.727,\"memoryPeakAfterCommandInMB\":99.727} {\"correlation_id\":\"be7d3c6a-664a-42e8-a4b3-cbd021296404\",\"trace_id\":\"0b250827-4c02-4237-8b34-e5ddd004eba1\"}\n[2026-04-27 10:30:37] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage before starting command {\"command\":\"conference:pre-meeting-notification\",\"memoryBeforeCommandInMb\":60.0,\"memoryPeakBeforeCommandInMb\":99.727} {\"correlation_id\":\"5d8fbdf6-2737-4d73-babe-699c88279e1f\",\"trace_id\":\"0882a963-b02f-4049-b670-12b92f80829a\"}\n[2026-04-27 10:30:37] local.INFO: Running pre-meeting notification command {\"correlation_id\":\"5d8fbdf6-2737-4d73-babe-699c88279e1f\",\"trace_id\":\"0882a963-b02f-4049-b670-12b92f80829a\"}\n[2026-04-27 10:30:37] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage for command {\"command\":\"conference:pre-meeting-notification\",\"memoryBeforeCommandInMb\":60.0,\"memoryAfterCommandInMB\":62.0,\"memoryPeakBeforeCommandInMb\":99.727,\"memoryPeakAfterCommandInMB\":99.727} {\"correlation_id\":\"5d8fbdf6-2737-4d73-babe-699c88279e1f\",\"trace_id\":\"0882a963-b02f-4049-b670-12b92f80829a\"}\n[2026-04-27 10:30:39] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage before starting command {\"command\":\"conference:monitor:start\",\"memoryBeforeCommandInMb\":60.0,\"memoryPeakBeforeCommandInMb\":99.727} {\"correlation_id\":\"e8968e47-9acd-4bd4-9e7d-3c415f46df05\",\"trace_id\":\"287fba85-f395-41ab-813d-49cb5d0ea06d\"}\n[2026-04-27 10:30:39] local.INFO: Running conference:monitor:start command for activities in (2026-04-27 10:20:00, 2026-04-27 10:25:00] {\"correlation_id\":\"e8968e47-9acd-4bd4-9e7d-3c415f46df05\",\"trace_id\":\"287fba85-f395-41ab-813d-49cb5d0ea06d\"}\n[2026-04-27 10:30:39] local.INFO: [conference:monitor:start] No activities found in (2026-04-27 10:20:00, 2026-04-27 10:25:00] {\"correlation_id\":\"e8968e47-9acd-4bd4-9e7d-3c415f46df05\",\"trace_id\":\"287fba85-f395-41ab-813d-49cb5d0ea06d\"}\n[2026-04-27 10:30:39] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage for command {\"command\":\"conference:monitor:start\",\"memoryBeforeCommandInMb\":60.0,\"memoryAfterCommandInMB\":62.0,\"memoryPeakBeforeCommandInMb\":99.727,\"memoryPeakAfterCommandInMB\":99.727} {\"correlation_id\":\"e8968e47-9acd-4bd4-9e7d-3c415f46df05\",\"trace_id\":\"287fba85-f395-41ab-813d-49cb5d0ea06d\"}\n[2026-04-27 10:30:43] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage before starting command {\"command\":\"conference:monitor:end\",\"memoryBeforeCommandInMb\":60.0,\"memoryPeakBeforeCommandInMb\":99.727} {\"correlation_id\":\"b87eb011-7108-46bd-aa90-975ebe41f483\",\"trace_id\":\"1f54c2a6-fad1-4804-87d8-4972a378e91b\"}\n[2026-04-27 10:30:43] local.INFO: conference:monitor:end:Jiminny\\Console\\Commands\\Activities\\MonitorMeetingEndCommand::logActivitiesEnded {\"from\":\"10:25\",\"to\":\"10:30\"} {\"correlation_id\":\"b87eb011-7108-46bd-aa90-975ebe41f483\",\"trace_id\":\"1f54c2a6-fad1-4804-87d8-4972a378e91b\"}\n[2026-04-27 10:30:44] local.INFO: conference:monitor:end:Jiminny\\Console\\Commands\\Activities\\MonitorMeetingEndCommand::logActivitiesWithUnfinishedSession {\"from\":\"00:20\",\"to\":\"00:25\"} {\"correlation_id\":\"b87eb011-7108-46bd-aa90-975ebe41f483\",\"trace_id\":\"1f54c2a6-fad1-4804-87d8-4972a378e91b\"}\n[2026-04-27 10:30:44] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage for command {\"command\":\"conference:monitor:end\",\"memoryBeforeCommandInMb\":60.0,\"memoryAfterCommandInMB\":62.0,\"memoryPeakBeforeCommandInMb\":99.727,\"memoryPeakAfterCommandInMB\":99.727} {\"correlation_id\":\"b87eb011-7108-46bd-aa90-975ebe41f483\",\"trace_id\":\"1f54c2a6-fad1-4804-87d8-4972a378e91b\"}\n[2026-04-27 10:30:51] local.NOTICE: Repairing HubSpot tokens start {\"correlation_id\":\"2e6dfa3d-f07d-4eee-8232-6138133e2fbb\",\"trace_id\":\"41dea86e-0ecc-414e-8303-e60d8ae09976\"}\n[2026-04-27 10:30:51] local.INFO: Trying to refresh HubSpot token {\"account_id\":59,\"updated_at\":\"2025-10-03 09:32:05\"} {\"correlation_id\":\"2e6dfa3d-f07d-4eee-8232-6138133e2fbb\",\"trace_id\":\"41dea86e-0ecc-414e-8303-e60d8ae09976\"}\n[2026-04-27 10:30:51] local.INFO: [EncryptedTokenManager] Generating access token. {\"mode\":\"legacy\"} {\"correlation_id\":\"2e6dfa3d-f07d-4eee-8232-6138133e2fbb\",\"trace_id\":\"41dea86e-0ecc-414e-8303-e60d8ae09976\"}\n[2026-04-27 10:30:51] local.INFO: [SocialAccountService] Refreshing token from provider {\"socialAccountId\":59,\"provider\":\"hubspot\",\"refreshToken\":\"97b78f6e2cc49965c00c2492b602b02708b1392551e6b3f113fbaa48992af90b\",\"state\":\"full-refresh\"} {\"correlation_id\":\"2e6dfa3d-f07d-4eee-8232-6138133e2fbb\",\"trace_id\":\"41dea86e-0ecc-414e-8303-e60d8ae09976\"}\n[2026-04-27 10:30:52] local.ERROR: Failed to refresh HubSpot token {\"account_id\":59,\"updated_at\":\"2025-10-03 09:32:05\",\"reason\":\"missing or invalid refresh token\",\"previous\":\"\"} {\"correlation_id\":\"2e6dfa3d-f07d-4eee-8232-6138133e2fbb\",\"trace_id\":\"41dea86e-0ecc-414e-8303-e60d8ae09976\"}\n[2026-04-27 10:30:52] local.INFO: Trying to refresh HubSpot token {\"account_id\":306,\"updated_at\":\"2023-11-27 09:30:03\"} {\"correlation_id\":\"2e6dfa3d-f07d-4eee-8232-6138133e2fbb\",\"trace_id\":\"41dea86e-0ecc-414e-8303-e60d8ae09976\"}\n[2026-04-27 10:30:52] local.INFO: [EncryptedTokenManager] Generating access token. {\"mode\":\"legacy\"} {\"correlation_id\":\"2e6dfa3d-f07d-4eee-8232-6138133e2fbb\",\"trace_id\":\"41dea86e-0ecc-414e-8303-e60d8ae09976\"}\n[2026-04-27 10:30:52] local.INFO: [SocialAccountService] Refreshing token from provider {\"socialAccountId\":306,\"provider\":\"hubspot\",\"refreshToken\":\"6fa6aa8cc641d131231acc3470f5c03cb3b07b2e580fb18f8acb3b1dbb72549b\",\"state\":\"full-refresh\"} {\"correlation_id\":\"2e6dfa3d-f07d-4eee-8232-6138133e2fbb\",\"trace_id\":\"41dea86e-0ecc-414e-8303-e60d8ae09976\"}\n[2026-04-27 10:30:52] local.ERROR: Failed to refresh HubSpot token {\"account_id\":306,\"updated_at\":\"2023-11-27 09:30:03\",\"reason\":\"missing or invalid refresh token\",\"previous\":\"\"} {\"correlation_id\":\"2e6dfa3d-f07d-4eee-8232-6138133e2fbb\",\"trace_id\":\"41dea86e-0ecc-414e-8303-e60d8ae09976\"}\n[2026-04-27 10:30:52] local.INFO: Trying to refresh HubSpot token {\"account_id\":1372,\"updated_at\":\"2025-10-02 14:47:06\"} {\"correlation_id\":\"2e6dfa3d-f07d-4eee-8232-6138133e2fbb\",\"trace_id\":\"41dea86e-0ecc-414e-8303-e60d8ae09976\"}\n[2026-04-27 10:30:52] local.INFO: [EncryptedTokenManager] Generating access token. {\"mode\":\"legacy\"} {\"correlation_id\":\"2e6dfa3d-f07d-4eee-8232-6138133e2fbb\",\"trace_id\":\"41dea86e-0ecc-414e-8303-e60d8ae09976\"}\n[2026-04-27 10:30:52] local.INFO: [SocialAccountService] Refreshing token from provider {\"socialAccountId\":1372,\"provider\":\"hubspot\",\"refreshToken\":\"9aa73948c761da29dce46c177cf9aee1fde483a44169ca38723f9f0597d7a8c4\",\"state\":\"full-refresh\"} {\"correlation_id\":\"2e6dfa3d-f07d-4eee-8232-6138133e2fbb\",\"trace_id\":\"41dea86e-0ecc-414e-8303-e60d8ae09976\"}\n[2026-04-27 10:30:52] local.ERROR: Failed to refresh HubSpot token {\"account_id\":1372,\"updated_at\":\"2025-10-02 14:47:06\",\"reason\":\"missing or invalid refresh token\",\"previous\":\"\"} {\"correlation_id\":\"2e6dfa3d-f07d-4eee-8232-6138133e2fbb\",\"trace_id\":\"41dea86e-0ecc-414e-8303-e60d8ae09976\"}\n[2026-04-27 10:30:52] local.NOTICE: Repairing HubSpot tokens end {\"total\":3,\"fixed\":0,\"failed\":3} {\"correlation_id\":\"2e6dfa3d-f07d-4eee-8232-6138133e2fbb\",\"trace_id\":\"41dea86e-0ecc-414e-8303-e60d8ae09976\"}\n[2026-04-27 10:30:56] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage before starting command {\"command\":\"jiminny:transcription:retry-failed\",\"memoryBeforeCommandInMb\":60.0,\"memoryPeakBeforeCommandInMb\":99.727} {\"correlation_id\":\"a6655a64-38ad-4c65-96f1-f5a294dbb343\",\"trace_id\":\"52cdca32-c0c8-4215-81a2-4263bbde2158\"}\n[2026-04-27 10:30:56] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage before starting command {\"command\":\"conference:pre-meeting-reminder\",\"memoryBeforeCommandInMb\":60.0,\"memoryPeakBeforeCommandInMb\":99.727} {\"correlation_id\":\"ec2d49dd-ab36-4f4e-8a60-7bf5ea384cac\",\"trace_id\":\"b3e89960-9a27-4bf7-8b55-95f5704f9a10\"}\n[2026-04-27 10:30:57] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage for command {\"command\":\"jiminny:transcription:retry-failed\",\"memoryBeforeCommandInMb\":60.0,\"memoryAfterCommandInMB\":62.0,\"memoryPeakBeforeCommandInMb\":99.727,\"memoryPeakAfterCommandInMB\":99.727} {\"correlation_id\":\"a6655a64-38ad-4c65-96f1-f5a294dbb343\",\"trace_id\":\"52cdca32-c0c8-4215-81a2-4263bbde2158\"}\n[2026-04-27 10:30:57] local.INFO: [HubSpot Journal Polling] Getting offset from database {\"offset\":\"\",\"jiminny_team_id\":1} {\"correlation_id\":\"1f928e93-b17e-4518-a1b9-a8280d1d95e8\",\"trace_id\":\"ba203ed6-fe98-4fd7-aa62-8de9fb8ffeca\"}\n[2026-04-27 10:30:57] local.INFO: [HubSpot Journal Command] Starting polling service {\"correlation_id\":\"1f928e93-b17e-4518-a1b9-a8280d1d95e8\",\"trace_id\":\"ba203ed6-fe98-4fd7-aa62-8de9fb8ffeca\"}\n[2026-04-27 10:30:57] local.INFO: [HubSpot Journal Polling] Service starting {\"memory_limit\":\"256M\",\"max_execution_time\":\"0\",\"initial_memory_mb\":62.0} {\"correlation_id\":\"1f928e93-b17e-4518-a1b9-a8280d1d95e8\",\"trace_id\":\"ba203ed6-fe98-4fd7-aa62-8de9fb8ffeca\"}\n[2026-04-27 10:30:57] local.INFO: [HubSpot Journal Polling] Acquired polling lock {\"expires_at\":\"2026-04-27T10:32:57.492763Z\"} {\"correlation_id\":\"1f928e93-b17e-4518-a1b9-a8280d1d95e8\",\"trace_id\":\"ba203ed6-fe98-4fd7-aa62-8de9fb8ffeca\"}\n[2026-04-27 10:30:57] local.INFO: [HubSpot Journal Polling] Getting offset from database {\"offset\":\"\",\"jiminny_team_id\":1} {\"correlation_id\":\"1f928e93-b17e-4518-a1b9-a8280d1d95e8\",\"trace_id\":\"ba203ed6-fe98-4fd7-aa62-8de9fb8ffeca\"}\n[2026-04-27 10:30:57] local.INFO: [HubSpot Journal API] Fetching latest journal entry {\"url\":\"https://api.hubapi.com/webhooks/v4/journal/latest\"} {\"correlation_id\":\"1f928e93-b17e-4518-a1b9-a8280d1d95e8\",\"trace_id\":\"ba203ed6-fe98-4fd7-aa62-8de9fb8ffeca\"}\n[2026-04-27 10:30:57] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage for command {\"command\":\"conference:pre-meeting-reminder\",\"memoryBeforeCommandInMb\":60.0,\"memoryAfterCommandInMB\":62.0,\"memoryPeakBeforeCommandInMb\":99.727,\"memoryPeakAfterCommandInMB\":99.727} {\"correlation_id\":\"ec2d49dd-ab36-4f4e-8a60-7bf5ea384cac\",\"trace_id\":\"b3e89960-9a27-4bf7-8b55-95f5704f9a10\"}\n[2026-04-27 10:30:58] local.INFO: [HubSpot Journal Polling] No data {\"correlation_id\":\"1f928e93-b17e-4518-a1b9-a8280d1d95e8\",\"trace_id\":\"ba203ed6-fe98-4fd7-aa62-8de9fb8ffeca\"}\n[2026-04-27 10:31:02] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage before starting command {\"command\":\"crm:reset-governor\",\"memoryBeforeCommandInMb\":60.0,\"memoryPeakBeforeCommandInMb\":99.727} {\"correlation_id\":\"f261cd75-c916-4f21-b4f4-856fe0ed315c\",\"trace_id\":\"6db8819d-ffaa-4fe8-8ccd-0dcc326b8cdc\"}\n[2026-04-27 10:31:02] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage for command {\"command\":\"crm:reset-governor\",\"memoryBeforeCommandInMb\":60.0,\"memoryAfterCommandInMB\":62.0,\"memoryPeakBeforeCommandInMb\":99.727,\"memoryPeakAfterCommandInMB\":99.727} {\"correlation_id\":\"f261cd75-c916-4f21-b4f4-856fe0ed315c\",\"trace_id\":\"6db8819d-ffaa-4fe8-8ccd-0dcc326b8cdc\"}\n[2026-04-27 10:31:03] local.INFO: [HubSpot Journal Polling] Getting offset from database {\"offset\":\"\",\"jiminny_team_id\":1} {\"correlation_id\":\"1f928e93-b17e-4518-a1b9-a8280d1d95e8\",\"trace_id\":\"ba203ed6-fe98-4fd7-aa62-8de9fb8ffeca\"}\n[2026-04-27 10:31:03] local.INFO: [HubSpot Journal API] Fetching latest journal entry {\"url\":\"https://api.hubapi.com/webhooks/v4/journal/latest\"} {\"correlation_id\":\"1f928e93-b17e-4518-a1b9-a8280d1d95e8\",\"trace_id\":\"ba203ed6-fe98-4fd7-aa62-8de9fb8ffeca\"}\n[2026-04-27 10:31:03] local.INFO: [HubSpot Journal Polling] No data {\"correlation_id\":\"1f928e93-b17e-4518-a1b9-a8280d1d95e8\",\"trace_id\":\"ba203ed6-fe98-4fd7-aa62-8de9fb8ffeca\"}\n[2026-04-27 10:31:06] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage before starting command {\"command\":\"activity:sync\",\"memoryBeforeCommandInMb\":60.0,\"memoryPeakBeforeCommandInMb\":99.727} {\"correlation_id\":\"f9151ed9-60aa-4b2b-b962-9eab5f75405b\",\"trace_id\":\"dba432b8-cb4b-4e76-8684-01d96f91e453\"}\n[2026-04-27 10:31:07] local.INFO: Dispatching activity sync job {\"import_id\":811739,\"provider\":\"twilio-flex\",\"team\":\"jiminny\"} {\"correlation_id\":\"f9151ed9-60aa-4b2b-b962-9eab5f75405b\",\"trace_id\":\"dba432b8-cb4b-4e76-8684-01d96f91e453\"}\n[2026-04-27 10:31:07] local.INFO: Dispatching activity sync job {\"import_id\":811740,\"provider\":\"xant\",\"team\":\"jiminny\"} {\"correlation_id\":\"f9151ed9-60aa-4b2b-b962-9eab5f75405b\",\"trace_id\":\"dba432b8-cb4b-4e76-8684-01d96f91e453\"}\n[2026-04-27 10:31:07] local.INFO: Dispatching activity sync job {\"import_id\":811741,\"provider\":\"apollo\",\"team\":\"jiminny\"} {\"correlation_id\":\"f9151ed9-60aa-4b2b-b962-9eab5f75405b\",\"trace_id\":\"dba432b8-cb4b-4e76-8684-01d96f91e453\"}\n[2026-04-27 10:31:07] local.INFO: Dispatching activity sync job {\"import_id\":811742,\"provider\":\"groove\",\"team\":\"jiminny\"} {\"correlation_id\":\"f9151ed9-60aa-4b2b-b962-9eab5f75405b\",\"trace_id\":\"dba432b8-cb4b-4e76-8684-01d96f91e453\"}\n[2026-04-27 10:31:07] local.INFO: Dispatching activity sync job {\"import_id\":811743,\"provider\":\"twilio-video\",\"team\":\"jiminny\"} {\"correlation_id\":\"f9151ed9-60aa-4b2b-b962-9eab5f75405b\",\"trace_id\":\"dba432b8-cb4b-4e76-8684-01d96f91e453\"}\n[2026-04-27 10:31:07] local.INFO: Dispatching activity sync job {\"import_id\":811744,\"provider\":\"hubspot\",\"team\":\"hubspot\"} {\"correlation_id\":\"f9151ed9-60aa-4b2b-b962-9eab5f75405b\",\"trace_id\":\"dba432b8-cb4b-4e76-8684-01d96f91e453\"}\n[2026-04-27 10:31:07] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage for command {\"command\":\"activity:sync\",\"memoryBeforeCommandInMb\":60.0,\"memoryAfterCommandInMB\":62.0,\"memoryPeakBeforeCommandInMb\":99.727,\"memoryPeakAfterCommandInMB\":99.727} {\"correlation_id\":\"f9151ed9-60aa-4b2b-b962-9eab5f75405b\",\"trace_id\":\"dba432b8-cb4b-4e76-8684-01d96f91e453\"}\n[2026-04-27 10:31:07] local.WARNING: [Salesforce] Account not connected for user {\"userId\":\"cdf8b554-d951-4758-bc2b-c1b85d1cd0b9\",\"account\":null} {\"correlation_id\":\"e7a521c5-fad4-48c2-b880-d572b5d237f4\",\"trace_id\":\"dba432b8-cb4b-4e76-8684-01d96f91e453\"}\n[2026-04-27 10:31:07] local.INFO: [CrmOwnerResolver] Integration owner is not connected, attempting team members {\"crm_provider\":\"salesforce\",\"crm_owner\":3,\"team_id\":1} {\"correlation_id\":\"e7a521c5-fad4-48c2-b880-d572b5d237f4\",\"trace_id\":\"dba432b8-cb4b-4e76-8684-01d96f91e453\"}\n[2026-04-27 10:31:07] local.INFO: [SocialAccountService] Fetching token {\"socialAccountId\":1500,\"provider\":\"salesforce\"} {\"correlation_id\":\"e7a521c5-fad4-48c2-b880-d572b5d237f4\",\"trace_id\":\"dba432b8-cb4b-4e76-8684-01d96f91e453\"}\n[2026-04-27 10:31:07] local.INFO: [SocialAccountService] Token retrieved {\"socialAccountId\":1500,\"provider\":\"salesforce\"} {\"correlation_id\":\"e7a521c5-fad4-48c2-b880-d572b5d237f4\",\"trace_id\":\"dba432b8-cb4b-4e76-8684-01d96f91e453\"}\n[2026-04-27 10:31:07] local.INFO: [EncryptedTokenManager] Generating access token. {\"mode\":\"legacy\"} {\"correlation_id\":\"e7a521c5-fad4-48c2-b880-d572b5d237f4\",\"trace_id\":\"dba432b8-cb4b-4e76-8684-01d96f91e453\"}\n[2026-04-27 10:31:07] local.INFO: [CrmOwnerResolver] TeamMember found with active crm connection {\"crm_provider\":\"salesforce\",\"crm_owner\":143,\"team_id\":1} {\"correlation_id\":\"e7a521c5-fad4-48c2-b880-d572b5d237f4\",\"trace_id\":\"dba432b8-cb4b-4e76-8684-01d96f91e453\"}\n[2026-04-27 10:31:07] local.INFO: [SocialAccountService] Fetching token {\"socialAccountId\":1194,\"provider\":\"twilio-flex\"} {\"correlation_id\":\"e7a521c5-fad4-48c2-b880-d572b5d237f4\",\"trace_id\":\"dba432b8-cb4b-4e76-8684-01d96f91e453\"}\n[2026-04-27 10:31:07] local.INFO: [SocialAccountService] Token retrieved {\"socialAccountId\":1194,\"provider\":\"twilio-flex\"} {\"correlation_id\":\"e7a521c5-fad4-48c2-b880-d572b5d237f4\",\"trace_id\":\"dba432b8-cb4b-4e76-8684-01d96f91e453\"}\n[2026-04-27 10:31:07] local.INFO: [EncryptedTokenManager] Generating access token. {\"mode\":\"legacy\"} {\"correlation_id\":\"e7a521c5-fad4-48c2-b880-d572b5d237f4\",\"trace_id\":\"dba432b8-cb4b-4e76-8684-01d96f91e453\"}\n[2026-04-27 10:31:07] local.INFO: [SyncActivity] Start {\"import_id\":811739,\"provider\":\"twilio-flex\",\"provider_id\":317,\"team\":\"jiminny\",\"team_id\":1} {\"correlation_id\":\"e7a521c5-fad4-48c2-b880-d572b5d237f4\",\"trace_id\":\"dba432b8-cb4b-4e76-8684-01d96f91e453\"}\n[2026-04-27 10:31:07] local.NOTICE: [TwilioFlex] Calls import start {\"correlation_id\":\"e7a521c5-fad4-48c2-b880-d572b5d237f4\",\"trace_id\":\"dba432b8-cb4b-4e76-8684-01d96f91e453\"}\n[2026-04-27 10:31:08] local.ALERT: [SyncActivity] Failed {\"import_id\":811739,\"provider\":\"twilio-flex\",\"provider_id\":317,\"team\":\"jiminny\",\"team_id\":1,\"reason\":\"[HTTP 401] Unable to fetch page: Authenticate\",\"file\":\"/home/jiminny/vendor/twilio/sdk/src/Twilio/Page.php\",\"line\":60} {\"correlation_id\":\"e7a521c5-fad4-48c2-b880-d572b5d237f4\",\"trace_id\":\"dba432b8-cb4b-4e76-8684-01d96f91e453\"}\n[2026-04-27 10:31:08] local.INFO: [HubSpot Journal Polling] Getting offset from database {\"offset\":\"\",\"jiminny_team_id\":1} {\"correlation_id\":\"1f928e93-b17e-4518-a1b9-a8280d1d95e8\",\"trace_id\":\"ba203ed6-fe98-4fd7-aa62-8de9fb8ffeca\"}\n[2026-04-27 10:31:08] local.INFO: [HubSpot Journal API] Fetching latest journal entry {\"url\":\"https://api.hubapi.com/webhooks/v4/journal/latest\"} {\"correlation_id\":\"1f928e93-b17e-4518-a1b9-a8280d1d95e8\",\"trace_id\":\"ba203ed6-fe98-4fd7-aa62-8de9fb8ffeca\"}\n[2026-04-27 10:31:09] local.INFO: [SocialAccountService] Fetching token {\"socialAccountId\":1500,\"provider\":\"salesforce\"} {\"correlation_id\":\"73848601-79bb-4161-91ae-37acf4385d24\",\"trace_id\":\"dba432b8-cb4b-4e76-8684-01d96f91e453\"}\n[2026-04-27 10:31:09] local.INFO: [SocialAccountService] Token retrieved {\"socialAccountId\":1500,\"provider\":\"salesforce\"} {\"correlation_id\":\"73848601-79bb-4161-91ae-37acf4385d24\",\"trace_id\":\"dba432b8-cb4b-4e76-8684-01d96f91e453\"}\n[2026-04-27 10:31:09] local.INFO: [EncryptedTokenManager] Generating access token. {\"mode\":\"legacy\"} {\"correlation_id\":\"73848601-79bb-4161-91ae-37acf4385d24\",\"trace_id\":\"dba432b8-cb4b-4e76-8684-01d96f91e453\"}\n[2026-04-27 10:31:09] local.INFO: [CrmOwnerResolver] Integration owner matched as CRM Owner {\"crm_provider\":\"salesforce\",\"crm_owner\":143,\"team_id\":1} {\"correlation_id\":\"73848601-79bb-4161-91ae-37acf4385d24\",\"trace_id\":\"dba432b8-cb4b-4e76-8684-01d96f91e453\"}\n[2026-04-27 10:31:09] local.INFO: [SocialAccountService] Fetching token {\"socialAccountId\":1500,\"provider\":\"salesforce\"} {\"correlation_id\":\"73848601-79bb-4161-91ae-37acf4385d24\",\"trace_id\":\"dba432b8-cb4b-4e76-8684-01d96f91e453\"}\n[2026-04-27 10:31:09] local.INFO: [HubSpot Journal Polling] No data {\"correlation_id\":\"1f928e93-b17e-4518-a1b9-a8280d1d95e8\",\"trace_id\":\"ba203ed6-fe98-4fd7-aa62-8de9fb8ffeca\"}\n[2026-04-27 10:31:09] local.INFO: [SocialAccountService] Token retrieved {\"socialAccountId\":1500,\"provider\":\"salesforce\"} {\"correlation_id\":\"73848601-79bb-4161-91ae-37acf4385d24\",\"trace_id\":\"dba432b8-cb4b-4e76-8684-01d96f91e453\"}\n[2026-04-27 10:31:09] local.INFO: [EncryptedTokenManager] Generating access token. {\"mode\":\"legacy\"} {\"correlation_id\":\"73848601-79bb-4161-91ae-37acf4385d24\",\"trace_id\":\"dba432b8-cb4b-4e76-8684-01d96f91e453\"}\n[2026-04-27 10:31:09] local.INFO: [SyncActivity] Start {\"import_id\":811740,\"provider\":\"xant\",\"provider_id\":161,\"team\":\"jiminny\",\"team_id\":1} {\"correlation_id\":\"73848601-79bb-4161-91ae-37acf4385d24\",\"trace_id\":\"dba432b8-cb4b-4e76-8684-01d96f91e453\"}\n[2026-04-27 10:31:09] local.INFO: [Salesforce] Performing query {\"query\":\"\n SELECT Playbooks_Call_Date__c,Playbooks_Call_Recording__c,CreatedDate,TaskSubtype,CallType,CallDurationInSeconds,Id,OwnerId,WhoId,WhatId,Priority,ActivityDate,Subject,Description,Status,Type\n FROM Task\n WHERE IsDeleted = false\n AND LastModifiedDate >= :from\n AND LastModifiedDate <= :to\n ORDER BY LastModifiedDate ASC\n LIMIT :limit\",\"params\":{\"from\":\"2026-04-27T10:14:00Z\",\"to\":\"2026-04-27T10:30:00Z\",\"ownerId\":null,\"subType\":null,\"limit\":5000}} {\"correlation_id\":\"73848601-79bb-4161-91ae-37acf4385d24\",\"trace_id\":\"dba432b8-cb4b-4e76-8684-01d96f91e453\"}\n[2026-04-27 10:31:09] local.INFO: [Salesforce] Sending request {\"endpoint\":\"https://jiminny--stagingenv.sandbox.my.salesforce.com/services/data/v50.0/query/?q=%0A++++++++++++SELECT+Playbooks_Call_Date__c%2CPlaybooks_Call_Recording__c%2CCreatedDate%2CTaskSubtype%2CCallType%2CCallDurationInSeconds%2CId%2COwnerId%2CWhoId%2CWhatId%2CPriority%2CActivityDate%2CSubject%2CDescription%2CStatus%2CType%0A++++++++++++++FROM+Task%0A+++++++++++++WHERE+IsDeleted+%3D+false%0A+++++++++++++++AND+LastModifiedDate+%3E%3D+2026-04-27T10%3A14%3A00Z%0A+++++++++++++++AND+LastModifiedDate+%3C%3D+2026-04-27T10%3A30%3A00Z%0A++++++++++ORDER+BY+LastModifiedDate+ASC%0A+++++++++++++LIMIT+5000 GET\",\"team_id\":1} {\"correlation_id\":\"73848601-79bb-4161-91ae-37acf4385d24\",\"trace_id\":\"dba432b8-cb4b-4e76-8684-01d96f91e453\"}\n[2026-04-27 10:31:09] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage before starting command {\"command\":\"mailbox:batch:fail-stalled\",\"memoryBeforeCommandInMb\":60.0,\"memoryPeakBeforeCommandInMb\":99.727} {\"correlation_id\":\"0d30446d-c469-4ade-80dc-f57f997ea610\",\"trace_id\":\"61eea326-3f40-4a50-a5e6-bddd7fa25d27\"}\n[2026-04-27 10:31:09] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage for command {\"command\":\"mailbox:batch:fail-stalled\",\"memoryBeforeCommandInMb\":60.0,\"memoryAfterCommandInMB\":62.0,\"memoryPeakBeforeCommandInMb\":99.727,\"memoryPeakAfterCommandInMB\":99.727} {\"correlation_id\":\"0d30446d-c469-4ade-80dc-f57f997ea610\",\"trace_id\":\"61eea326-3f40-4a50-a5e6-bddd7fa25d27\"}\n[2026-04-27 10:31:09] local.INFO: [Xant (InsideSales)] No calls found. {\"correlation_id\":\"73848601-79bb-4161-91ae-37acf4385d24\",\"trace_id\":\"dba432b8-cb4b-4e76-8684-01d96f91e453\"}\n[2026-04-27 10:31:09] local.INFO: [SyncActivity] End {\"import_id\":811740,\"provider\":\"xant\",\"provider_id\":161,\"team\":\"jiminny\",\"team_id\":1} {\"correlation_id\":\"73848601-79bb-4161-91ae-37acf4385d24\",\"trace_id\":\"dba432b8-cb4b-4e76-8684-01d96f91e453\"}\n[2026-04-27 10:31:09] local.INFO: [SyncActivity] Memory usage {\"import_id\":811740,\"provider\":\"xant\",\"provider_id\":161,\"team\":\"jiminny\",\"team_id\":1,\"memory_usage\":25149472,\"memory_real_usage\":65011712,\"pid\":13616} {\"correlation_id\":\"73848601-79bb-4161-91ae-37acf4385d24\",\"trace_id\":\"dba432b8-cb4b-4e76-8684-01d96f91e453\"}\n[2026-04-27 10:31:09] local.INFO: [SocialAccountService] Fetching token {\"socialAccountId\":1500,\"provider\":\"salesforce\"} {\"correlation_id\":\"11c977fb-9603-4dde-a4d7-f821d47fcdc9\",\"trace_id\":\"dba432b8-cb4b-4e76-8684-01d96f91e453\"}\n[2026-04-27 10:31:09] local.INFO: [SocialAccountService] Token retrieved {\"socialAccountId\":1500,\"provider\":\"salesforce\"} {\"correlation_id\":\"11c977fb-9603-4dde-a4d7-f821d47fcdc9\",\"trace_id\":\"dba432b8-cb4b-4e76-8684-01d96f91e453\"}\n[2026-04-27 10:31:09] local.INFO: [EncryptedTokenManager] Generating access token. {\"mode\":\"legacy\"} {\"correlation_id\":\"11c977fb-9603-4dde-a4d7-f821d47fcdc9\",\"trace_id\":\"dba432b8-cb4b-4e76-8684-01d96f91e453\"}\n[2026-04-27 10:31:09] local.INFO: [CrmOwnerResolver] Integration owner matched as CRM Owner {\"crm_provider\":\"salesforce\",\"crm_owner\":143,\"team_id\":1} {\"correlation_id\":\"11c977fb-9603-4dde-a4d7-f821d47fcdc9\",\"trace_id\":\"dba432b8-cb4b-4e76-8684-01d96f91e453\"}\n[2026-04-27 10:31:09] local.INFO: [SocialAccountService] Fetching token {\"socialAccountId\":1500,\"provider\":\"salesforce\"} {\"correlation_id\":\"11c977fb-9603-4dde-a4d7-f821d47fcdc9\",\"trace_id\":\"dba432b8-cb4b-4e76-8684-01d96f91e453\"}\n[2026-04-27 10:31:09] local.INFO: [SocialAccountService] Token retrieved {\"socialAccountId\":1500,\"provider\":\"salesforce\"} {\"correlation_id\":\"11c977fb-9603-4dde-a4d7-f821d47fcdc9\",\"trace_id\":\"dba432b8-cb4b-4e76-8684-01d96f91e453\"}\n[2026-04-27 10:31:09] local.INFO: [EncryptedTokenManager] Generating access token. {\"mode\":\"legacy\"} {\"correlation_id\":\"11c977fb-9603-4dde-a4d7-f821d47fcdc9\",\"trace_id\":\"dba432b8-cb4b-4e76-8684-01d96f91e453\"}\n[2026-04-27 10:31:09] local.INFO: [SyncActivity] Start {\"import_id\":811741,\"provider\":\"apollo\",\"provider_id\":441,\"team\":\"jiminny\",\"team_id\":1} {\"correlation_id\":\"11c977fb-9603-4dde-a4d7-f821d47fcdc9\",\"trace_id\":\"dba432b8-cb4b-4e76-8684-01d96f91e453\"}\n[2026-04-27 10:31:09] local.INFO: [Salesforce] Performing query {\"query\":\"\n SELECT AccountId,CreatedDate,TaskSubtype,CallType,Id,OwnerId,WhoId,WhatId,Priority,ActivityDate,Subject,Description,Status,Type\n FROM Task\n WHERE IsDeleted = false\n AND LastModifiedDate >= :from\n AND LastModifiedDate <= :to\n ORDER BY LastModifiedDate ASC\n LIMIT :limit\",\"params\":{\"from\":\"2026-04-27T10:14:00Z\",\"to\":\"2026-04-27T10:30:00Z\",\"ownerId\":null,\"subType\":null,\"limit\":5000}} {\"correlation_id\":\"11c977fb-9603-4dde-a4d7-f821d47fcdc9\",\"trace_id\":\"dba432b8-cb4b-4e76-8684-01d96f91e453\"}\n[2026-04-27 10:31:09] local.INFO: [Salesforce] Sending request {\"endpoint\":\"https://jiminny--stagingenv.sandbox.my.salesforce.com/services/data/v50.0/query/?q=%0A++++++++++++SELECT+AccountId%2CCreatedDate%2CTaskSubtype%2CCallType%2CId%2COwnerId%2CWhoId%2CWhatId%2CPriority%2CActivityDate%2CSubject%2CDescription%2CStatus%2CType%0A++++++++++++++FROM+Task%0A+++++++++++++WHERE+IsDeleted+%3D+false%0A+++++++++++++++AND+LastModifiedDate+%3E%3D+2026-04-27T10%3A14%3A00Z%0A+++++++++++++++AND+LastModifiedDate+%3C%3D+2026-04-27T10%3A30%3A00Z%0A++++++++++ORDER+BY+LastModifiedDate+ASC%0A+++++++++++++LIMIT+5000 GET\",\"team_id\":1} {\"correlation_id\":\"11c977fb-9603-4dde-a4d7-f821d47fcdc9\",\"trace_id\":\"dba432b8-cb4b-4e76-8684-01d96f91e453\"}\n[2026-04-27 10:31:10] local.INFO: [Apollo] No calls found. {\"correlation_id\":\"11c977fb-9603-4dde-a4d7-f821d47fcdc9\",\"trace_id\":\"dba432b8-cb4b-4e76-8684-01d96f91e453\"}\n[2026-04-27 10:31:10] local.INFO: [SyncActivity] End {\"import_id\":811741,\"provider\":\"apollo\",\"provider_id\":441,\"team\":\"jiminny\",\"team_id\":1} {\"correlation_id\":\"11c977fb-9603-4dde-a4d7-f821d47fcdc9\",\"trace_id\":\"dba432b8-cb4b-4e76-8684-01d96f91e453\"}\n[2026-04-27 10:31:10] local.INFO: [SyncActivity] Memory usage {\"import_id\":811741,\"provider\":\"apollo\",\"provider_id\":441,\"team\":\"jiminny\",\"team_id\":1,\"memory_usage\":25527752,\"memory_real_usage\":65011712,\"pid\":13616} {\"correlation_id\":\"11c977fb-9603-4dde-a4d7-f821d47fcdc9\",\"trace_id\":\"dba432b8-cb4b-4e76-8684-01d96f91e453\"}\n[2026-04-27 10:31:10] local.INFO: [SocialAccountService] Fetching token {\"socialAccountId\":1500,\"provider\":\"salesforce\"} {\"correlation_id\":\"856f8092-8c5e-409d-8aa6-ffb8efd8bfa1\",\"trace_id\":\"dba432b8-cb4b-4e76-8684-01d96f91e453\"}\n[2026-04-27 10:31:10] local.INFO: [SocialAccountService] Token retrieved {\"socialAccountId\":1500,\"provider\":\"salesforce\"} {\"correlation_id\":\"856f8092-8c5e-409d-8aa6-ffb8efd8bfa1\",\"trace_id\":\"dba432b8-cb4b-4e76-8684-01d96f91e453\"}\n[2026-04-27 10:31:10] local.INFO: [EncryptedTokenManager] Generating access token. {\"mode\":\"legacy\"} {\"correlation_id\":\"856f8092-8c5e-409d-8aa6-ffb8efd8bfa1\",\"trace_id\":\"dba432b8-cb4b-4e76-8684-01d96f91e453\"}\n[2026-04-27 10:31:10] local.INFO: [CrmOwnerResolver] Integration owner matched as CRM Owner {\"crm_provider\":\"salesforce\",\"crm_owner\":143,\"team_id\":1} {\"correlation_id\":\"856f8092-8c5e-409d-8aa6-ffb8efd8bfa1\",\"trace_id\":\"dba432b8-cb4b-4e76-8684-01d96f91e453\"}\n[2026-04-27 10:31:10] local.INFO: [SocialAccountService] Fetching token {\"socialAccountId\":1500,\"provider\":\"salesforce\"} {\"correlation_id\":\"856f8092-8c5e-409d-8aa6-ffb8efd8bfa1\",\"trace_id\":\"dba432b8-cb4b-4e76-8684-01d96f91e453\"}\n[2026-04-27 10:31:10] local.INFO: [SocialAccountService] Token retrieved {\"socialAccountId\":1500,\"provider\":\"salesforce\"} {\"correlation_id\":\"856f8092-8c5e-409d-8aa6-ffb8efd8bfa1\",\"trace_id\":\"dba432b8-cb4b-4e76-8684-01d96f91e453\"}\n[2026-04-27 10:31:10] local.INFO: [EncryptedTokenManager] Generating access token. {\"mode\":\"legacy\"} {\"correlation_id\":\"856f8092-8c5e-409d-8aa6-ffb8efd8bfa1\",\"trace_id\":\"dba432b8-cb4b-4e76-8684-01d96f91e453\"}\n[2026-04-27 10:31:10] local.INFO: [SyncActivity] Start {\"import_id\":811742,\"provider\":\"groove\",\"provider_id\":228,\"team\":\"jiminny\",\"team_id\":1} {\"correlation_id\":\"856f8092-8c5e-409d-8aa6-ffb8efd8bfa1\",\"trace_id\":\"dba432b8-cb4b-4e76-8684-01d96f91e453\"}\n[2026-04-27 10:31:10] local.INFO: [Salesforce] Performing query {\"query\":\"\n SELECT call_recording_url__c,TaskSubtype,CreatedDate,CallType,CallDurationInSeconds,Id,OwnerId,WhoId,WhatId,Priority,ActivityDate,Subject,Description,Status,Type\n FROM Task\n WHERE IsDeleted = false\n AND LastModifiedDate >= :from\n AND LastModifiedDate <= :to\n ORDER BY LastModifiedDate ASC\n LIMIT :limit\",\"params\":{\"from\":\"2026-04-27T10:14:00Z\",\"to\":\"2026-04-27T10:30:00Z\",\"ownerId\":null,\"subType\":null,\"limit\":5000}} {\"correlation_id\":\"856f8092-8c5e-409d-8aa6-ffb8efd8bfa1\",\"trace_id\":\"dba432b8-cb4b-4e76-8684-01d96f91e453\"}\n[2026-04-27 10:31:10] local.INFO: [Salesforce] Sending request {\"endpoint\":\"https://jiminny--stagingenv.sandbox.my.salesforce.com/services/data/v50.0/query/?q=%0A++++++++++++SELECT+call_recording_url__c%2CTaskSubtype%2CCreatedDate%2CCallType%2CCallDurationInSeconds%2CId%2COwnerId%2CWhoId%2CWhatId%2CPriority%2CActivityDate%2CSubject%2CDescription%2CStatus%2CType%0A++++++++++++++FROM+Task%0A+++++++++++++WHERE+IsDeleted+%3D+false%0A+++++++++++++++AND+LastModifiedDate+%3E%3D+2026-04-27T10%3A14%3A00Z%0A+++++++++++++++AND+LastModifiedDate+%3C%3D+2026-04-27T10%3A30%3A00Z%0A++++++++++ORDER+BY+LastModifiedDate+ASC%0A+++++++++++++LIMIT+5000 GET\",\"team_id\":1} {\"correlation_id\":\"856f8092-8c5e-409d-8aa6-ffb8efd8bfa1\",\"trace_id\":\"dba432b8-cb4b-4e76-8684-01d96f91e453\"}\n[2026-04-27 10:31:10] local.ERROR: [Salesforce] Request exception [400] \nSELECT call_recording_url__c,TaskSubtype\n ^\nERROR at Row:1:Column:8\nNo such column 'call_recording_url__c' on entity 'Task'. If you are attempting to use a custom field, be sure to append the '__c' after the custom field name. Please reference your WSDL or the describe call for the appropriate names. {\"url\":\"https://jiminny--stagingenv.sandbox.my.salesforce.com/services/data/v50.0/query/?q=%0A++++++++++++SELECT+call_recording_url__c%2CTaskSubtype%2CCreatedDate%2CCallType%2CCallDurationInSeconds%2CId%2COwnerId%2CWhoId%2CWhatId%2CPriority%2CActivityDate%2CSubject%2CDescription%2CStatus%2CType%0A++++++++++++++FROM+Task%0A+++++++++++++WHERE+IsDeleted+%3D+false%0A+++++++++++++++AND+LastModifiedDate+%3E%3D+2026-04-27T10%3A14%3A00Z%0A+++++++++++++++AND+LastModifiedDate+%3C%3D+2026-04-27T10%3A30%3A00Z%0A++++++++++ORDER+BY+LastModifiedDate+ASC%0A+++++++++++++LIMIT+5000\",\"data\":{\"headers\":{\"Authorization\":\"Bearer 00D2g0000008hH4!AQEAQC41CLa_xP1bAbi9E2OUbAOpW..Wx961OtqTwJ3oWpOKs6yPdm47B4yKRBHbRmmJ.Lvd34sjr0gnAkV8fLb8IZWzIW93\"}},\"response\":{\"GuzzleHttp\\\\Psr7\\\\Stream\":\"[{\\\"message\\\":\\\"\\\\nSELECT call_recording_url__c,TaskSubtype\\\\n ^\\\\nERROR at Row:1:Column:8\\\\nNo such column 'call_recording_url__c' on entity 'Task'. If you are attempting to use a custom field, be sure to append the '__c' after the custom field name. Please reference your WSDL or the describe call for the appropriate names.\\\",\\\"errorCode\\\":\\\"INVALID_FIELD\\\"}]\"},\"fields\":[]} {\"correlation_id\":\"856f8092-8c5e-409d-8aa6-ffb8efd8bfa1\",\"trace_id\":\"dba432b8-cb4b-4e76-8684-01d96f91e453\"}\n[2026-04-27 10:31:10] local.ALERT: [SyncActivity] Failed {\"import_id\":811742,\"provider\":\"groove\",\"provider_id\":228,\"team\":\"jiminny\",\"team_id\":1,\"reason\":\"\nSELECT call_recording_url__c,TaskSubtype\n ^\nERROR at Row:1:Column:8\nNo such column 'call_recording_url__c' on entity 'Task'. If you are attempting to use a custom field, be sure to append the '__c' after the custom field name. Please reference your WSDL or the describe call for the appropriate names.\",\"file\":\"/home/jiminny/app/Services/Crm/Salesforce/Client.php\",\"line\":564} {\"correlation_id\":\"856f8092-8c5e-409d-8aa6-ffb8efd8bfa1\",\"trace_id\":\"dba432b8-cb4b-4e76-8684-01d96f91e453\"}\n[2026-04-27 10:31:10] local.INFO: [SocialAccountService] Fetching token {\"socialAccountId\":1500,\"provider\":\"salesforce\"} {\"correlation_id\":\"0ec6aef6-345c-4602-b6a1-646b95e481c0\",\"trace_id\":\"dba432b8-cb4b-4e76-8684-01d96f91e453\"}\n[2026-04-27 10:31:10] local.INFO: [SocialAccountService] Token retrieved {\"socialAccountId\":1500,\"provider\":\"salesforce\"} {\"correlation_id\":\"0ec6aef6-345c-4602-b6a1-646b95e481c0\",\"trace_id\":\"dba432b8-cb4b-4e76-8684-01d96f91e453\"}\n[2026-04-27 10:31:10] local.INFO: [EncryptedTokenManager] Generating access token. {\"mode\":\"legacy\"} {\"correlation_id\":\"0ec6aef6-345c-4602-b6a1-646b95e481c0\",\"trace_id\":\"dba432b8-cb4b-4e76-8684-01d96f91e453\"}\n[2026-04-27 10:31:10] local.INFO: [CrmOwnerResolver] Integration owner matched as CRM Owner {\"crm_provider\":\"salesforce\",\"crm_owner\":143,\"team_id\":1} {\"correlation_id\":\"0ec6aef6-345c-4602-b6a1-646b95e481c0\",\"trace_id\":\"dba432b8-cb4b-4e76-8684-01d96f91e453\"}\n[2026-04-27 10:31:10] local.INFO: [SocialAccountService] Fetching token {\"socialAccountId\":1500,\"provider\":\"salesforce\"} {\"correlation_id\":\"0ec6aef6-345c-4602-b6a1-646b95e481c0\",\"trace_id\":\"dba432b8-cb4b-4e76-8684-01d96f91e453\"}\n[2026-04-27 10:31:10] local.INFO: [SocialAccountService] Token retrieved {\"socialAccountId\":1500,\"provider\":\"salesforce\"} {\"correlation_id\":\"0ec6aef6-345c-4602-b6a1-646b95e481c0\",\"trace_id\":\"dba432b8-cb4b-4e76-8684-01d96f91e453\"}\n[2026-04-27 10:31:10] local.INFO: [EncryptedTokenManager] Generating access token. {\"mode\":\"legacy\"} {\"correlation_id\":\"0ec6aef6-345c-4602-b6a1-646b95e481c0\",\"trace_id\":\"dba432b8-cb4b-4e76-8684-01d96f91e453\"}\n[2026-04-27 10:31:10] local.INFO: [SyncActivity] Start {\"import_id\":811743,\"provider\":\"twilio-video\",\"provider_id\":243,\"team\":\"jiminny\",\"team_id\":1} {\"correlation_id\":\"0ec6aef6-345c-4602-b6a1-646b95e481c0\",\"trace_id\":\"dba432b8-cb4b-4e76-8684-01d96f91e453\"}\n[2026-04-27 10:31:10] local.INFO: [Salesforce] Performing query {\"query\":\"SELECT Id,OwnerId,WhoId,WhatId,Priority,ActivityDate,Subject,Description,Status,Type,twilio_call_sid__c,Lead_UUID__c,Opportunity__c\n FROM Task\n WHERE Type = 'Video'\n AND isClosed = true\n AND IsDeleted = false\n AND LastModifiedDate >= :from\n AND twilio_call_sid__c != NULL AND LastModifiedDate <= :to ORDER BY LastModifiedDate ASC\n LIMIT :limit\",\"params\":{\"from\":\"2026-04-27T10:14:00Z\",\"to\":\"2026-04-27T10:30:00Z\",\"ownerId\":null,\"subType\":null,\"limit\":5000}} {\"correlation_id\":\"0ec6aef6-345c-4602-b6a1-646b95e481c0\",\"trace_id\":\"dba432b8-cb4b-4e76-8684-01d96f91e453\"}\n[2026-04-27 10:31:10] local.INFO: [Salesforce] Sending request {\"endpoint\":\"https://jiminny--stagingenv.sandbox.my.salesforce.com/services/data/v50.0/query/?q=SELECT+Id%2COwnerId%2CWhoId%2CWhatId%2CPriority%2CActivityDate%2CSubject%2CDescription%2CStatus%2CType%2Ctwilio_call_sid__c%2CLead_UUID__c%2COpportunity__c%0A++++++++++++++FROM+Task%0A++++++++++++WHERE+Type+%3D+%27Video%27%0A++++++++++++++AND+isClosed+%3D+true%0A++++++++++++++AND+IsDeleted+%3D+false%0A++++++++++++++AND+LastModifiedDate+%3E%3D+2026-04-27T10%3A14%3A00Z%0A++++++++++++++AND+twilio_call_sid__c+%21%3D+NULL+AND+LastModifiedDate+%3C%3D+2026-04-27T10%3A30%3A00Z+ORDER+BY+LastModifiedDate+ASC%0A+++++++++++++LIMIT+5000 GET\",\"team_id\":1} {\"correlation_id\":\"0ec6aef6-345c-4602-b6a1-646b95e481c0\",\"trace_id\":\"dba432b8-cb4b-4e76-8684-01d96f91e453\"}\n[2026-04-27 10:31:11] local.INFO: [Twilio Video] No calls found. {\"correlation_id\":\"0ec6aef6-345c-4602-b6a1-646b95e481c0\",\"trace_id\":\"dba432b8-cb4b-4e76-8684-01d96f91e453\"}\n[2026-04-27 10:31:11] local.INFO: [SyncActivity] End {\"import_id\":811743,\"provider\":\"twilio-video\",\"provider_id\":243,\"team\":\"jiminny\",\"team_id\":1} {\"correlation_id\":\"0ec6aef6-345c-4602-b6a1-646b95e481c0\",\"trace_id\":\"dba432b8-cb4b-4e76-8684-01d96f91e453\"}\n[2026-04-27 10:31:11] local.INFO: [SyncActivity] Memory usage {\"import_id\":811743,\"provider\":\"twilio-video\",\"provider_id\":243,\"team\":\"jiminny\",\"team_id\":1,\"memory_usage\":25679680,\"memory_real_usage\":65011712,\"pid\":13616} {\"correlation_id\":\"0ec6aef6-345c-4602-b6a1-646b95e481c0\",\"trace_id\":\"dba432b8-cb4b-4e76-8684-01d96f91e453\"}\n[2026-04-27 10:31:11] local.INFO: [SocialAccountService] Fetching token {\"socialAccountId\":1499,\"provider\":\"hubspot\"} {\"correlation_id\":\"3b9d7e85-b8c2-4ae2-863e-c343c277afdc\",\"trace_id\":\"dba432b8-cb4b-4e76-8684-01d96f91e453\"}\n[2026-04-27 10:31:11] local.INFO: [SocialAccountService] Token retrieved {\"socialAccountId\":1499,\"provider\":\"hubspot\"} {\"correlation_id\":\"3b9d7e85-b8c2-4ae2-863e-c343c277afdc\",\"trace_id\":\"dba432b8-cb4b-4e76-8684-01d96f91e453\"}\n[2026-04-27 10:31:11] local.INFO: [EncryptedTokenManager] Generating access token. {\"mode\":\"legacy\"} {\"correlation_id\":\"3b9d7e85-b8c2-4ae2-863e-c343c277afdc\",\"trace_id\":\"dba432b8-cb4b-4e76-8684-01d96f91e453\"}\n[2026-04-27 10:31:11] local.INFO: [CrmOwnerResolver] Integration owner matched as CRM Owner {\"crm_provider\":\"hubspot\",\"crm_owner\":89,\"team_id\":2} {\"correlation_id\":\"3b9d7e85-b8c2-4ae2-863e-c343c277afdc\",\"trace_id\":\"dba432b8-cb4b-4e76-8684-01d96f91e453\"}\n[2026-04-27 10:31:11] local.INFO: [SocialAccountService] Fetching token {\"socialAccountId\":408,\"provider\":\"hubspot\"} {\"correlation_id\":\"3b9d7e85-b8c2-4ae2-863e-c343c277afdc\",\"trace_id\":\"dba432b8-cb4b-4e76-8684-01d96f91e453\"}\n[2026-04-27 10:31:11] local.INFO: [SocialAccountService] Token needs refreshing {\"socialAccountId\":408,\"provider\":\"hubspot\"} {\"correlation_id\":\"3b9d7e85-b8c2-4ae2-863e-c343c277afdc\",\"trace_id\":\"dba432b8-cb4b-4e76-8684-01d96f91e453\"}\n[2026-04-27 10:31:11] local.INFO: [EncryptedTokenManager] Generating access token. {\"mode\":\"legacy\"} {\"correlation_id\":\"3b9d7e85-b8c2-4ae2-863e-c343c277afdc\",\"trace_id\":\"dba432b8-cb4b-4e76-8684-01d96f91e453\"}\n[2026-04-27 10:31:11] local.INFO: [SocialAccountService] Refreshing token from provider {\"socialAccountId\":408,\"provider\":\"hubspot\",\"refreshToken\":\"de4e47eb985578f4218833e763e31059e88b562e87e10749b3389be2328f0aa7\",\"state\":\"connected\"} {\"correlation_id\":\"3b9d7e85-b8c2-4ae2-863e-c343c277afdc\",\"trace_id\":\"dba432b8-cb4b-4e76-8684-01d96f91e453\"}\n[2026-04-27 10:31:11] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage before starting command {\"command\":\"crm:bullhorn:ping\",\"memoryBeforeCommandInMb\":60.0,\"memoryPeakBeforeCommandInMb\":99.727} {\"correlation_id\":\"a2085168-092a-44b0-8dd2-c8845bc36f86\",\"trace_id\":\"9445425b-6aaf-4a23-9323-ecf85939f967\"}\n[2026-04-27 10:31:11] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage for command {\"command\":\"crm:bullhorn:ping\",\"memoryBeforeCommandInMb\":60.0,\"memoryAfterCommandInMB\":62.0,\"memoryPeakBeforeCommandInMb\":99.727,\"memoryPeakAfterCommandInMB\":99.727} {\"correlation_id\":\"a2085168-092a-44b0-8dd2-c8845bc36f86\",\"trace_id\":\"9445425b-6aaf-4a23-9323-ecf85939f967\"}\n[2026-04-27 10:31:12] local.INFO: [SocialAccountObserver] Saving model {\"correlation_id\":\"3b9d7e85-b8c2-4ae2-863e-c343c277afdc\",\"trace_id\":\"dba432b8-cb4b-4e76-8684-01d96f91e453\"}","role_description":"text entry area","is_enabled":true,"is_focused":true,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Sync Changes","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.008643617,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Hide This Notification","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.008643617,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Code changed:","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.042220745,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Hide","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.008643617,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"2","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.007978723,"height":0.0},"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"60","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.010305851,"height":0.0},"on_screen":false,"role_description":"text"},{"role":"AXButton","text":"Previous Highlighted Error","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.00731383,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Next Highlighted Error","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.006981383,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXTextArea","text":"<?php\n\ndeclare(strict_types=1);\n\nnamespace Jiminny\\Services\\Crm\\Hubspot;\n\nuse HubSpot\\Client\\Crm\\Deals\\ApiException as DealApiException;\nuse HubSpot\\Client\\Crm\\Contacts\\ApiException as ContactApiException;\nuse HubSpot\\Client\\Crm\\Companies\\ApiException as CompanyApiException;\nuse HubSpot\\Client\\Crm\\Contacts\\Model\\SimplePublicObjectWithAssociations as ContactsWithAssociations;\nuse HubSpot\\Client\\Crm\\Companies\\Model\\SimplePublicObjectWithAssociations as CompaniesWithAssociations;\nuse HubSpot\\Client\\Crm\\Deals\\Model\\SimplePublicObjectWithAssociations as DealWithAssociations;\nuse HubSpot\\Client\\Crm\\Objects\\Model\\SimplePublicObjectInput;\nuse HubSpot\\Client\\Crm\\Objects\\Model\\SimplePublicObjectWithAssociations as ObjectWithAssociations;\nuse HubSpot\\Client\\Crm\\Pipelines\\Model\\Error;\nuse HubSpot\\Client\\Crm\\Pipelines\\Model\\PipelineStage;\nuse HubSpot\\Client\\Crm\\Properties\\Model\\Property;\nuse HubSpot\\Discovery\\Discovery;\nuse Jiminny\\Component\\Utility\\Service\\ProviderRateLimiter;\nuse Jiminny\\Exceptions\\CrmException;\nuse Jiminny\\Exceptions\\RateLimitException;\nuse Jiminny\\Exceptions\\SocialAccountTokenInvalidException;\nuse Jiminny\\Jobs\\Crm\\NoteObject;\nuse Jiminny\\Models\\Crm\\Field;\nuse Jiminny\\Services\\Crm\\BaseClient;\nuse Jiminny\\Services\\Crm\\Hubspot\\DTO\\Response\\Owner;\nuse Jiminny\\Services\\SocialAccountService;\nuse SevenShores\\Hubspot\\Exceptions\\BadRequest;\nuse SevenShores\\Hubspot\\Exceptions\\HubspotException;\nuse SevenShores\\Hubspot\\Factory;\nuse SevenShores\\Hubspot\\Http\\Response;\nuse Jiminny\\Services\\Crm\\Hubspot\\Pagination\\HubspotPaginationService;\nuse Throwable;\n\n/**\n * @phpstan-type CrmFieldOption array{id:string, label:string, value?:string}\n */\nclass Client extends BaseClient implements HubspotClientInterface\n{\n public const string MIN_API_VERSION = '2';\n\n public const string BASE_URL = 'https://api.hubapi.com';\n\n public const int ASSOCIATIONS_BATCH_SIZE_LIMIT = 1000;\n\n private HubspotPaginationService $paginationService;\n private HubspotTokenManager $tokenManager;\n private ProviderRateLimiter $rateLimiter;\n\n public function __construct(\n SocialAccountService $socialAccountService,\n HubspotPaginationService $paginationService,\n HubspotTokenManager $tokenManager,\n ProviderRateLimiter $rateLimiter,\n ) {\n parent::__construct($socialAccountService);\n $this->paginationService = $paginationService;\n $this->tokenManager = $tokenManager;\n $this->rateLimiter = $rateLimiter;\n\n $this->setBaseUrl(self::BASE_URL);\n $this->setVersion(self::MIN_API_VERSION);\n }\n\n /**\n * Single entry point for every HubSpot API call. Enforces the per-portal\n * rate limit configured in the rate_limits table (morphed to the current\n * Configuration) and reacts to a real 429 from HubSpot by translating it\n * into a RateLimitException carrying Retry-After.\n *\n * Wrap any outbound HubSpot call (SDK or raw HTTP) like:\n *\n * $this->executeRequest(fn () => $this->getNewInstance()->crm()->...);\n *\n * @template T\n * @param callable(): T $apiCall\n * @return T\n *\n * @throws RateLimitException\n */\n private function executeRequest(callable $apiCall)\n {\n if (! $this->rateLimiter->canMakeRequest($this->config)) {\n $retryAfter = $this->rateLimiter->requestAvailableIn($this->config);\n\n $this->log->warning('[Hubspot] Rate limit exceeded, deferring request', [\n 'team_id' => $this->config->team_id,\n 'config_id' => $this->config->getId(),\n 'retry_after' => $retryAfter,\n ]);\n\n throw new RateLimitException(\n 'Hubspot rate limit reached for configuration ' . $this->config->getId(),\n $retryAfter,\n );\n }\n\n $this->rateLimiter->incrementRequestCount($this->config);\n\n try {\n return $apiCall();\n } catch (Throwable $e) {\n if ($this->isHubspotRateLimit($e)) {\n $retryAfter = $this->parseRetryAfter($e);\n\n $this->log->warning('[Hubspot] Received 429 from API', [\n 'team_id' => $this->config->team_id,\n 'config_id' => $this->config->getId(),\n 'retry_after' => $retryAfter,\n 'reason' => $e->getMessage(),\n ]);\n\n throw new RateLimitException('Hubspot returned 429', $retryAfter, $e);\n }\n\n throw $e;\n }\n }\n\n private function isHubspotRateLimit(Throwable $e): bool\n {\n return method_exists($e, 'getCode') && (int) $e->getCode() === 429;\n }\n\n private function parseRetryAfter(Throwable $e): int\n {\n if (method_exists($e, 'getResponseHeaders')) {\n $headers = $e->getResponseHeaders() ?: [];\n $value = $headers['Retry-After'] ?? $headers['retry-after'] ?? null;\n if (is_array($value)) {\n $value = $value[0] ?? null;\n }\n if (is_numeric($value)) {\n return (int) $value;\n }\n }\n\n return 10;\n }\n\n public function getMinimumApiVersion(): string\n {\n return self::MIN_API_VERSION;\n }\n\n public function getInstance(): Factory\n {\n return new Factory([\n 'key' => $this->accessToken,\n 'oauth2' => true,\n 'base_url' => $this->baseUrl,\n ]);\n }\n\n public function getNewInstance(): Discovery\n {\n return \\HubSpot\\Factory::createWithAccessToken($this->accessToken);\n }\n\n /**\n * Secondly and daily limits for Hubspot API\n *\n * Product Tier: Free & Starter | Professional & Enterprise | API add-on (any tier)\n * Burst: 100/10 seconds | 150/10 seconds | 200/10 seconds\n * Daily: 250,000 | 500,000 | 1,000,000\n *\n * Official documentation states: The search endpoints are rate limited to five requests per second.\n * Since with 5 RPS were still hitting secondly rate limits we lowered it to 4\n */\n public function getPaginatedData(array $payload, string $type, int $offset = 0): array\n {\n $total = 0;\n $lastId = null;\n $rows = [];\n foreach ($this->getPaginatedDataGenerator($payload, $type, $offset, $total, $lastId) as $row) {\n $rows[] = $row;\n }\n\n return ['results' => $rows, 'total' => $total, 'last_record' => $lastId];\n }\n\n /**\n * @throws HubspotException\n * @throws SocialAccountTokenInvalidException\n * @throws BadRequest\n */\n public function getPaginatedDataGenerator(\n array $payload,\n string $type,\n int $offset = 0,\n int &$total = 0,\n ?string &$lastRecordId = null\n ): \\Generator {\n return $this->paginationService->getPaginatedDataGenerator(\n $this,\n $payload,\n $type,\n $offset,\n $total,\n $lastRecordId\n );\n }\n\n /**\n * @throws DealApiException\n * @throws CrmException\n */\n public function getOpportunityById(string $crmId, array $fields): array\n {\n try {\n $deal = $this->executeRequest(fn () => $this->getNewInstance()->crm()->deals()->basicApi()->getById(\n $crmId,\n implode(',', $fields),\n 'companies,contacts'\n ));\n } catch (DealApiException $e) {\n $this->log->info('[Hubspot] Failed to fetch opportunity', [\n 'crm_id' => $crmId,\n 'reason' => $e->getMessage(),\n ]);\n\n throw $e;\n }\n\n if (! $deal instanceof DealWithAssociations) {\n throw new CrmException('Deal not found');\n }\n\n return [\n 'id' => $deal->getId(),\n 'properties' => $deal->getProperties(),\n 'associations' => $deal->getAssociations(),\n ];\n }\n\n /**\n * Generic batch read method for HubSpot objects\n *\n * @param string $objectType The object type ('deals', 'companies', 'contacts')\n * @param array<string> $crmIds Array of HubSpot object IDs (max 100)\n * @param array<string> $fields Array of property names to fetch\n *\n * @return array<string, array> Array keyed by CRM ID with object data\n */\n private function batchReadObjects(string $objectType, array $crmIds, array $fields): array\n {\n if (empty($crmIds)) {\n return [];\n }\n\n $this->validateBatchSize($objectType, $crmIds);\n $this->ensureValidToken();\n\n try {\n $batchConfig = $this->createBatchConfiguration($objectType);\n $batchReadRequest = $this->prepareBatchRequest($batchConfig, $crmIds, $fields);\n $response = $batchConfig['api']->read($batchReadRequest);\n\n $this->validateApiResponse($response, $objectType);\n\n $results = $this->processApiResults($response);\n $this->logBatchResults($objectType, $crmIds, $results);\n\n return $results;\n } catch (\\Throwable $e) {\n $this->handleBatchError($e, $objectType, $crmIds);\n }\n }\n\n private function validateBatchSize(string $objectType, array $crmIds): void\n {\n if (count($crmIds) > 100) {\n throw new \\InvalidArgumentException(\"Batch size cannot exceed 100 {$objectType}\");\n }\n }\n\n private function createBatchConfiguration(string $objectType): array\n {\n $configurations = [\n 'deals' => [\n 'batchReadRequest' => new \\HubSpot\\Client\\Crm\\Deals\\Model\\BatchReadInputSimplePublicObjectId(),\n 'inputClass' => \\HubSpot\\Client\\Crm\\Deals\\Model\\SimplePublicObjectId::class,\n 'api' => $this->getNewInstance()->crm()->deals()->batchApi(),\n ],\n 'companies' => [\n 'batchReadRequest' => new \\HubSpot\\Client\\Crm\\Companies\\Model\\BatchReadInputSimplePublicObjectId(),\n 'inputClass' => \\HubSpot\\Client\\Crm\\Companies\\Model\\SimplePublicObjectId::class,\n 'api' => $this->getNewInstance()->crm()->companies()->batchApi(),\n ],\n 'contacts' => [\n 'batchReadRequest' => new \\HubSpot\\Client\\Crm\\Contacts\\Model\\BatchReadInputSimplePublicObjectId(),\n 'inputClass' => \\HubSpot\\Client\\Crm\\Contacts\\Model\\SimplePublicObjectId::class,\n 'api' => $this->getNewInstance()->crm()->contacts()->batchApi(),\n ],\n ];\n\n if (! isset($configurations[$objectType])) {\n throw new \\InvalidArgumentException(\"Unsupported object type: {$objectType}\");\n }\n\n return $configurations[$objectType];\n }\n\n private function prepareBatchRequest(array $batchConfig, array $crmIds, array $fields): object\n {\n $batchReadRequest = $batchConfig['batchReadRequest'];\n $inputClass = $batchConfig['inputClass'];\n\n $inputs = array_map(function ($crmId) use ($inputClass) {\n $input = new $inputClass();\n $input->setId($crmId);\n\n return $input;\n }, $crmIds);\n\n $batchReadRequest->setInputs($inputs);\n $batchReadRequest->setProperties($fields);\n\n return $batchReadRequest;\n }\n\n private function validateApiResponse($response, string $objectType): void\n {\n if (! $response) {\n throw new CrmException(\"HubSpot API returned null response for {$objectType} batch read\");\n }\n }\n\n private function processApiResults($response): array\n {\n $results = [];\n $responseResults = $response->getResults();\n\n if ($responseResults) {\n foreach ($responseResults as $object) {\n if ($object && $object->getId()) {\n $results[$object->getId()] = [\n 'id' => $object->getId(),\n 'properties' => $object->getProperties() ?: [],\n ];\n }\n }\n }\n\n return $results;\n }\n\n private function logBatchResults(string $objectType, array $crmIds, array $results): void\n {\n $this->log->info(\"[HubSpot] Batch fetched {$objectType}\", [\n 'requested_count' => count($crmIds),\n 'returned_count' => count($results),\n 'crm_ids' => $crmIds,\n ]);\n }\n\n private function handleBatchError(\\Throwable $e, string $objectType, array $crmIds): void\n {\n $errorMessage = $e->getMessage() ?: 'Unknown error';\n $errorTrace = $e->getTraceAsString() ?: 'No trace available';\n\n $this->log->error(\"[HubSpot] Failed to batch fetch {$objectType}\", [\n 'crm_ids' => $crmIds,\n 'error' => $errorMessage,\n 'trace' => $errorTrace,\n ]);\n\n throw new CrmException(\"Failed to batch fetch {$objectType}: \" . $errorMessage);\n }\n\n /**\n * Batch read multiple opportunities by their CRM IDs\n *\n * @param array<string> $crmIds Array of HubSpot deal IDs (max 100)\n * @param array<string> $fields Array of property names to fetch\n *\n * @return array<string, array> Array keyed by CRM ID with opportunity data\n */\n public function getOpportunitiesByIds(array $crmIds, array $fields): array\n {\n return $this->batchReadObjects('deals', $crmIds, $fields);\n }\n\n /**\n * Batch read multiple companies by their CRM IDs\n *\n * @param array<string> $crmIds Array of HubSpot company IDs (max 100)\n * @param array<string> $fields Array of property names to fetch\n *\n * @return array<string, array> Array keyed by CRM ID with company data\n */\n public function getCompaniesByIds(array $crmIds, array $fields): array\n {\n return $this->batchReadObjects('companies', $crmIds, $fields);\n }\n\n /**\n * Batch read multiple contacts by their CRM IDs\n *\n * @param array<string> $crmIds Array of HubSpot contact IDs (max 100)\n * @param array<string> $fields Array of property names to fetch\n *\n * @return array<string, array> Array keyed by CRM ID with contact data\n */\n public function getContactsByIds(array $crmIds, array $fields): array\n {\n return $this->batchReadObjects('contacts', $crmIds, $fields);\n }\n\n /**\n * @throws CompanyApiException\n * @throws CrmException\n */\n public function getAccountById(string $crmId, array $fields): array\n {\n try {\n $company = $this->getNewInstance()->crm()->companies()->basicApi()->getById(\n $crmId,\n implode(',', $fields),\n );\n } catch (CompanyApiException $e) {\n $this->log->info('[Hubspot] Failed to fetch account', [\n 'crm_id' => $crmId,\n 'reason' => $e->getMessage(),\n ]);\n\n throw $e;\n }\n\n if (! $company instanceof CompaniesWithAssociations) {\n throw new CrmException('Account not found');\n }\n\n return [\n 'id' => $company->getId(),\n 'properties' => $company->getProperties(),\n ];\n }\n\n /**\n * @throws ContactApiException\n * @throws CrmException\n */\n public function getContactById(string $crmId, array $fields): array\n {\n try {\n $contact = $this->getNewInstance()->crm()->contacts()->basicApi()->getById(\n $crmId,\n implode(',', $fields)\n );\n } catch (ContactApiException $e) {\n $this->log->info('[Hubspot] Failed to fetch contact', [\n 'crm_id' => $crmId,\n 'reason' => $e->getMessage(),\n ]);\n\n throw $e;\n }\n\n if (! $contact instanceof ContactsWithAssociations) {\n throw new CrmException('Contact not found');\n }\n\n return [\n 'id' => $contact->getId(),\n 'properties' => $contact->getProperties(),\n ];\n }\n\n /**\n * This is email search request that Hubspot offers as GET (more generous quota)\n */\n public function getContactByEmail(string $email, array $fields = []): array\n {\n try {\n $contact = $this->getNewInstance()->crm()->contacts()->basicApi()->getById(\n $email,\n implode(',', $fields),\n null,\n false,\n 'email'\n );\n\n return [\n 'id' => $contact->getId(),\n 'properties' => $contact->getProperties(),\n ];\n } catch (ContactApiException $e) {\n $this->log->info('[Hubspot] Failed to fetch contact', [\n 'email' => $email,\n 'reason' => $e->getMessage(),\n ]);\n\n return [];\n }\n }\n\n /**\n * @throws CrmException\n */\n public function fetchProperty(string $objectType, string $propertyId): Property\n {\n $result = $this->getNewInstance()->crm()->properties()->coreApi()->getByName($objectType, $propertyId);\n\n if (! $result instanceof Property) {\n $this->log->error('[Hubspot] Failed to fetch property', [\n 'object_type' => $objectType,\n 'property_id' => $propertyId,\n 'reason' => $result->getMessage(),\n ]);\n\n throw new CrmException('Failed to fetch property');\n }\n\n return $result;\n }\n\n /**\n * @return array<CrmFieldOption>\n */\n public function fetchPropertyOptions(string $objectType, string $propertyId): array\n {\n /** @var array<CrmFieldOption> */\n return $this->fetchProperty($objectType, $propertyId)->getOptions();\n }\n\n /**\n * @return array<array{id:string, label:string, deleted:bool}>\n */\n public function fetchCallDispositions(): array\n {\n /** @var Response $response */\n $response = $this->getInstance()->engagements()->getCallDispositions();\n\n /**\n * @var array<array{\n * id:string,\n * label:string,\n * deleted: bool\n * }>\n */\n return $response->toArray();\n }\n\n /**\n * @return array<CrmFieldOption>\n */\n public function fetchOpportunityPipelineStages(): array\n {\n $stages = [];\n $apiResponse = $this->getNewInstance()->crm()->pipelines()->pipelinesApi()->getAll('deals');\n\n if ($apiResponse instanceof Error) {\n $this->log->error('[Hubspot] Failed to fetch opportunity pipelines', [\n 'reason' => $apiResponse->getMessage(),\n ]);\n\n return [];\n }\n\n foreach ($apiResponse->getResults() as $pipeline) {\n $pipelineStages = array_map(\n static function (PipelineStage $stage) {\n return [\n 'id' => $stage->getId(),\n 'label' => $stage->getLabel(),\n ];\n },\n $pipeline->getStages()\n );\n\n $stages = array_merge($stages, $pipelineStages);\n }\n\n return $stages;\n }\n\n public function fetchOpportunityPipelines(): array\n {\n $pipelines = [];\n\n try {\n $apiResponse = $this->makeRequest('/crm/v3/pipelines/deals');\n } catch (\\Exception $e) {\n $this->log->info('[Hubspot] Failed to fetch opportunity pipelines', [\n 'reason' => $e->getMessage(),\n ]);\n\n return [];\n }\n\n $response = $apiResponse->toArray();\n\n foreach ($response['results'] as $pipeline) {\n $pipelines[] = [\n 'id' => $pipeline['id'],\n 'label' => $pipeline['label'],\n ];\n }\n\n return $pipelines;\n }\n\n /**\n * @return array<CrmFieldOption>\n */\n public function fetchMeetingOutcomeFieldOptions(Field $field): array\n {\n return $field->getCrmProviderId() === 'meetingOutcome'\n ? $this->fetchMeetingOutcomeTypes()\n : $this->fetchCallActivityTypes();\n }\n\n public function fetchMeetingOutcomeTypes(): array\n {\n return $this->extractMeetingTypeOptions(\n 'https://api.hubapi.com/crm/v3/properties/meeting/hs_meeting_outcome'\n );\n }\n\n public function fetchCallActivityTypes(): array\n {\n return $this->extractMeetingTypeOptions(\n 'https://api.hubapi.com/crm/v3/properties/call/hs_activity_type'\n );\n }\n\n private function extractMeetingTypeOptions(string $endpoint): array\n {\n /** @var Response $response */\n $response = $this->getInstance()\n ->getClient()\n ->request('GET', $endpoint);\n\n /**\n * @var array<array{\n * value: string,\n * label: string,\n * displayOrder: int\n * }> $optionData\n */\n $optionData = $response->toArray()['options'] ?? [];\n\n $options = [];\n foreach ($optionData as $item) {\n $options[] = [\n 'id' => $item['value'],\n 'value' => $item['value'],\n 'label' => $item['label'],\n 'display_order' => $item['displayOrder'],\n ];\n }\n\n return $options;\n }\n\n /**\n * @return array<CrmFieldOption>\n */\n public function fetchDispositionFieldOptions(): array\n {\n $options = [];\n\n $dispositions = $this->fetchCallDispositions();\n\n foreach ($dispositions as $disposition) {\n if ($disposition['deleted'] !== false) {\n continue;\n }\n\n $option['value'] = $disposition['id'];\n $option['id'] = $disposition['id'];\n $option['label'] = $disposition['label'];\n\n $options[] = $option;\n }\n\n return $options;\n }\n\n /**\n * @return array<CrmFieldOption>\n */\n public function fetchOpportunityFieldOptions(Field $field): array\n {\n if ($field->isStageField()) {\n return $this->fetchOpportunityPipelineStages();\n }\n\n if ($field->isPipelineField()) {\n return $this->fetchOpportunityPipelines();\n }\n\n return $this->fetchPropertyOptions('deals', $field->getCrmProviderId());\n }\n\n /**\n * @throws BadRequest\n * @throws HubspotException\n */\n public function makeRequest(string $endpoint, $method = 'GET', $payload = [], ?string $queryString = null)\n {\n $endpoint = self::BASE_URL . $endpoint;\n\n if ($method === 'GET') {\n $response = $this->getInstance()->getClient()?->request(\n method: $method,\n endpoint: $endpoint,\n query_string: $queryString\n );\n } else {\n $response = $this->getInstance()->getClient()->request($method, $endpoint, [\n 'json' => ($payload),\n ]);\n }\n\n $max = $response->getHeaderLine('X-HubSpot-RateLimit-Max'); // \"110\"\n $remaining = $response->getHeaderLine('X-HubSpot-RateLimit-Remaining'); // \"109\"\n $interval = $response->getHeaderLine('X-HubSpot-RateLimit-Interval-Milliseconds'); // \"10000\"\n $body = json_decode((string) $response->getBody(), true);\n\n \\Illuminate\\Support\\Facades\\Log::channel('custom_channel')->info('$max ' . PHP_EOL . print_r($max, true));\n \\Illuminate\\Support\\Facades\\Log::channel('custom_channel')->info('$remaining ' . PHP_EOL . print_r($remaining, true));\n \\Illuminate\\Support\\Facades\\Log::channel('custom_channel')->info('$interval ' . PHP_EOL . print_r($interval, true));\n \\Illuminate\\Support\\Facades\\Log::channel('custom_channel')->info('$body ' . PHP_EOL . print_r($body, true));\n\n return $response;\n }\n\n /**\n * @throws BadRequest\n * @throws HubspotException\n */\n public function createMeeting(array $payload): Response\n {\n $endpoint = '/crm/v3/objects/meetings';\n\n return $this->makeRequest($endpoint, 'POST', $payload);\n }\n\n /**\n * @throws BadRequest\n * @throws HubspotException\n */\n public function updateMeeting(string $meetingId, array $payload): Response\n {\n $endpoint = '/crm/v3/objects/meetings/' . $meetingId;\n\n return $this->makeRequest($endpoint, 'PATCH', $payload);\n }\n\n /**\n * @throws \\Exception\n */\n public function createNote(\n string $body,\n string $ownerId,\n int $timestamp,\n string $objectId,\n NoteObject $noteObject\n ): ?string {\n try {\n $noteInput = new SimplePublicObjectInput([\n 'properties' => [\n 'hs_note_body' => $body,\n 'hubspot_owner_id' => $ownerId,\n 'hs_timestamp' => $timestamp,\n ],\n ]);\n\n // Create note\n $note = $this->getNewInstance()->crm()->objects()->basicApi()->create('note', $noteInput);\n\n $this->getNewInstance()->crm()->objects()->associationsApi()->create(\n 'note',\n $note->getId(),\n $this->getNoteObject($noteObject),\n $objectId,\n $this->getNoteAssociationType($noteObject),\n );\n\n return $note->getId();\n } catch (\\Exception $e) {\n $this->log->error('[Hubspot] Failed to create note', [\n 'objectId' => $objectId,\n 'noteObject' => $noteObject->getObjectType(),\n 'reason' => $e->getMessage(),\n ]);\n\n \\Sentry::captureException($e);\n }\n\n return null;\n }\n\n public function updateEngagement(string $objectId, array $engagement, array $metadata): void\n {\n $this->getInstance()->engagements()->update($objectId, $engagement, $metadata);\n }\n\n public function getEngagementData(string $engagementId): array\n {\n $engagement = $this->getInstance()->engagements()->get($engagementId);\n\n return $engagement->toArray();\n }\n\n public function createEngagement(array $engagement, array $associations, array $metadata): Response\n {\n return $this->getInstance()\n ->engagements()\n ->create($engagement, $associations, $metadata);\n }\n\n public function isUnauthorizedException(\\Exception $e): bool\n {\n // Check for specific HubSpot API exception types first\n if ($e instanceof BadRequest) {\n // BadRequest can contain 401 status codes\n return $e->getCode() === 401;\n }\n\n // Check for HTTP client exceptions with status codes\n if ($e instanceof \\GuzzleHttp\\Exception\\RequestException && $e->hasResponse()) {\n $response = $e->getResponse();\n if ($response !== null) {\n return $response->getStatusCode() === 401;\n }\n }\n\n // Check for Guzzle HTTP exceptions\n if ($e instanceof \\GuzzleHttp\\Exception\\ClientException) {\n return $e->getCode() === 401;\n }\n\n // Fallback to string matching as last resort, but be more specific\n $message = strtolower($e->getMessage());\n\n return str_contains($message, '401 unauthorized') ||\n str_contains($message, 'http 401') ||\n str_contains($message, 'status code 401') ||\n (preg_match('/\\b401\\b/', $message) && str_contains($message, 'unauthorized'));\n }\n\n /**\n * Validates and refreshes the access token if needed before API requests.\n * This ensures long-running processes don't fail due to token expiration.\n *\n * @throws SocialAccountTokenInvalidException\n */\n public function ensureValidToken(): void\n {\n if ($this->oauthAccount === null) {\n return;\n }\n\n $newToken = $this->tokenManager->ensureValidToken($this->oauthAccount);\n if ($newToken !== null) {\n $this->accessToken = $newToken;\n }\n }\n\n public function getConfig()\n {\n return $this->config;\n }\n\n // returns only active (archived=false)\n public function getOwners(): array\n {\n return $this->getNewInstance()->crm()->owners()->getAll();\n }\n\n /**\n * @param bool $archived\n *\n * @return array<Owner>|[]\n */\n public function getOwnersArchived(bool $archived = true): array\n {\n $endpoint = '/crm/v3/owners';\n $queryParams = [\n 'archived' => $archived ? 'true' : 'false',\n ];\n $queryString = http_build_query($queryParams);\n\n $owners = [];\n\n try {\n $response = $this->makeRequest(endpoint: $endpoint, queryString: $queryString);\n $responseData = $response?->toArray();\n\n foreach ($responseData['results'] as $result) {\n try {\n $owners[] = Owner::create($result);\n } catch (Throwable $e) {\n $this->log->error('[HubSpot] Failed to process owner data', [\n 'result' => $result,\n 'error' => $e->getMessage(),\n ]);\n\n continue;\n }\n }\n } catch (Throwable $e) {\n $this->log->error('HubSpot] Failed to fetch owners', [\n 'archived' => $archived,\n 'error' => $e->getMessage(),\n ]);\n\n return [];\n }\n\n return $owners;\n }\n\n public function getMeeting(string $engagementId): ObjectWithAssociations\n {\n return $this->getNewInstance()->crm()->objects()->basicApi()\n ->getById('meeting', $engagementId, null, 'contact,company,deal');\n }\n\n public function deleteEngagement(string $engagementId): void\n {\n $this->getInstance()->engagements()->delete((int) $engagementId);\n }\n\n public function getAssociationsData(array $ids, string $fromObject, string $toObject): array\n {\n $associationData = [];\n $idChunks = array_chunk($ids, self::ASSOCIATIONS_BATCH_SIZE_LIMIT);\n\n foreach ($idChunks as $idChunk) {\n try {\n $batchInput = new \\HubSpot\\Client\\Crm\\Associations\\Model\\BatchInputPublicObjectId();\n $batchInput->setInputs(array_map(function ($id) {\n $publicObjectId = new \\HubSpot\\Client\\Crm\\Associations\\Model\\PublicObjectId();\n $publicObjectId->setId($id);\n\n return $publicObjectId;\n }, $idChunk));\n\n $associatedObjectsData = $this\n ->getNewInstance()\n ->crm()\n ->associations()\n ->batchApi()\n ->read($fromObject, $toObject, $batchInput);\n\n if ($associatedObjectsData instanceof \\HubSpot\\Client\\Crm\\Associations\\Model\\BatchResponsePublicAssociationMulti) {\n foreach ($associatedObjectsData->getResults() as $association) {\n $from = $association->getFrom()->getId();\n $toAssociations = $association->getTo();\n\n if (! empty($toAssociations)) {\n $associationData[$from] = array_map(function ($item) {\n return $item->getId();\n }, $toAssociations);\n }\n }\n }\n } catch (\\Exception $e) {\n $this->log->error('[Hubspot] Failed to fetch associations', [\n 'from_object' => $fromObject,\n 'to_object' => $toObject,\n 'reason' => $e->getMessage(),\n ]);\n }\n }\n\n return $associationData;\n }\n\n /**\n * @throws \\Exception\n */\n private function getNoteAssociationType(NoteObject $noteObject): string\n {\n return match($noteObject) {\n NoteObject::Opportunity => 'note_to_deal',\n NoteObject::Lead, NoteObject::Contact => 'note_to_contact', // or 'note_to_lead' if your portal supports it\n NoteObject::Account => 'note_to_company',\n NoteObject::Call, NoteObject::Event => throw new \\Exception('Not supported'),\n };\n }\n\n /**\n * @throws \\Exception\n */\n private function getNoteObject(NoteObject $noteObject): string\n {\n return match($noteObject) {\n NoteObject::Opportunity => 'deal',\n NoteObject::Lead, NoteObject::Contact => 'contact',\n NoteObject::Account => 'company',\n NoteObject::Call, NoteObject::Event => throw new \\Exception('Not supported'),\n };\n }\n\n public function addAssociations(string $objectType, string $associationType, array $payload): Response\n {\n $endpoint = \"/crm/v4/associations/$objectType/$associationType/batch/create\";\n\n return $this->makeRequest($endpoint, 'POST', $payload);\n }\n\n public function removeAssociations(string $objectType, string $associationType, array $payload): Response\n {\n $endpoint = \"/crm/v4/associations/$objectType/$associationType/batch/archive\";\n\n return $this->makeRequest($endpoint, 'POST', $payload);\n }\n}","depth":4,"on_screen":true,"value":"<?php\n\ndeclare(strict_types=1);\n\nnamespace Jiminny\\Services\\Crm\\Hubspot;\n\nuse HubSpot\\Client\\Crm\\Deals\\ApiException as DealApiException;\nuse HubSpot\\Client\\Crm\\Contacts\\ApiException as ContactApiException;\nuse HubSpot\\Client\\Crm\\Companies\\ApiException as CompanyApiException;\nuse HubSpot\\Client\\Crm\\Contacts\\Model\\SimplePublicObjectWithAssociations as ContactsWithAssociations;\nuse HubSpot\\Client\\Crm\\Companies\\Model\\SimplePublicObjectWithAssociations as CompaniesWithAssociations;\nuse HubSpot\\Client\\Crm\\Deals\\Model\\SimplePublicObjectWithAssociations as DealWithAssociations;\nuse HubSpot\\Client\\Crm\\Objects\\Model\\SimplePublicObjectInput;\nuse HubSpot\\Client\\Crm\\Objects\\Model\\SimplePublicObjectWithAssociations as ObjectWithAssociations;\nuse HubSpot\\Client\\Crm\\Pipelines\\Model\\Error;\nuse HubSpot\\Client\\Crm\\Pipelines\\Model\\PipelineStage;\nuse HubSpot\\Client\\Crm\\Properties\\Model\\Property;\nuse HubSpot\\Discovery\\Discovery;\nuse Jiminny\\Component\\Utility\\Service\\ProviderRateLimiter;\nuse Jiminny\\Exceptions\\CrmException;\nuse Jiminny\\Exceptions\\RateLimitException;\nuse Jiminny\\Exceptions\\SocialAccountTokenInvalidException;\nuse Jiminny\\Jobs\\Crm\\NoteObject;\nuse Jiminny\\Models\\Crm\\Field;\nuse Jiminny\\Services\\Crm\\BaseClient;\nuse Jiminny\\Services\\Crm\\Hubspot\\DTO\\Response\\Owner;\nuse Jiminny\\Services\\SocialAccountService;\nuse SevenShores\\Hubspot\\Exceptions\\BadRequest;\nuse SevenShores\\Hubspot\\Exceptions\\HubspotException;\nuse SevenShores\\Hubspot\\Factory;\nuse SevenShores\\Hubspot\\Http\\Response;\nuse Jiminny\\Services\\Crm\\Hubspot\\Pagination\\HubspotPaginationService;\nuse Throwable;\n\n/**\n * @phpstan-type CrmFieldOption array{id:string, label:string, value?:string}\n */\nclass Client extends BaseClient implements HubspotClientInterface\n{\n public const string MIN_API_VERSION = '2';\n\n public const string BASE_URL = 'https://api.hubapi.com';\n\n public const int ASSOCIATIONS_BATCH_SIZE_LIMIT = 1000;\n\n private HubspotPaginationService $paginationService;\n private HubspotTokenManager $tokenManager;\n private ProviderRateLimiter $rateLimiter;\n\n public function __construct(\n SocialAccountService $socialAccountService,\n HubspotPaginationService $paginationService,\n HubspotTokenManager $tokenManager,\n ProviderRateLimiter $rateLimiter,\n ) {\n parent::__construct($socialAccountService);\n $this->paginationService = $paginationService;\n $this->tokenManager = $tokenManager;\n $this->rateLimiter = $rateLimiter;\n\n $this->setBaseUrl(self::BASE_URL);\n $this->setVersion(self::MIN_API_VERSION);\n }\n\n /**\n * Single entry point for every HubSpot API call. Enforces the per-portal\n * rate limit configured in the rate_limits table (morphed to the current\n * Configuration) and reacts to a real 429 from HubSpot by translating it\n * into a RateLimitException carrying Retry-After.\n *\n * Wrap any outbound HubSpot call (SDK or raw HTTP) like:\n *\n * $this->executeRequest(fn () => $this->getNewInstance()->crm()->...);\n *\n * @template T\n * @param callable(): T $apiCall\n * @return T\n *\n * @throws RateLimitException\n */\n private function executeRequest(callable $apiCall)\n {\n if (! $this->rateLimiter->canMakeRequest($this->config)) {\n $retryAfter = $this->rateLimiter->requestAvailableIn($this->config);\n\n $this->log->warning('[Hubspot] Rate limit exceeded, deferring request', [\n 'team_id' => $this->config->team_id,\n 'config_id' => $this->config->getId(),\n 'retry_after' => $retryAfter,\n ]);\n\n throw new RateLimitException(\n 'Hubspot rate limit reached for configuration ' . $this->config->getId(),\n $retryAfter,\n );\n }\n\n $this->rateLimiter->incrementRequestCount($this->config);\n\n try {\n return $apiCall();\n } catch (Throwable $e) {\n if ($this->isHubspotRateLimit($e)) {\n $retryAfter = $this->parseRetryAfter($e);\n\n $this->log->warning('[Hubspot] Received 429 from API', [\n 'team_id' => $this->config->team_id,\n 'config_id' => $this->config->getId(),\n 'retry_after' => $retryAfter,\n 'reason' => $e->getMessage(),\n ]);\n\n throw new RateLimitException('Hubspot returned 429', $retryAfter, $e);\n }\n\n throw $e;\n }\n }\n\n private function isHubspotRateLimit(Throwable $e): bool\n {\n return method_exists($e, 'getCode') && (int) $e->getCode() === 429;\n }\n\n private function parseRetryAfter(Throwable $e): int\n {\n if (method_exists($e, 'getResponseHeaders')) {\n $headers = $e->getResponseHeaders() ?: [];\n $value = $headers['Retry-After'] ?? $headers['retry-after'] ?? null;\n if (is_array($value)) {\n $value = $value[0] ?? null;\n }\n if (is_numeric($value)) {\n return (int) $value;\n }\n }\n\n return 10;\n }\n\n public function getMinimumApiVersion(): string\n {\n return self::MIN_API_VERSION;\n }\n\n public function getInstance(): Factory\n {\n return new Factory([\n 'key' => $this->accessToken,\n 'oauth2' => true,\n 'base_url' => $this->baseUrl,\n ]);\n }\n\n public function getNewInstance(): Discovery\n {\n return \\HubSpot\\Factory::createWithAccessToken($this->accessToken);\n }\n\n /**\n * Secondly and daily limits for Hubspot API\n *\n * Product Tier: Free & Starter | Professional & Enterprise | API add-on (any tier)\n * Burst: 100/10 seconds | 150/10 seconds | 200/10 seconds\n * Daily: 250,000 | 500,000 | 1,000,000\n *\n * Official documentation states: The search endpoints are rate limited to five requests per second.\n * Since with 5 RPS were still hitting secondly rate limits we lowered it to 4\n */\n public function getPaginatedData(array $payload, string $type, int $offset = 0): array\n {\n $total = 0;\n $lastId = null;\n $rows = [];\n foreach ($this->getPaginatedDataGenerator($payload, $type, $offset, $total, $lastId) as $row) {\n $rows[] = $row;\n }\n\n return ['results' => $rows, 'total' => $total, 'last_record' => $lastId];\n }\n\n /**\n * @throws HubspotException\n * @throws SocialAccountTokenInvalidException\n * @throws BadRequest\n */\n public function getPaginatedDataGenerator(\n array $payload,\n string $type,\n int $offset = 0,\n int &$total = 0,\n ?string &$lastRecordId = null\n ): \\Generator {\n return $this->paginationService->getPaginatedDataGenerator(\n $this,\n $payload,\n $type,\n $offset,\n $total,\n $lastRecordId\n );\n }\n\n /**\n * @throws DealApiException\n * @throws CrmException\n */\n public function getOpportunityById(string $crmId, array $fields): array\n {\n try {\n $deal = $this->executeRequest(fn () => $this->getNewInstance()->crm()->deals()->basicApi()->getById(\n $crmId,\n implode(',', $fields),\n 'companies,contacts'\n ));\n } catch (DealApiException $e) {\n $this->log->info('[Hubspot] Failed to fetch opportunity', [\n 'crm_id' => $crmId,\n 'reason' => $e->getMessage(),\n ]);\n\n throw $e;\n }\n\n if (! $deal instanceof DealWithAssociations) {\n throw new CrmException('Deal not found');\n }\n\n return [\n 'id' => $deal->getId(),\n 'properties' => $deal->getProperties(),\n 'associations' => $deal->getAssociations(),\n ];\n }\n\n /**\n * Generic batch read method for HubSpot objects\n *\n * @param string $objectType The object type ('deals', 'companies', 'contacts')\n * @param array<string> $crmIds Array of HubSpot object IDs (max 100)\n * @param array<string> $fields Array of property names to fetch\n *\n * @return array<string, array> Array keyed by CRM ID with object data\n */\n private function batchReadObjects(string $objectType, array $crmIds, array $fields): array\n {\n if (empty($crmIds)) {\n return [];\n }\n\n $this->validateBatchSize($objectType, $crmIds);\n $this->ensureValidToken();\n\n try {\n $batchConfig = $this->createBatchConfiguration($objectType);\n $batchReadRequest = $this->prepareBatchRequest($batchConfig, $crmIds, $fields);\n $response = $batchConfig['api']->read($batchReadRequest);\n\n $this->validateApiResponse($response, $objectType);\n\n $results = $this->processApiResults($response);\n $this->logBatchResults($objectType, $crmIds, $results);\n\n return $results;\n } catch (\\Throwable $e) {\n $this->handleBatchError($e, $objectType, $crmIds);\n }\n }\n\n private function validateBatchSize(string $objectType, array $crmIds): void\n {\n if (count($crmIds) > 100) {\n throw new \\InvalidArgumentException(\"Batch size cannot exceed 100 {$objectType}\");\n }\n }\n\n private function createBatchConfiguration(string $objectType): array\n {\n $configurations = [\n 'deals' => [\n 'batchReadRequest' => new \\HubSpot\\Client\\Crm\\Deals\\Model\\BatchReadInputSimplePublicObjectId(),\n 'inputClass' => \\HubSpot\\Client\\Crm\\Deals\\Model\\SimplePublicObjectId::class,\n 'api' => $this->getNewInstance()->crm()->deals()->batchApi(),\n ],\n 'companies' => [\n 'batchReadRequest' => new \\HubSpot\\Client\\Crm\\Companies\\Model\\BatchReadInputSimplePublicObjectId(),\n 'inputClass' => \\HubSpot\\Client\\Crm\\Companies\\Model\\SimplePublicObjectId::class,\n 'api' => $this->getNewInstance()->crm()->companies()->batchApi(),\n ],\n 'contacts' => [\n 'batchReadRequest' => new \\HubSpot\\Client\\Crm\\Contacts\\Model\\BatchReadInputSimplePublicObjectId(),\n 'inputClass' => \\HubSpot\\Client\\Crm\\Contacts\\Model\\SimplePublicObjectId::class,\n 'api' => $this->getNewInstance()->crm()->contacts()->batchApi(),\n ],\n ];\n\n if (! isset($configurations[$objectType])) {\n throw new \\InvalidArgumentException(\"Unsupported object type: {$objectType}\");\n }\n\n return $configurations[$objectType];\n }\n\n private function prepareBatchRequest(array $batchConfig, array $crmIds, array $fields): object\n {\n $batchReadRequest = $batchConfig['batchReadRequest'];\n $inputClass = $batchConfig['inputClass'];\n\n $inputs = array_map(function ($crmId) use ($inputClass) {\n $input = new $inputClass();\n $input->setId($crmId);\n\n return $input;\n }, $crmIds);\n\n $batchReadRequest->setInputs($inputs);\n $batchReadRequest->setProperties($fields);\n\n return $batchReadRequest;\n }\n\n private function validateApiResponse($response, string $objectType): void\n {\n if (! $response) {\n throw new CrmException(\"HubSpot API returned null response for {$objectType} batch read\");\n }\n }\n\n private function processApiResults($response): array\n {\n $results = [];\n $responseResults = $response->getResults();\n\n if ($responseResults) {\n foreach ($responseResults as $object) {\n if ($object && $object->getId()) {\n $results[$object->getId()] = [\n 'id' => $object->getId(),\n 'properties' => $object->getProperties() ?: [],\n ];\n }\n }\n }\n\n return $results;\n }\n\n private function logBatchResults(string $objectType, array $crmIds, array $results): void\n {\n $this->log->info(\"[HubSpot] Batch fetched {$objectType}\", [\n 'requested_count' => count($crmIds),\n 'returned_count' => count($results),\n 'crm_ids' => $crmIds,\n ]);\n }\n\n private function handleBatchError(\\Throwable $e, string $objectType, array $crmIds): void\n {\n $errorMessage = $e->getMessage() ?: 'Unknown error';\n $errorTrace = $e->getTraceAsString() ?: 'No trace available';\n\n $this->log->error(\"[HubSpot] Failed to batch fetch {$objectType}\", [\n 'crm_ids' => $crmIds,\n 'error' => $errorMessage,\n 'trace' => $errorTrace,\n ]);\n\n throw new CrmException(\"Failed to batch fetch {$objectType}: \" . $errorMessage);\n }\n\n /**\n * Batch read multiple opportunities by their CRM IDs\n *\n * @param array<string> $crmIds Array of HubSpot deal IDs (max 100)\n * @param array<string> $fields Array of property names to fetch\n *\n * @return array<string, array> Array keyed by CRM ID with opportunity data\n */\n public function getOpportunitiesByIds(array $crmIds, array $fields): array\n {\n return $this->batchReadObjects('deals', $crmIds, $fields);\n }\n\n /**\n * Batch read multiple companies by their CRM IDs\n *\n * @param array<string> $crmIds Array of HubSpot company IDs (max 100)\n * @param array<string> $fields Array of property names to fetch\n *\n * @return array<string, array> Array keyed by CRM ID with company data\n */\n public function getCompaniesByIds(array $crmIds, array $fields): array\n {\n return $this->batchReadObjects('companies', $crmIds, $fields);\n }\n\n /**\n * Batch read multiple contacts by their CRM IDs\n *\n * @param array<string> $crmIds Array of HubSpot contact IDs (max 100)\n * @param array<string> $fields Array of property names to fetch\n *\n * @return array<string, array> Array keyed by CRM ID with contact data\n */\n public function getContactsByIds(array $crmIds, array $fields): array\n {\n return $this->batchReadObjects('contacts', $crmIds, $fields);\n }\n\n /**\n * @throws CompanyApiException\n * @throws CrmException\n */\n public function getAccountById(string $crmId, array $fields): array\n {\n try {\n $company = $this->getNewInstance()->crm()->companies()->basicApi()->getById(\n $crmId,\n implode(',', $fields),\n );\n } catch (CompanyApiException $e) {\n $this->log->info('[Hubspot] Failed to fetch account', [\n 'crm_id' => $crmId,\n 'reason' => $e->getMessage(),\n ]);\n\n throw $e;\n }\n\n if (! $company instanceof CompaniesWithAssociations) {\n throw new CrmException('Account not found');\n }\n\n return [\n 'id' => $company->getId(),\n 'properties' => $company->getProperties(),\n ];\n }\n\n /**\n * @throws ContactApiException\n * @throws CrmException\n */\n public function getContactById(string $crmId, array $fields): array\n {\n try {\n $contact = $this->getNewInstance()->crm()->contacts()->basicApi()->getById(\n $crmId,\n implode(',', $fields)\n );\n } catch (ContactApiException $e) {\n $this->log->info('[Hubspot] Failed to fetch contact', [\n 'crm_id' => $crmId,\n 'reason' => $e->getMessage(),\n ]);\n\n throw $e;\n }\n\n if (! $contact instanceof ContactsWithAssociations) {\n throw new CrmException('Contact not found');\n }\n\n return [\n 'id' => $contact->getId(),\n 'properties' => $contact->getProperties(),\n ];\n }\n\n /**\n * This is email search request that Hubspot offers as GET (more generous quota)\n */\n public function getContactByEmail(string $email, array $fields = []): array\n {\n try {\n $contact = $this->getNewInstance()->crm()->contacts()->basicApi()->getById(\n $email,\n implode(',', $fields),\n null,\n false,\n 'email'\n );\n\n return [\n 'id' => $contact->getId(),\n 'properties' => $contact->getProperties(),\n ];\n } catch (ContactApiException $e) {\n $this->log->info('[Hubspot] Failed to fetch contact', [\n 'email' => $email,\n 'reason' => $e->getMessage(),\n ]);\n\n return [];\n }\n }\n\n /**\n * @throws CrmException\n */\n public function fetchProperty(string $objectType, string $propertyId): Property\n {\n $result = $this->getNewInstance()->crm()->properties()->coreApi()->getByName($objectType, $propertyId);\n\n if (! $result instanceof Property) {\n $this->log->error('[Hubspot] Failed to fetch property', [\n 'object_type' => $objectType,\n 'property_id' => $propertyId,\n 'reason' => $result->getMessage(),\n ]);\n\n throw new CrmException('Failed to fetch property');\n }\n\n return $result;\n }\n\n /**\n * @return array<CrmFieldOption>\n */\n public function fetchPropertyOptions(string $objectType, string $propertyId): array\n {\n /** @var array<CrmFieldOption> */\n return $this->fetchProperty($objectType, $propertyId)->getOptions();\n }\n\n /**\n * @return array<array{id:string, label:string, deleted:bool}>\n */\n public function fetchCallDispositions(): array\n {\n /** @var Response $response */\n $response = $this->getInstance()->engagements()->getCallDispositions();\n\n /**\n * @var array<array{\n * id:string,\n * label:string,\n * deleted: bool\n * }>\n */\n return $response->toArray();\n }\n\n /**\n * @return array<CrmFieldOption>\n */\n public function fetchOpportunityPipelineStages(): array\n {\n $stages = [];\n $apiResponse = $this->getNewInstance()->crm()->pipelines()->pipelinesApi()->getAll('deals');\n\n if ($apiResponse instanceof Error) {\n $this->log->error('[Hubspot] Failed to fetch opportunity pipelines', [\n 'reason' => $apiResponse->getMessage(),\n ]);\n\n return [];\n }\n\n foreach ($apiResponse->getResults() as $pipeline) {\n $pipelineStages = array_map(\n static function (PipelineStage $stage) {\n return [\n 'id' => $stage->getId(),\n 'label' => $stage->getLabel(),\n ];\n },\n $pipeline->getStages()\n );\n\n $stages = array_merge($stages, $pipelineStages);\n }\n\n return $stages;\n }\n\n public function fetchOpportunityPipelines(): array\n {\n $pipelines = [];\n\n try {\n $apiResponse = $this->makeRequest('/crm/v3/pipelines/deals');\n } catch (\\Exception $e) {\n $this->log->info('[Hubspot] Failed to fetch opportunity pipelines', [\n 'reason' => $e->getMessage(),\n ]);\n\n return [];\n }\n\n $response = $apiResponse->toArray();\n\n foreach ($response['results'] as $pipeline) {\n $pipelines[] = [\n 'id' => $pipeline['id'],\n 'label' => $pipeline['label'],\n ];\n }\n\n return $pipelines;\n }\n\n /**\n * @return array<CrmFieldOption>\n */\n public function fetchMeetingOutcomeFieldOptions(Field $field): array\n {\n return $field->getCrmProviderId() === 'meetingOutcome'\n ? $this->fetchMeetingOutcomeTypes()\n : $this->fetchCallActivityTypes();\n }\n\n public function fetchMeetingOutcomeTypes(): array\n {\n return $this->extractMeetingTypeOptions(\n 'https://api.hubapi.com/crm/v3/properties/meeting/hs_meeting_outcome'\n );\n }\n\n public function fetchCallActivityTypes(): array\n {\n return $this->extractMeetingTypeOptions(\n 'https://api.hubapi.com/crm/v3/properties/call/hs_activity_type'\n );\n }\n\n private function extractMeetingTypeOptions(string $endpoint): array\n {\n /** @var Response $response */\n $response = $this->getInstance()\n ->getClient()\n ->request('GET', $endpoint);\n\n /**\n * @var array<array{\n * value: string,\n * label: string,\n * displayOrder: int\n * }> $optionData\n */\n $optionData = $response->toArray()['options'] ?? [];\n\n $options = [];\n foreach ($optionData as $item) {\n $options[] = [\n 'id' => $item['value'],\n 'value' => $item['value'],\n 'label' => $item['label'],\n 'display_order' => $item['displayOrder'],\n ];\n }\n\n return $options;\n }\n\n /**\n * @return array<CrmFieldOption>\n */\n public function fetchDispositionFieldOptions(): array\n {\n $options = [];\n\n $dispositions = $this->fetchCallDispositions();\n\n foreach ($dispositions as $disposition) {\n if ($disposition['deleted'] !== false) {\n continue;\n }\n\n $option['value'] = $disposition['id'];\n $option['id'] = $disposition['id'];\n $option['label'] = $disposition['label'];\n\n $options[] = $option;\n }\n\n return $options;\n }\n\n /**\n * @return array<CrmFieldOption>\n */\n public function fetchOpportunityFieldOptions(Field $field): array\n {\n if ($field->isStageField()) {\n return $this->fetchOpportunityPipelineStages();\n }\n\n if ($field->isPipelineField()) {\n return $this->fetchOpportunityPipelines();\n }\n\n return $this->fetchPropertyOptions('deals', $field->getCrmProviderId());\n }\n\n /**\n * @throws BadRequest\n * @throws HubspotException\n */\n public function makeRequest(string $endpoint, $method = 'GET', $payload = [], ?string $queryString = null)\n {\n $endpoint = self::BASE_URL . $endpoint;\n\n if ($method === 'GET') {\n $response = $this->getInstance()->getClient()?->request(\n method: $method,\n endpoint: $endpoint,\n query_string: $queryString\n );\n } else {\n $response = $this->getInstance()->getClient()->request($method, $endpoint, [\n 'json' => ($payload),\n ]);\n }\n\n $max = $response->getHeaderLine('X-HubSpot-RateLimit-Max'); // \"110\"\n $remaining = $response->getHeaderLine('X-HubSpot-RateLimit-Remaining'); // \"109\"\n $interval = $response->getHeaderLine('X-HubSpot-RateLimit-Interval-Milliseconds'); // \"10000\"\n $body = json_decode((string) $response->getBody(), true);\n\n \\Illuminate\\Support\\Facades\\Log::channel('custom_channel')->info('$max ' . PHP_EOL . print_r($max, true));\n \\Illuminate\\Support\\Facades\\Log::channel('custom_channel')->info('$remaining ' . PHP_EOL . print_r($remaining, true));\n \\Illuminate\\Support\\Facades\\Log::channel('custom_channel')->info('$interval ' . PHP_EOL . print_r($interval, true));\n \\Illuminate\\Support\\Facades\\Log::channel('custom_channel')->info('$body ' . PHP_EOL . print_r($body, true));\n\n return $response;\n }\n\n /**\n * @throws BadRequest\n * @throws HubspotException\n */\n public function createMeeting(array $payload): Response\n {\n $endpoint = '/crm/v3/objects/meetings';\n\n return $this->makeRequest($endpoint, 'POST', $payload);\n }\n\n /**\n * @throws BadRequest\n * @throws HubspotException\n */\n public function updateMeeting(string $meetingId, array $payload): Response\n {\n $endpoint = '/crm/v3/objects/meetings/' . $meetingId;\n\n return $this->makeRequest($endpoint, 'PATCH', $payload);\n }\n\n /**\n * @throws \\Exception\n */\n public function createNote(\n string $body,\n string $ownerId,\n int $timestamp,\n string $objectId,\n NoteObject $noteObject\n ): ?string {\n try {\n $noteInput = new SimplePublicObjectInput([\n 'properties' => [\n 'hs_note_body' => $body,\n 'hubspot_owner_id' => $ownerId,\n 'hs_timestamp' => $timestamp,\n ],\n ]);\n\n // Create note\n $note = $this->getNewInstance()->crm()->objects()->basicApi()->create('note', $noteInput);\n\n $this->getNewInstance()->crm()->objects()->associationsApi()->create(\n 'note',\n $note->getId(),\n $this->getNoteObject($noteObject),\n $objectId,\n $this->getNoteAssociationType($noteObject),\n );\n\n return $note->getId();\n } catch (\\Exception $e) {\n $this->log->error('[Hubspot] Failed to create note', [\n 'objectId' => $objectId,\n 'noteObject' => $noteObject->getObjectType(),\n 'reason' => $e->getMessage(),\n ]);\n\n \\Sentry::captureException($e);\n }\n\n return null;\n }\n\n public function updateEngagement(string $objectId, array $engagement, array $metadata): void\n {\n $this->getInstance()->engagements()->update($objectId, $engagement, $metadata);\n }\n\n public function getEngagementData(string $engagementId): array\n {\n $engagement = $this->getInstance()->engagements()->get($engagementId);\n\n return $engagement->toArray();\n }\n\n public function createEngagement(array $engagement, array $associations, array $metadata): Response\n {\n return $this->getInstance()\n ->engagements()\n ->create($engagement, $associations, $metadata);\n }\n\n public function isUnauthorizedException(\\Exception $e): bool\n {\n // Check for specific HubSpot API exception types first\n if ($e instanceof BadRequest) {\n // BadRequest can contain 401 status codes\n return $e->getCode() === 401;\n }\n\n // Check for HTTP client exceptions with status codes\n if ($e instanceof \\GuzzleHttp\\Exception\\RequestException && $e->hasResponse()) {\n $response = $e->getResponse();\n if ($response !== null) {\n return $response->getStatusCode() === 401;\n }\n }\n\n // Check for Guzzle HTTP exceptions\n if ($e instanceof \\GuzzleHttp\\Exception\\ClientException) {\n return $e->getCode() === 401;\n }\n\n // Fallback to string matching as last resort, but be more specific\n $message = strtolower($e->getMessage());\n\n return str_contains($message, '401 unauthorized') ||\n str_contains($message, 'http 401') ||\n str_contains($message, 'status code 401') ||\n (preg_match('/\\b401\\b/', $message) && str_contains($message, 'unauthorized'));\n }\n\n /**\n * Validates and refreshes the access token if needed before API requests.\n * This ensures long-running processes don't fail due to token expiration.\n *\n * @throws SocialAccountTokenInvalidException\n */\n public function ensureValidToken(): void\n {\n if ($this->oauthAccount === null) {\n return;\n }\n\n $newToken = $this->tokenManager->ensureValidToken($this->oauthAccount);\n if ($newToken !== null) {\n $this->accessToken = $newToken;\n }\n }\n\n public function getConfig()\n {\n return $this->config;\n }\n\n // returns only active (archived=false)\n public function getOwners(): array\n {\n return $this->getNewInstance()->crm()->owners()->getAll();\n }\n\n /**\n * @param bool $archived\n *\n * @return array<Owner>|[]\n */\n public function getOwnersArchived(bool $archived = true): array\n {\n $endpoint = '/crm/v3/owners';\n $queryParams = [\n 'archived' => $archived ? 'true' : 'false',\n ];\n $queryString = http_build_query($queryParams);\n\n $owners = [];\n\n try {\n $response = $this->makeRequest(endpoint: $endpoint, queryString: $queryString);\n $responseData = $response?->toArray();\n\n foreach ($responseData['results'] as $result) {\n try {\n $owners[] = Owner::create($result);\n } catch (Throwable $e) {\n $this->log->error('[HubSpot] Failed to process owner data', [\n 'result' => $result,\n 'error' => $e->getMessage(),\n ]);\n\n continue;\n }\n }\n } catch (Throwable $e) {\n $this->log->error('HubSpot] Failed to fetch owners', [\n 'archived' => $archived,\n 'error' => $e->getMessage(),\n ]);\n\n return [];\n }\n\n return $owners;\n }\n\n public function getMeeting(string $engagementId): ObjectWithAssociations\n {\n return $this->getNewInstance()->crm()->objects()->basicApi()\n ->getById('meeting', $engagementId, null, 'contact,company,deal');\n }\n\n public function deleteEngagement(string $engagementId): void\n {\n $this->getInstance()->engagements()->delete((int) $engagementId);\n }\n\n public function getAssociationsData(array $ids, string $fromObject, string $toObject): array\n {\n $associationData = [];\n $idChunks = array_chunk($ids, self::ASSOCIATIONS_BATCH_SIZE_LIMIT);\n\n foreach ($idChunks as $idChunk) {\n try {\n $batchInput = new \\HubSpot\\Client\\Crm\\Associations\\Model\\BatchInputPublicObjectId();\n $batchInput->setInputs(array_map(function ($id) {\n $publicObjectId = new \\HubSpot\\Client\\Crm\\Associations\\Model\\PublicObjectId();\n $publicObjectId->setId($id);\n\n return $publicObjectId;\n }, $idChunk));\n\n $associatedObjectsData = $this\n ->getNewInstance()\n ->crm()\n ->associations()\n ->batchApi()\n ->read($fromObject, $toObject, $batchInput);\n\n if ($associatedObjectsData instanceof \\HubSpot\\Client\\Crm\\Associations\\Model\\BatchResponsePublicAssociationMulti) {\n foreach ($associatedObjectsData->getResults() as $association) {\n $from = $association->getFrom()->getId();\n $toAssociations = $association->getTo();\n\n if (! empty($toAssociations)) {\n $associationData[$from] = array_map(function ($item) {\n return $item->getId();\n }, $toAssociations);\n }\n }\n }\n } catch (\\Exception $e) {\n $this->log->error('[Hubspot] Failed to fetch associations', [\n 'from_object' => $fromObject,\n 'to_object' => $toObject,\n 'reason' => $e->getMessage(),\n ]);\n }\n }\n\n return $associationData;\n }\n\n /**\n * @throws \\Exception\n */\n private function getNoteAssociationType(NoteObject $noteObject): string\n {\n return match($noteObject) {\n NoteObject::Opportunity => 'note_to_deal',\n NoteObject::Lead, NoteObject::Contact => 'note_to_contact', // or 'note_to_lead' if your portal supports it\n NoteObject::Account => 'note_to_company',\n NoteObject::Call, NoteObject::Event => throw new \\Exception('Not supported'),\n };\n }\n\n /**\n * @throws \\Exception\n */\n private function getNoteObject(NoteObject $noteObject): string\n {\n return match($noteObject) {\n NoteObject::Opportunity => 'deal',\n NoteObject::Lead, NoteObject::Contact => 'contact',\n NoteObject::Account => 'company',\n NoteObject::Call, NoteObject::Event => throw new \\Exception('Not supported'),\n };\n }\n\n public function addAssociations(string $objectType, string $associationType, array $payload): Response\n {\n $endpoint = \"/crm/v4/associations/$objectType/$associationType/batch/create\";\n\n return $this->makeRequest($endpoint, 'POST', $payload);\n }\n\n public function removeAssociations(string $objectType, string $associationType, array $payload): Response\n {\n $endpoint = \"/crm/v4/associations/$objectType/$associationType/batch/archive\";\n\n return $this->makeRequest($endpoint, 'POST', $payload);\n }\n}","role_description":"text entry area","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"Project","depth":3,"on_screen":false,"role_description":"text"},{"role":"AXButton","text":"Project","depth":3,"bounds":{"left":0.011968086,"top":0.047885075,"width":0.024268618,"height":0.024740623},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"New File or Directory…","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.008643617,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Expand Selected","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.008643617,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Collapse All","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.008643617,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Options","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.008643617,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Hide","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.008643617,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false}]...
|
-6881570513140039651
|
-8151256403349000395
|
click
|
accessibility
|
NULL
|
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
[2026-04-27 10:26:25] local.INFO: Jiminny\Console\Commands\Command::run Memory usage before starting command {"command":"mailbox:sync","memoryBeforeCommandInMb":60.0,"memoryPeakBeforeCommandInMb":99.727} {"correlation_id":"7b1ea2fb-8f36-4897-9970-91b4abba2cc7","trace_id":"265be7c0-bf8f-4037-a0d2-57df3ef1d7e1"}
[2026-04-27 10:26:25] local.INFO: [EmailSchedule] STARTING Inbox Sync {"host":"docker_lamp_1"} {"correlation_id":"7b1ea2fb-8f36-4897-9970-91b4abba2cc7","trace_id":"265be7c0-bf8f-4037-a0d2-57df3ef1d7e1"}
[2026-04-27 10:26:25] local.INFO: [EmailSchedule] FINISHED Inbox Sync {"host":"docker_lamp_1","events":2} {"correlation_id":"7b1ea2fb-8f36-4897-9970-91b4abba2cc7","trace_id":"265be7c0-bf8f-4037-a0d2-57df3ef1d7e1"}
[2026-04-27 10:26:25] local.INFO: Jiminny\Console\Commands\Command::run Memory usage for command {"command":"mailbox:sync","memoryBeforeCommandInMb":60.0,"memoryAfterCommandInMB":62.0,"memoryPeakBeforeCommandInMb":99.727,"memoryPeakAfterCommandInMB":99.727} {"correlation_id":"7b1ea2fb-8f36-4897-9970-91b4abba2cc7","trace_id":"265be7c0-bf8f-4037-a0d2-57df3ef1d7e1"}
[2026-04-27 10:26:26] local.INFO: [automated-reports] Started {"correlation_id":"d3f79034-397b-4936-9305-bdd99b23f2dd","trace_id":"aa0f8702-73eb-4947-a765-2e89244599c3"}
[2026-04-27 10:26:26] local.INFO: [automated-reports] Checking conditions {"isMonday":true,"isWeekend":false,"isFirstDayOfMonth":false,"currentMonth":4,"isQuarterlyMonth":true} {"correlation_id":"d3f79034-397b-4936-9305-bdd99b23f2dd","trace_id":"aa0f8702-73eb-4947-a765-2e89244599c3"}
[2026-04-27 10:26:26] local.INFO: [automated-reports] Processing daily reports {"correlation_id":"d3f79034-397b-4936-9305-bdd99b23f2dd","trace_id":"aa0f8702-73eb-4947-a765-2e89244599c3"}
[2026-04-27 10:26:26] local.INFO: [automated-reports] Found 1 daily reports to process {"correlation_id":"d3f79034-397b-4936-9305-bdd99b23f2dd","trace_id":"aa0f8702-73eb-4947-a765-2e89244599c3"}
[2026-04-27 10:26:26] local.INFO: [automated-reports] Dispatching Generate Report job for report {"reportUuid":"4f6ca2b5-1993-48aa-99ad-b66f19f15d43","teamId":1,"frequency":"weekly","type":"ask_jiminny"} {"correlation_id":"d3f79034-397b-4936-9305-bdd99b23f2dd","trace_id":"aa0f8702-73eb-4947-a765-2e89244599c3"}
[2026-04-27 10:26:26] local.INFO: [automated-reports] Processing weekly reports {"correlation_id":"d3f79034-397b-4936-9305-bdd99b23f2dd","trace_id":"aa0f8702-73eb-4947-a765-2e89244599c3"}
[2026-04-27 10:26:26] local.INFO: [automated-reports] Found 1 weekly reports to process {"correlation_id":"d3f79034-397b-4936-9305-bdd99b23f2dd","trace_id":"aa0f8702-73eb-4947-a765-2e89244599c3"}
[2026-04-27 10:26:26] local.INFO: [automated-reports] Dispatching Generate Report job for report {"reportUuid":"4f6ca2b5-1993-48aa-99ad-b66f19f15d43","teamId":1,"frequency":"weekly","type":"ask_jiminny"} {"correlation_id":"d3f79034-397b-4936-9305-bdd99b23f2dd","trace_id":"aa0f8702-73eb-4947-a765-2e89244599c3"}
[2026-04-27 10:26:26] local.INFO: [automated-reports] Completed {"correlation_id":"d3f79034-397b-4936-9305-bdd99b23f2dd","trace_id":"aa0f8702-73eb-4947-a765-2e89244599c3"}
[2026-04-27 10:26:26] local.INFO: [HubSpot Journal Polling] Getting offset from database {"offset":"","jiminny_team_id":1} {"correlation_id":"b453f0f4-0d2c-491c-a935-14c60265ec18","trace_id":"a50202d6-5601-46b4-a02e-cf5ac8d061fa"}
[2026-04-27 10:26:26] local.INFO: [HubSpot Journal API] Fetching latest journal entry {"url":"[URL_WITH_CREDENTIALS] {"correlation_id":"d3e3eb3d-5c83-4d6e-9592-aaa38c1843a4","trace_id":"265be7c0-bf8f-4037-a0d2-57df3ef1d7e1"}
[2026-04-27 10:26:28] local.INFO: [Gmail] imported 7 emails via full sync workflow for inbox 212 {"correlation_id":"d3e3eb3d-5c83-4d6e-9592-aaa38c1843a4","trace_id":"265be7c0-bf8f-4037-a0d2-57df3ef1d7e1"}
[2026-04-27 10:26:28] local.INFO: [Gmail] seeding inbox 212 with last message time : 2026-04-27 10:24:57 {"correlation_id":"d3e3eb3d-5c83-4d6e-9592-aaa38c1843a4","trace_id":"265be7c0-bf8f-4037-a0d2-57df3ef1d7e1"}
[2026-04-27 10:26:28] local.INFO: [Sync Mailbox] Sync complete {"inbox_id":212} {"correlation_id":"d3e3eb3d-5c83-4d6e-9592-aaa38c1843a4","trace_id":"265be7c0-bf8f-4037-a0d2-57df3ef1d7e1"}
[2026-04-27 10:27:04] local.INFO: Jiminny\Console\Commands\Command::run Memory usage before starting command {"command":"meeting-bot:schedule-bot","memoryBeforeCommandInMb":60.0,"memoryPeakBeforeCommandInMb":99.727} {"correlation_id":"f55fbf77-7e5f-4c87-accd-011697ca0fa3","trace_id":"9bd1c431-26de-4e31-83d8-3129ed9b3b79"}
[2026-04-27 10:27:04] local.INFO: [ScheduleBotCommand] Number of activities to be captured: 0 {"correlation_id":"f55fbf77-7e5f-4c87-accd-011697ca0fa3","trace_id":"9bd1c431-26de-4e31-83d8-3129ed9b3b79"}
[2026-04-27 10:27:04] local.INFO: Jiminny\Console\Commands\Command::run Memory usage for command {"command":"meeting-bot:schedule-bot","memoryBeforeCommandInMb":60.0,"memoryAfterCommandInMB":62.0,"memoryPeakBeforeCommandInMb":99.727,"memoryPeakAfterCommandInMB":99.727} {"correlation_id":"f55fbf77-7e5f-4c87-accd-011697ca0fa3","trace_id":"9bd1c431-26de-4e31-83d8-3129ed9b3b79"}
[2026-04-27 10:27:05] local.INFO: Jiminny\Console\Commands\Command::run Memory usage before starting command {"command":"dialers:monitor-activities","memoryBeforeCommandInMb":60.0,"memoryPeakBeforeCommandInMb":99.727} {"correlation_id":"da241053-5f3b-4d02-9504-4ef2987cdbd7","trace_id":"364d3fc7-0f11-40c6-8697-d18eff2c4cb6"}
[2026-04-27 10:27:05] local.INFO: Jiminny\Console\Commands\Command::run Memory usage for command {"command":"dialers:monitor-activities","memoryBeforeCommandInMb":60.0,"memoryAfterCommandInMB":62.0,"memoryPeakBeforeCommandInMb":99.727,"memoryPeakAfterCommandInMB":99.727} {"correlation_id":"da241053-5f3b-4d02-9504-4ef2987cdbd7","trace_id":"364d3fc7-0f11-40c6-8697-d18eff2c4cb6"}
[2026-04-27 10:27:06] local.NOTICE: Monitoring start {"correlation_id":"ad0d871a-a1fd-4725-b2f7-ac8f0fc1857e","trace_id":"cb504e84-d924-43a0-82d5-faf1840134ba"}
[2026-04-27 10:27:06] local.NOTICE: Monitoring end {"correlation_id":"ad0d871a-a1fd-4725-b2f7-ac8f0fc1857e","trace_id":"cb504e84-d924-43a0-82d5-faf1840134ba"}
[2026-04-27 10:27:08] local.INFO: Jiminny\Console\Commands\Command::run Memory usage before starting command {"command":"mailbox:skip-lists:refresh","memoryBeforeCommandInMb":60.0,"memoryPeakBeforeCommandInMb":99.727} {"correlation_id":"b8652f0d-1cee-44ae-a0ea-3d3137a882c1","trace_id":"7fe19940-0813-4199-a2a8-958de09bd0ee"}
[2026-04-27 10:27:08] local.INFO: Jiminny\Console\Commands\Command::run Memory usage for command {"command":"mailbox:skip-lists:refresh","memoryBeforeCommandInMb":60.0,"memoryAfterCommandInMB":62.0,"memoryPeakBeforeCommandInMb":99.727,"memoryPeakAfterCommandInMB":99.727} {"correlation_id":"b8652f0d-1cee-44ae-a0ea-3d3137a882c1","trace_id":"7fe19940-0813-4199-a2a8-958de09bd0ee"}
[2026-04-27 10:27:09] local.INFO: Jiminny\Console\Commands\Command::run Memory usage before starting command {"command":"mailbox:batch:process","memoryBeforeCommandInMb":60.0,"memoryPeakBeforeCommandInMb":99.727} {"correlation_id":"06190462-0f47-403f-8e95-ad5d05bff1fe","trace_id":"4aa6bed6-05a1-4661-a9c9-edadc61d84ac"}
[2026-04-27 10:27:09] local.INFO: [EmailSchedule] STARTING batch process {"host":"docker_lamp_1"} {"correlation_id":"06190462-0f47-403f-8e95-ad5d05bff1fe","trace_id":"4aa6bed6-05a1-4661-a9c9-edadc61d84ac"}
[2026-04-27 10:27:09] local.INFO: [EmailSchedule] FINISHED batch process {"host":"docker_lamp_1","processed":0} {"correlation_id":"06190462-0f47-403f-8e95-ad5d05bff1fe","trace_id":"4aa6bed6-05a1-4661-a9c9-edadc61d84ac"}
[2026-04-27 10:27:09] local.INFO: Jiminny\Console\Commands\Command::run Memory usage for command {"command":"mailbox:batch:process","memoryBeforeCommandInMb":60.0,"memoryAfterCommandInMB":62.0,"memoryPeakBeforeCommandInMb":99.727,"memoryPeakAfterCommandInMB":99.727} {"correlation_id":"06190462-0f47-403f-8e95-ad5d05bff1fe","trace_id":"4aa6bed6-05a1-4661-a9c9-edadc61d84ac"}
[2026-04-27 10:27:11] local.INFO: Jiminny\Console\Commands\Command::run Memory usage before starting command {"command":"mailbox:batch:create","memoryBeforeCommandInMb":60.0,"memoryPeakBeforeCommandInMb":99.727} {"correlation_id":"b561c5d5-e6e3-4091-9517-b0458190a360","trace_id":"bf9679fb-b785-40cb-b814-04a1dc385a1c"}
[2026-04-27 10:27:11] local.INFO: [EmailSchedule] STARTING batch create {"host":"docker_lamp_1"} {"correlation_id":"b561c5d5-e6e3-4091-9517-b0458190a360","trace_id":"bf9679fb-b785-40cb-b814-04a1dc385a1c"}
[2026-04-27 10:27:11] local.INFO: [EmailSchedule] FINISHED batch create {"host":"docker_lamp_1"} {"correlation_id":"b561c5d5-e6e3-4091-9517-b0458190a360","trace_id":"bf9679fb-b785-40cb-b814-04a1dc385a1c"}
[2026-04-27 10:27:11] local.INFO: Jiminny\Console\Commands\Command::run Memory usage for command {"command":"mailbox:batch:create","memoryBeforeCommandInMb":60.0,"memoryAfterCommandInMB":62.0,"memoryPeakBeforeCommandInMb":99.727,"memoryPeakAfterCommandInMB":99.727} {"correlation_id":"b561c5d5-e6e3-4091-9517-b0458190a360","trace_id":"bf9679fb-b785-40cb-b814-04a1dc385a1c"}
[2026-04-27 10:27:14] local.INFO: [Jiminny\Jobs\Mailbox\CreateBatches] processed 2 inboxes and created 1 batches {"userId":null,"batchSize":30,"maxBatches":1000} {"correlation_id":"98416296-43c2-4834-927e-4a0e86dc99cf","trace_id":"bf9679fb-b785-40cb-b814-04a1dc385a1c"}
[2026-04-27 10:28:04] local.INFO: Jiminny\Console\Commands\Command::run Memory usage before starting command {"command":"meeting-bot:schedule-bot","memoryBeforeCommandInMb":60.0,"memoryPeakBeforeCommandInMb":99.727} {"correlation_id":"5852bcf1-af92-4d24-8a2d-08a9d4a5cfac","trace_id":"25af5e8c-a148-493e-ac9f-4d53f1960177"}
[2026-04-27 10:28:04] local.INFO: [ScheduleBotCommand] Number of activities to be captured: 0 {"correlation_id":"5852bcf1-af92-4d24-8a2d-08a9d4a5cfac","trace_id":"25af5e8c-a148-493e-ac9f-4d53f1960177"}
[2026-04-27 10:28:04] local.INFO: Jiminny\Console\Commands\Command::run Memory usage for command {"command":"meeting-bot:schedule-bot","memoryBeforeCommandInMb":60.0,"memoryAfterCommandInMB":62.0,"memoryPeakBeforeCommandInMb":99.727,"memoryPeakAfterCommandInMB":99.727} {"correlation_id":"5852bcf1-af92-4d24-8a2d-08a9d4a5cfac","trace_id":"25af5e8c-a148-493e-ac9f-4d53f1960177"}
[2026-04-27 10:28:06] local.INFO: Jiminny\Console\Commands\Command::run Memory usage before starting command {"command":"dialers:monitor-activities","memoryBeforeCommandInMb":60.0,"memoryPeakBeforeCommandInMb":99.727} {"correlation_id":"cc1cff1f-71b4-4bb1-800f-66e27c0f2b3a","trace_id":"c03103a3-9ab8-480b-9753-7c7c6488f246"}
[2026-04-27 10:28:06] local.INFO: Jiminny\Console\Commands\Command::run Memory usage for command {"command":"dialers:monitor-activities","memoryBeforeCommandInMb":60.0,"memoryAfterCommandInMB":62.0,"memoryPeakBeforeCommandInMb":99.727,"memoryPeakAfterCommandInMB":99.727} {"correlation_id":"cc1cff1f-71b4-4bb1-800f-66e27c0f2b3a","trace_id":"c03103a3-9ab8-480b-9753-7c7c6488f246"}
[2026-04-27 10:28:09] local.ERROR: Target class [Jiminny\Repositories\AjReportsRepository] does not exist. {"exception":"[object] (Illuminate\\Contracts\\Container\\BindingResolutionException(code: 0): Target class [Jiminny\\Repositories\\AjReportsRepository] does not exist. at /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php:1124)
[stacktrace]
#0 /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php(933): Illuminate\\Container\\Container->build('Jiminny\\\\Reposit...')
#1 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Application.php(1078): Illuminate\\Container\\Container->resolve('Jiminny\\\\Reposit...', Array, true)
#2 /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php(864): Illuminate\\Foundation\\Application->resolve('Jiminny\\\\Reposit...', Array)
#3 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Application.php(1058): Illuminate\\Container\\Container->make('Jiminny\\\\Reposit...', Array)
#4 /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php(1336): Illuminate\\Foundation\\Application->make('Jiminny\\\\Reposit...')
#5 /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php(1237): Illuminate\\Container\\Container->resolveClass(Object(ReflectionParameter))
#6 /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php(1162): Illuminate\\Container\\Container->resolveDependencies(Array)
#7 /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php(933): Illuminate\\Container\\Container->build('Jiminny\\\\Http\\\\Co...')
#8 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Application.php(1078): Illuminate\\Container\\Container->resolve('Jiminny\\\\Http\\\\Co...', Array, true)
#9 /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php(864): Illuminate\\Foundation\\Application->resolve('Jiminny\\\\Http\\\\Co...', Array)
#10 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Application.php(1058): Illuminate\\Container\\Container->make('Jiminny\\\\Http\\\\Co...', Array)
#11 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Route.php(286): Illuminate\\Foundation\\Application->make('Jiminny\\\\Http\\\\Co...')
#12 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Route.php(1133): Illuminate\\Routing\\Route->getController()
#13 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Route.php(1062): Illuminate\\Routing\\Route->controllerMiddleware()
#14 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Router.php(834): Illuminate\\Routing\\Route->gatherMiddleware()
#15 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Router.php(816): Illuminate\\Routing\\Router->gatherRouteMiddleware(Object(Illuminate\\Routing\\Route))
#16 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Router.php(800): Illuminate\\Routing\\Router->runRouteWithinStack(Object(Illuminate\\Routing\\Route), Object(Illuminate\\Http\\Request))
#17 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Router.php(764): Illuminate\\Routing\\Router->runRoute(Object(Illuminate\\Http\\Request), Object(Illuminate\\Routing\\Route))
#18 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Router.php(753): Illuminate\\Routing\\Router->dispatchToRoute(Object(Illuminate\\Http\\Request))
#19 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Http/Kernel.php(200): Illuminate\\Routing\\Router->dispatch(Object(Illuminate\\Http\\Request))
#20 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(180): Illuminate\\Foundation\\Http\\Kernel->Illuminate\\Foundation\\Http\\{closure}(Object(Illuminate\\Http\\Request))
#21 /home/jiminny/vendor/barryvdh/laravel-debugbar/src/Middleware/InjectDebugbar.php(59): Illuminate\\Pipeline\\Pipeline->Illuminate\\Pipeline\\{closure}(Object(Illuminate\\Http\\Request))
#22 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(219): Barryvdh\\Debugbar\\Middleware\\InjectDebugbar->handle(Object(Illuminate\\Http\\Request), Object(Closure))
#23 /home/jiminny/vendor/laravel/framework/src/Illuminate/Http/Middleware/HandleCors.php(74): Illuminate\\Pipeline\\Pipeline->Illuminate\\Pipeline\\{closure}(Object(Illuminate\\Http\\Request))
#24 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(219): Illuminate\\Http\\Middleware\\HandleCors->handle(Object(Illuminate\\Http\\Request), Object(Closure))
#25 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Http/Middleware/TransformsRequest.php(21): Illuminate\\Pipeline\\Pipeline->Illuminate\\Pipeline\\{closure}(Object(Illuminate\\Http\\Request))
#26 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Http/Middleware/TrimStrings.php(51): Illuminate\\Foundation\\Http\\Middleware\\TransformsRequest->handle(Object(Illuminate\\Http\\Request), Object(Closure))
#27 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(219): Illuminate\\Foundation\\Http\\Middleware\\TrimStrings->handle(Object(Illuminate\\Http\\Request), Object(Closure))
#28 /home/jiminny/vendor/bepsvpt/secure-headers/src/SecureHeadersMiddleware.php(18): Illuminate\\Pipeline\\Pipeline->Illuminate\\Pipeline\\{closure}(Object(Illuminate\\Http\\Request))
#29 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(219): Bepsvpt\\SecureHeaders\\SecureHeadersMiddleware->handle(Object(Illuminate\\Http\\Request), Object(Closure))
#30 /home/jiminny/app/Http/Middleware/SentryContext.php(60): Illuminate\\Pipeline\\Pipeline->Illuminate\\Pipeline\\{closure}(Object(Illuminate\\Http\\Request))
#31 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(219): Jiminny\\Http\\Middleware\\SentryContext->handle(Object(Illuminate\\Http\\Request), Object(Closure))
#32 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Http/Middleware/PreventRequestsDuringMaintenance.php(109): Illuminate\\Pipeline\\Pipeline->Illuminate\\Pipeline\\{closure}(Object(Illuminate\\Http\\Request))
#33 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(219): Illuminate\\Foundation\\Http\\Middleware\\PreventRequestsDuringMaintenance->handle(Object(Illuminate\\Http\\Request), Object(Closure))
#34 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Http/Middleware/InvokeDeferredCallbacks.php(22): Illuminate\\Pipeline\\Pipeline->Illuminate\\Pipeline\\{closure}(Object(Illuminate\\Http\\Request))
#35 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(219): Illuminate\\Foundation\\Http\\Middleware\\InvokeDeferredCallbacks->handle(Object(Illuminate\\Http\\Request), Object(Closure))
#36 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(137): Illuminate\\Pipeline\\Pipeline->Illuminate\\Pipeline\\{closure}(Object(Illuminate\\Http\\Request))
#37 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Http/Kernel.php(175): Illuminate\\Pipeline\\Pipeline->then(Object(Closure))
#38 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Http/Kernel.php(144): Illuminate\\Foundation\\Http\\Kernel->sendRequestThroughRouter(Object(Illuminate\\Http\\Request))
#39 /home/jiminny/public/index.php(51): Illuminate\\Foundation\\Http\\Kernel->handle(Object(Illuminate\\Http\\Request))
#40 {main}
[previous exception] [object] (ReflectionException(code: -1): Class \"Jiminny\\Repositories\\AjReportsRepository\" does not exist at /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php:1122)
[stacktrace]
#0 /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php(1122): ReflectionClass->__construct('Jiminny\\\\Reposit...')
#1 /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php(933): Illuminate\\Container\\Container->build('Jiminny\\\\Reposit...')
#2 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Application.php(1078): Illuminate\\Container\\Container->resolve('Jiminny\\\\Reposit...', Array, true)
#3 /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php(864): Illuminate\\Foundation\\Application->resolve('Jiminny\\\\Reposit...', Array)
#4 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Application.php(1058): Illuminate\\Container\\Container->make('Jiminny\\\\Reposit...', Array)
#5 /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php(1336): Illuminate\\Foundation\\Application->make('Jiminny\\\\Reposit...')
#6 /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php(1237): Illuminate\\Container\\Container->resolveClass(Object(ReflectionParameter))
#7 /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php(1162): Illuminate\\Container\\Container->resolveDependencies(Array)
#8 /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php(933): Illuminate\\Container\\Container->build('Jiminny\\\\Http\\\\Co...')
#9 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Application.php(1078): Illuminate\\Container\\Container->resolve('Jiminny\\\\Http\\\\Co...', Array, true)
#10 /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php(864): Illuminate\\Foundation\\Application->resolve('Jiminny\\\\Http\\\\Co...', Array)
#11 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Application.php(1058): Illuminate\\Container\\Container->make('Jiminny\\\\Http\\\\Co...', Array)
#12 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Route.php(286): Illuminate\\Foundation\\Application->make('Jiminny\\\\Http\\\\Co...')
#13 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Route.php(1133): Illuminate\\Routing\\Route->getController()
#14 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Route.php(1062): Illuminate\\Routing\\Route->controllerMiddleware()
#15 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Router.php(834): Illuminate\\Routing\\Route->gatherMiddleware()
#16 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Router.php(816): Illuminate\\Routing\\Router->gatherRouteMiddleware(Object(Illuminate\\Routing\\Route))
#17 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Router.php(800): Illuminate\\Routing\\Router->runRouteWithinStack(Object(Illuminate\\Routing\\Route), Object(Illuminate\\Http\\Request))
#18 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Router.php(764): Illuminate\\Routing\\Router->runRoute(Object(Illuminate\\Http\\Request), Object(Illuminate\\Routing\\Route))
#19 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Router.php(753): Illuminate\\Routing\\Router->dispatchToRoute(Object(Illuminate\\Http\\Request))
#20 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Http/Kernel.php(200): Illuminate\\Routing\\Router->dispatch(Object(Illuminate\\Http\\Request))
#21 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(180): Illuminate\\Foundation\\Http\\Kernel->Illuminate\\Foundation\\Http\\{closure}(Object(Illuminate\\Http\\Request))
#22 /home/jiminny/vendor/barryvdh/laravel-debugbar/src/Middleware/InjectDebugbar.php(59): Illuminate\\Pipeline\\Pipeline->Illuminate\\Pipeline\\{closure}(Object(Illuminate\\Http\\Request))
#23 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(219): Barryvdh\\Debugbar\\Middleware\\InjectDebugbar->handle(Object(Illuminate\\Http\\Request), Object(Closure))
#24 /home/jiminny/vendor/laravel/framework/src/Illuminate/Http/Middleware/HandleCors.php(74): Illuminate\\Pipeline\\Pipeline->Illuminate\\Pipeline\\{closure}(Object(Illuminate\\Http\\Request))
#25 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(219): Illuminate\\Http\\Middleware\\HandleCors->handle(Object(Illuminate\\Http\\Request), Object(Closure))
#26 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Http/Middleware/TransformsRequest.php(21): Illuminate\\Pipeline\\Pipeline->Illuminate\\Pipeline\\{closure}(Object(Illuminate\\Http\\Request))
#27 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Http/Middleware/TrimStrings.php(51): Illuminate\\Foundation\\Http\\Middleware\\TransformsRequest->handle(Object(Illuminate\\Http\\Request), Object(Closure))
#28 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(219): Illuminate\\Foundation\\Http\\Middleware\\TrimStrings->handle(Object(Illuminate\\Http\\Request), Object(Closure))
#29 /home/jiminny/vendor/bepsvpt/secure-headers/src/SecureHeadersMiddleware.php(18): Illuminate\\Pipeline\\Pipeline->Illuminate\\Pipeline\\{closure}(Object(Illuminate\\Http\\Request))
#30 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(219): Bepsvpt\\SecureHeaders\\SecureHeadersMiddleware->handle(Object(Illuminate\\Http\\Request), Object(Closure))
#31 /home/jiminny/app/Http/Middleware/SentryContext.php(60): Illuminate\\Pipeline\\Pipeline->Illuminate\\Pipeline\\{closure}(Object(Illuminate\\Http\\Request))
#32 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(219): Jiminny\\Http\\Middleware\\SentryContext->handle(Object(Illuminate\\Http\\Request), Object(Closure))
#33 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Http/Middleware/PreventRequestsDuringMaintenance.php(109): Illuminate\\Pipeline\\Pipeline->Illuminate\\Pipeline\\{closure}(Object(Illuminate\\Http\\Request))
#34 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(219): Illuminate\\Foundation\\Http\\Middleware\\PreventRequestsDuringMaintenance->handle(Object(Illuminate\\Http\\Request), Object(Closure))
#35 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Http/Middleware/InvokeDeferredCallbacks.php(22): Illuminate\\Pipeline\\Pipeline->Illuminate\\Pipeline\\{closure}(Object(Illuminate\\Http\\Request))
#36 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(219): Illuminate\\Foundation\\Http\\Middleware\\InvokeDeferredCallbacks->handle(Object(Illuminate\\Http\\Request), Object(Closure))
#37 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(137): Illuminate\\Pipeline\\Pipeline->Illuminate\\Pipeline\\{closure}(Object(Illuminate\\Http\\Request))
#38 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Http/Kernel.php(175): Illuminate\\Pipeline\\Pipeline->then(Object(Closure))
#39 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Http/Kernel.php(144): Illuminate\\Foundation\\Http\\Kernel->sendRequestThroughRouter(Object(Illuminate\\Http\\Request))
#40 /home/jiminny/public/index.php(51): Illuminate\\Foundation\\Http\\Kernel->handle(Object(Illuminate\\Http\\Request))
#41 {main}
"} {"correlation_id":"dfad8817-1dde-4622-8320-f5fc1616a8fe","trace_id":"d21232b2-60fb-4ca0-838c-ba42e61593f9"}
[2026-04-27 10:28:09] local.NOTICE: Monitoring start {"correlation_id":"93744c67-4444-41ff-aaad-6aa9cafe294e","trace_id":"80e396b0-b1ac-4ec8-838c-00021f98072c"}
[2026-04-27 10:28:09] local.NOTICE: Monitoring end {"correlation_id":"93744c67-4444-41ff-aaad-6aa9cafe294e","trace_id":"80e396b0-b1ac-4ec8-838c-00021f98072c"}
[2026-04-27 10:28:10] local.ERROR: Target class [Jiminny\Repositories\AjReportsRepository] does not exist. {"exception":"[object] (Illuminate\\Contracts\\Container\\BindingResolutionException(code: 0): Target class [Jiminny\\Repositories\\AjReportsRepository] does not exist. at /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php:1124)
[stacktrace]
#0 /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php(933): Illuminate\\Container\\Container->build('Jiminny\\\\Reposit...')
#1 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Application.php(1078): Illuminate\\Container\\Container->resolve('Jiminny\\\\Reposit...', Array, true)
#2 /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php(864): Illuminate\\Foundation\\Application->resolve('Jiminny\\\\Reposit...', Array)
#3 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Application.php(1058): Illuminate\\Container\\Container->make('Jiminny\\\\Reposit...', Array)
#4 /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php(1336): Illuminate\\Foundation\\Application->make('Jiminny\\\\Reposit...')
#5 /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php(1237): Illuminate\\Container\\Container->resolveClass(Object(ReflectionParameter))
#6 /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php(1162): Illuminate\\Container\\Container->resolveDependencies(Array)
#7 /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php(933): Illuminate\\Container\\Container->build('Jiminny\\\\Http\\\\Co...')
#8 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Application.php(1078): Illuminate\\Container\\Container->resolve('Jiminny\\\\Http\\\\Co...', Array, true)
#9 /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php(864): Illuminate\\Foundation\\Application->resolve('Jiminny\\\\Http\\\\Co...', Array)
#10 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Application.php(1058): Illuminate\\Container\\Container->make('Jiminny\\\\Http\\\\Co...', Array)
#11 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Route.php(286): Illuminate\\Foundation\\Application->make('Jiminny\\\\Http\\\\Co...')
#12 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Route.php(1133): Illuminate\\Routing\\Route->getController()
#13 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Route.php(1062): Illuminate\\Routing\\Route->controllerMiddleware()
#14 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Router.php(834): Illuminate\\Routing\\Route->gatherMiddleware()
#15 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Router.php(816): Illuminate\\Routing\\Router->gatherRouteMiddleware(Object(Illuminate\\Routing\\Route))
#16 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Router.php(800): Illuminate\\Routing\\Router->runRouteWithinStack(Object(Illuminate\\Routing\\Route), Object(Illuminate\\Http\\Request))
#17 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Router.php(764): Illuminate\\Routing\\Router->runRoute(Object(Illuminate\\Http\\Request), Object(Illuminate\\Routing\\Route))
#18 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Router.php(753): Illuminate\\Routing\\Router->dispatchToRoute(Object(Illuminate\\Http\\Request))
#19 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Http/Kernel.php(200): Illuminate\\Routing\\Router->dispatch(Object(Illuminate\\Http\\Request))
#20 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(180): Illuminate\\Foundation\\Http\\Kernel->Illuminate\\Foundation\\Http\\{closure}(Object(Illuminate\\Http\\Request))
#21 /home/jiminny/vendor/barryvdh/laravel-debugbar/src/Middleware/InjectDebugbar.php(59): Illuminate\\Pipeline\\Pipeline->Illuminate\\Pipeline\\{closure}(Object(Illuminate\\Http\\Request))
#22 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(219): Barryvdh\\Debugbar\\Middleware\\InjectDebugbar->handle(Object(Illuminate\\Http\\Request), Object(Closure))
#23 /home/jiminny/vendor/laravel/framework/src/Illuminate/Http/Middleware/HandleCors.php(74): Illuminate\\Pipeline\\Pipeline->Illuminate\\Pipeline\\{closure}(Object(Illuminate\\Http\\Request))
#24 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(219): Illuminate\\Http\\Middleware\\HandleCors->handle(Object(Illuminate\\Http\\Request), Object(Closure))
#25 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Http/Middleware/TransformsRequest.php(21): Illuminate\\Pipeline\\Pipeline->Illuminate\\Pipeline\\{closure}(Object(Illuminate\\Http\\Request))
#26 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Http/Middleware/TrimStrings.php(51): Illuminate\\Foundation\\Http\\Middleware\\TransformsRequest->handle(Object(Illuminate\\Http\\Request), Object(Closure))
#27 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(219): Illuminate\\Foundation\\Http\\Middleware\\TrimStrings->handle(Object(Illuminate\\Http\\Request), Object(Closure))
#28 /home/jiminny/vendor/bepsvpt/secure-headers/src/SecureHeadersMiddleware.php(18): Illuminate\\Pipeline\\Pipeline->Illuminate\\Pipeline\\{closure}(Object(Illuminate\\Http\\Request))
#29 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(219): Bepsvpt\\SecureHeaders\\SecureHeadersMiddleware->handle(Object(Illuminate\\Http\\Request), Object(Closure))
#30 /home/jiminny/app/Http/Middleware/SentryContext.php(60): Illuminate\\Pipeline\\Pipeline->Illuminate\\Pipeline\\{closure}(Object(Illuminate\\Http\\Request))
#31 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(219): Jiminny\\Http\\Middleware\\SentryContext->handle(Object(Illuminate\\Http\\Request), Object(Closure))
#32 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Http/Middleware/PreventRequestsDuringMaintenance.php(109): Illuminate\\Pipeline\\Pipeline->Illuminate\\Pipeline\\{closure}(Object(Illuminate\\Http\\Request))
#33 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(219): Illuminate\\Foundation\\Http\\Middleware\\PreventRequestsDuringMaintenance->handle(Object(Illuminate\\Http\\Request), Object(Closure))
#34 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Http/Middleware/InvokeDeferredCallbacks.php(22): Illuminate\\Pipeline\\Pipeline->Illuminate\\Pipeline\\{closure}(Object(Illuminate\\Http\\Request))
#35 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(219): Illuminate\\Foundation\\Http\\Middleware\\InvokeDeferredCallbacks->handle(Object(Illuminate\\Http\\Request), Object(Closure))
#36 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(137): Illuminate\\Pipeline\\Pipeline->Illuminate\\Pipeline\\{closure}(Object(Illuminate\\Http\\Request))
#37 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Http/Kernel.php(175): Illuminate\\Pipeline\\Pipeline->then(Object(Closure))
#38 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Http/Kernel.php(144): Illuminate\\Foundation\\Http\\Kernel->sendRequestThroughRouter(Object(Illuminate\\Http\\Request))
#39 /home/jiminny/public/index.php(51): Illuminate\\Foundation\\Http\\Kernel->handle(Object(Illuminate\\Http\\Request))
#40 {main}
[previous exception] [object] (ReflectionException(code: -1): Class \"Jiminny\\Repositories\\AjReportsRepository\" does not exist at /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php:1122)
[stacktrace]
#0 /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php(1122): ReflectionClass->__construct('Jiminny\\\\Reposit...')
#1 /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php(933): Illuminate\\Container\\Container->build('Jiminny\\\\Reposit...')
#2 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Application.php(1078): Illuminate\\Container\\Container->resolve('Jiminny\\\\Reposit...', Array, true)
#3 /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php(864): Illuminate\\Foundation\\Application->resolve('Jiminny\\\\Reposit...', Array)
#4 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Application.php(1058): Illuminate\\Container\\Container->make('Jiminny\\\\Reposit...', Array)
#5 /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php(1336): Illuminate\\Foundation\\Application->make('Jiminny\\\\Reposit...')
#6 /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php(1237): Illuminate\\Container\\Container->resolveClass(Object(ReflectionParameter))
#7 /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php(1162): Illuminate\\Container\\Container->resolveDependencies(Array)
#8 /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php(933): Illuminate\\Container\\Container->build('Jiminny\\\\Http\\\\Co...')
#9 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Application.php(1078): Illuminate\\Container\\Container->resolve('Jiminny\\\\Http\\\\Co...', Array, true)
#10 /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php(864): Illuminate\\Foundation\\Application->resolve('Jiminny\\\\Http\\\\Co...', Array)
#11 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Application.php(1058): Illuminate\\Container\\Container->make('Jiminny\\\\Http\\\\Co...', Array)
#12 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Route.php(286): Illuminate\\Foundation\\Application->make('Jiminny\\\\Http\\\\Co...')
#13 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Route.php(1133): Illuminate\\Routing\\Route->getController()
#14 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Route.php(1062): Illuminate\\Routing\\Route->controllerMiddleware()
#15 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Router.php(834): Illuminate\\Routing\\Route->gatherMiddleware()
#16 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Router.php(816): Illuminate\\Routing\\Router->gatherRouteMiddleware(Object(Illuminate\\Routing\\Route))
#17 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Router.php(800): Illuminate\\Routing\\Router->runRouteWithinStack(Object(Illuminate\\Routing\\Route), Object(Illuminate\\Http\\Request))
#18 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Router.php(764): Illuminate\\Routing\\Router->runRoute(Object(Illuminate\\Http\\Request), Object(Illuminate\\Routing\\Route))
#19 /home/jiminny/vendor/laravel/framework/src/Illuminate/Routing/Router.php(753): Illuminate\\Routing\\Router->dispatchToRoute(Object(Illuminate\\Http\\Request))
#20 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Http/Kernel.php(200): Illuminate\\Routing\\Router->dispatch(Object(Illuminate\\Http\\Request))
#21 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(180): Illuminate\\Foundation\\Http\\Kernel->Illuminate\\Foundation\\Http\\{closure}(Object(Illuminate\\Http\\Request))
#22 /home/jiminny/vendor/barryvdh/laravel-debugbar/src/Middleware/InjectDebugbar.php(59): Illuminate\\Pipeline\\Pipeline->Illuminate\\Pipeline\\{closure}(Object(Illuminate\\Http\\Request))
#23 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(219): Barryvdh\\Debugbar\\Middleware\\InjectDebugbar->handle(Object(Illuminate\\Http\\Request), Object(Closure))
#24 /home/jiminny/vendor/laravel/framework/src/Illuminate/Http/Middleware/HandleCors.php(74): Illuminate\\Pipeline\\Pipeline->Illuminate\\Pipeline\\{closure}(Object(Illuminate\\Http\\Request))
#25 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(219): Illuminate\\Http\\Middleware\\HandleCors->handle(Object(Illuminate\\Http\\Request), Object(Closure))
#26 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Http/Middleware/TransformsRequest.php(21): Illuminate\\Pipeline\\Pipeline->Illuminate\\Pipeline\\{closure}(Object(Illuminate\\Http\\Request))
#27 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Http/Middleware/TrimStrings.php(51): Illuminate\\Foundation\\Http\\Middleware\\TransformsRequest->handle(Object(Illuminate\\Http\\Request), Object(Closure))
#28 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(219): Illuminate\\Foundation\\Http\\Middleware\\TrimStrings->handle(Object(Illuminate\\Http\\Request), Object(Closure))
#29 /home/jiminny/vendor/bepsvpt/secure-headers/src/SecureHeadersMiddleware.php(18): Illuminate\\Pipeline\\Pipeline->Illuminate\\Pipeline\\{closure}(Object(Illuminate\\Http\\Request))
#30 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(219): Bepsvpt\\SecureHeaders\\SecureHeadersMiddleware->handle(Object(Illuminate\\Http\\Request), Object(Closure))
#31 /home/jiminny/app/Http/Middleware/SentryContext.php(60): Illuminate\\Pipeline\\Pipeline->Illuminate\\Pipeline\\{closure}(Object(Illuminate\\Http\\Request))
#32 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(219): Jiminny\\Http\\Middleware\\SentryContext->handle(Object(Illuminate\\Http\\Request), Object(Closure))
#33 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Http/Middleware/PreventRequestsDuringMaintenance.php(109): Illuminate\\Pipeline\\Pipeline->Illuminate\\Pipeline\\{closure}(Object(Illuminate\\Http\\Request))
#34 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(219): Illuminate\\Foundation\\Http\\Middleware\\PreventRequestsDuringMaintenance->handle(Object(Illuminate\\Http\\Request), Object(Closure))
#35 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Http/Middleware/InvokeDeferredCallbacks.php(22): Illuminate\\Pipeline\\Pipeline->Illuminate\\Pipeline\\{closure}(Object(Illuminate\\Http\\Request))
#36 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(219): Illuminate\\Foundation\\Http\\Middleware\\InvokeDeferredCallbacks->handle(Object(Illuminate\\Http\\Request), Object(Closure))
#37 /home/jiminny/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(137): Illuminate\\Pipeline\\Pipeline->Illuminate\\Pipeline\\{closure}(Object(Illuminate\\Http\\Request))
#38 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Http/Kernel.php(175): Illuminate\\Pipeline\\Pipeline->then(Object(Closure))
#39 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Http/Kernel.php(144): Illuminate\\Foundation\\Http\\Kernel->sendRequestThroughRouter(Object(Illuminate\\Http\\Request))
#40 /home/jiminny/public/index.php(51): Illuminate\\Foundation\\Http\\Kernel->handle(Object(Illuminate\\Http\\Request))
#41 {main}
"} {"correlation_id":"de768781-5c05-418c-baf3-79f77a8c8694","trace_id":"fec74062-3f6d-4cfc-968a-1bb285496db3"}
[2026-04-27 10:28:12] local.INFO: Jiminny\Console\Commands\Command::run Memory usage before starting command {"command":"mailbox:skip-lists:refresh","memoryBeforeCommandInMb":60.0,"memoryPeakBeforeCommandInMb":99.727} {"correlation_id":"907d15ed-8c84-41a1-a473-590eebb55dbc","trace_id":"ec7bd353-516e-491d-89dd-de44136c1672"}
[2026-04-27 10:28:12] local.INFO: Jiminny\Console\Commands\Command::run Memory usage for command {"command":"mailbox:skip-lists:refresh","memoryBeforeCommandInMb":60.0,"memoryAfterCommandInMB":62.0,"memoryPeakBeforeCommandInMb":99.727,"memoryPeakAfterCommandInMB":99.727} {"correlation_id":"907d15ed-8c84-41a1-a473-590eebb55dbc","trace_id":"ec7bd353-516e-491d-89dd-de44136c1672"}
[2026-04-27 10:28:13] local.ERROR: Target class [Jiminny\Repositories\AjReportsRepository] does not exist. {"exception":"[object] (Illuminate\\Contracts\\Container\\BindingResolutionException(code: 0): Target class [Jiminny\\Repositories\\AjReportsRepository] does not exist. at /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php:1124)
[stacktrace]
#0 /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php(933): Illuminate\\Container\\Container->build('Jiminny\\\\Reposit...')
#1 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Application.php(1078): Illuminate\\Container\\Container->resolve('Jiminny\\\\Reposit...', Array, true)
#2 /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php(864): Illuminate\\Foundation\\Application->resolve('Jiminny\\\\Reposit...', Array)
#3 /home/jiminny/vendor/laravel/framewo...
|
2701
|
NULL
|
NULL
|
NULL
|
|
2706
|
111
|
23
|
2026-05-07T11:36:29.040403+00:00
|
/Users/lukas/.screenpipe/data/data/2026-05-07/1778 /Users/lukas/.screenpipe/data/data/2026-05-07/1778153789040_m1.jpg...
|
iTerm2
|
DEV (docker)
|
True
|
NULL
|
monitor_1
|
NULL
|
NULL
|
NULL
|
NULL
|
Last login: Thu May 7 09:44:56 on ttys006
Poetry Last login: Thu May 7 09:44:56 on ttys006
Poetry could not find a pyproject.toml file in /Users/lukas/jiminny/app or its parents
Poetry could not find a pyproject.toml file in /Users/lukas/jiminny/app or its parents
lukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/jiminny/app (JY-20773-fix-automated-reports-user-pilot-tracking) $ dev
root@docker_lamp_1:/home/jiminny# php artisan crm:sync-opportunity --teamId=2 --from='2026-05-01 00:00:00'
Syncing opportunity for Hubspot
Your HubSpot account has become disconnected. Please login to Jiminny to reconnect. skipping...
root@docker_lamp_1:/home/jiminny# php artisan jiminny:token-info -A 1499 -R
----------------------------------------------------------------------------------------------------
access_token => CNeR-JHgMxIZQlNQMl8kQEwrAgwACAkUAhIJBB4BAQEDBxiCiYwCIN7Y_Qwo0qwCMhTnG549n-YtNuc1jgj-2AsLPSmw3DoyQlNQMl8kQEwrAiUACBkGawEFThwBARIBAQEEATEEAQEBAQEBAQEBAQUBEggBAQEBAYlCFPAsBNZxoDp5kAcRyeBlQoE5SM7DSgNuYTFSAFoAYABo3tj9DHAAeAA
----------------------------------------------------------------------------------------------------
access_token_expires_at => 2026-05-07 11:41:20
----------------------------------------------------------------------------------------------------
refresh_token => d5ab04e2-2109-4c0b-b513-8cba1dd54371
----------------------------------------------------------------------------------------------------
refresh_token_expires_at =>
----------------------------------------------------------------------------------------------------
root@docker_lamp_1:/home/jiminny# php artisan crm:sync-opportunity --teamId=2 --from='2026-05-01 00:00:00'
DOCKER
Close Tab
DEV (docker)
Close Tab
APP (-zsh)
Close Tab
-zsh
Close Tab
screenpipe"
Close Tab
-zsh
Close Tab
⌥⌘1
DEV (docker)...
|
[{"role":"AXTextArea","text [{"role":"AXTextArea","text":"Last login: Thu May 7 09:44:56 on ttys006\n\nPoetry could not find a pyproject.toml file in /Users/lukas/jiminny/app or its parents\n\nPoetry could not find a pyproject.toml file in /Users/lukas/jiminny/app or its parents\nlukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/jiminny/app (JY-20773-fix-automated-reports-user-pilot-tracking) $ dev\nroot@docker_lamp_1:/home/jiminny# php artisan crm:sync-opportunity --teamId=2 --from='2026-05-01 00:00:00'\nSyncing opportunity for Hubspot\nYour HubSpot account has become disconnected. Please login to Jiminny to reconnect. skipping...\nroot@docker_lamp_1:/home/jiminny# php artisan jiminny:token-info -A 1499 -R\n----------------------------------------------------------------------------------------------------\naccess_token => CNeR-JHgMxIZQlNQMl8kQEwrAgwACAkUAhIJBB4BAQEDBxiCiYwCIN7Y_Qwo0qwCMhTnG549n-YtNuc1jgj-2AsLPSmw3DoyQlNQMl8kQEwrAiUACBkGawEFThwBARIBAQEEATEEAQEBAQEBAQEBAQUBEggBAQEBAYlCFPAsBNZxoDp5kAcRyeBlQoE5SM7DSgNuYTFSAFoAYABo3tj9DHAAeAA\n----------------------------------------------------------------------------------------------------\naccess_token_expires_at => 2026-05-07 11:41:20\n----------------------------------------------------------------------------------------------------\nrefresh_token => d5ab04e2-2109-4c0b-b513-8cba1dd54371\n----------------------------------------------------------------------------------------------------\nrefresh_token_expires_at => \n----------------------------------------------------------------------------------------------------\nroot@docker_lamp_1:/home/jiminny# php artisan crm:sync-opportunity --teamId=2 --from='2026-05-01 00:00:00'","depth":4,"bounds":{"left":0.0,"top":0.08777778,"width":1.0,"height":0.9122222},"on_screen":true,"lines":[{"char_start":0,"char_count":43,"bounds":{"left":0.0034722222,"top":0.08777778,"width":0.23888889,"height":0.02}},{"char_start":43,"char_count":1,"bounds":{"left":0.0034722222,"top":0.107777774,"width":0.0055555557,"height":0.02}},{"char_start":44,"char_count":87,"bounds":{"left":0.0034722222,"top":0.12777779,"width":0.48333332,"height":0.02}},{"char_start":131,"char_count":1,"bounds":{"left":0.0034722222,"top":0.14777778,"width":0.0055555557,"height":0.02}},{"char_start":132,"char_count":87,"bounds":{"left":0.0034722222,"top":0.16777778,"width":0.48333332,"height":0.02}},{"char_start":219,"char_count":114,"bounds":{"left":0.0034722222,"top":0.18777777,"width":0.6333333,"height":0.02}},{"char_start":333,"char_count":107,"bounds":{"left":0.0034722222,"top":0.20777778,"width":0.59444445,"height":0.02}},{"char_start":440,"char_count":32,"bounds":{"left":0.0034722222,"top":0.22777778,"width":0.17777778,"height":0.02}},{"char_start":472,"char_count":97,"bounds":{"left":0.0034722222,"top":0.24777777,"width":0.5388889,"height":0.02}},{"char_start":569,"char_count":76,"bounds":{"left":0.0034722222,"top":0.26777777,"width":0.42222223,"height":0.02}},{"char_start":645,"char_count":101,"bounds":{"left":0.0034722222,"top":0.28777778,"width":0.5611111,"height":0.02}},{"char_start":925,"char_count":57,"bounds":{"left":0.0034722222,"top":0.32777777,"width":0.31666666,"height":0.02}},{"char_start":982,"char_count":101,"bounds":{"left":0.0034722222,"top":0.34777778,"width":0.5611111,"height":0.02}},{"char_start":1083,"char_count":47,"bounds":{"left":0.0034722222,"top":0.36777776,"width":0.2611111,"height":0.02}},{"char_start":1130,"char_count":101,"bounds":{"left":0.0034722222,"top":0.38777778,"width":0.5611111,"height":0.02}},{"char_start":1231,"char_count":54,"bounds":{"left":0.0034722222,"top":0.4077778,"width":0.3,"height":0.02}},{"char_start":1285,"char_count":101,"bounds":{"left":0.0034722222,"top":0.42777777,"width":0.5611111,"height":0.02}},{"char_start":1386,"char_count":29,"bounds":{"left":0.0034722222,"top":0.44777778,"width":0.16111112,"height":0.02}},{"char_start":1415,"char_count":101,"bounds":{"left":0.0034722222,"top":0.4677778,"width":0.5611111,"height":0.02}},{"char_start":1516,"char_count":106,"bounds":{"left":0.0034722222,"top":0.48777777,"width":0.5888889,"height":0.02}}],"value":"Last login: Thu May 7 09:44:56 on ttys006\n\nPoetry could not find a pyproject.toml file in /Users/lukas/jiminny/app or its parents\n\nPoetry could not find a pyproject.toml file in /Users/lukas/jiminny/app or its parents\nlukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/jiminny/app (JY-20773-fix-automated-reports-user-pilot-tracking) $ dev\nroot@docker_lamp_1:/home/jiminny# php artisan crm:sync-opportunity --teamId=2 --from='2026-05-01 00:00:00'\nSyncing opportunity for Hubspot\nYour HubSpot account has become disconnected. Please login to Jiminny to reconnect. skipping...\nroot@docker_lamp_1:/home/jiminny# php artisan jiminny:token-info -A 1499 -R\n----------------------------------------------------------------------------------------------------\naccess_token => CNeR-JHgMxIZQlNQMl8kQEwrAgwACAkUAhIJBB4BAQEDBxiCiYwCIN7Y_Qwo0qwCMhTnG549n-YtNuc1jgj-2AsLPSmw3DoyQlNQMl8kQEwrAiUACBkGawEFThwBARIBAQEEATEEAQEBAQEBAQEBAQUBEggBAQEBAYlCFPAsBNZxoDp5kAcRyeBlQoE5SM7DSgNuYTFSAFoAYABo3tj9DHAAeAA\n----------------------------------------------------------------------------------------------------\naccess_token_expires_at => 2026-05-07 11:41:20\n----------------------------------------------------------------------------------------------------\nrefresh_token => d5ab04e2-2109-4c0b-b513-8cba1dd54371\n----------------------------------------------------------------------------------------------------\nrefresh_token_expires_at => \n----------------------------------------------------------------------------------------------------\nroot@docker_lamp_1:/home/jiminny# php artisan crm:sync-opportunity --teamId=2 --from='2026-05-01 00:00:00'","is_focused":true},{"role":"AXRadioButton","text":"DOCKER","depth":2,"bounds":{"left":0.0,"top":0.05888889,"width":0.16458334,"height":0.026666667},"on_screen":true,"role_description":"radio button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Close Tab","depth":3,"bounds":{"left":0.004166667,"top":0.06333333,"width":0.011111111,"height":0.017777778},"on_screen":true,"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXRadioButton","text":"DEV (docker)","depth":2,"bounds":{"left":0.16458334,"top":0.05888889,"width":0.16458334,"height":0.026666667},"on_screen":true,"role_description":"radio button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Close Tab","depth":3,"bounds":{"left":0.16875,"top":0.06333333,"width":0.011111111,"height":0.017777778},"on_screen":true,"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXRadioButton","text":"APP (-zsh)","depth":2,"bounds":{"left":0.32916668,"top":0.05888889,"width":0.16423611,"height":0.026666667},"on_screen":true,"role_description":"radio button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Close Tab","depth":3,"bounds":{"left":0.33333334,"top":0.06333333,"width":0.011111111,"height":0.017777778},"on_screen":true,"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXRadioButton","text":"-zsh","depth":2,"bounds":{"left":0.49340278,"top":0.05888889,"width":0.16423611,"height":0.026666667},"on_screen":true,"role_description":"radio button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Close Tab","depth":3,"bounds":{"left":0.49756944,"top":0.06333333,"width":0.011111111,"height":0.017777778},"on_screen":true,"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXRadioButton","text":"screenpipe\"","depth":2,"bounds":{"left":0.6576389,"top":0.05888889,"width":0.16423611,"height":0.026666667},"on_screen":true,"role_description":"radio button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Close Tab","depth":3,"bounds":{"left":0.66180557,"top":0.06333333,"width":0.011111111,"height":0.017777778},"on_screen":true,"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXRadioButton","text":"-zsh","depth":2,"bounds":{"left":0.821875,"top":0.05888889,"width":0.16423611,"height":0.026666667},"on_screen":true,"role_description":"radio button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Close Tab","depth":3,"bounds":{"left":0.82604164,"top":0.06333333,"width":0.011111111,"height":0.017777778},"on_screen":true,"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"⌥⌘1","depth":1,"bounds":{"left":0.95763886,"top":0.032222223,"width":0.03888889,"height":0.018888889},"on_screen":true,"automation_id":"_NS:8","role_description":"text"},{"role":"AXStaticText","text":"DEV (docker)","depth":1,"bounds":{"left":0.47013888,"top":0.033333335,"width":0.0625,"height":0.017777778},"on_screen":true,"role_description":"text"}]...
|
-8970535408088379666
|
4648522587191274515
|
click
|
accessibility
|
NULL
|
Last login: Thu May 7 09:44:56 on ttys006
Poetry Last login: Thu May 7 09:44:56 on ttys006
Poetry could not find a pyproject.toml file in /Users/lukas/jiminny/app or its parents
Poetry could not find a pyproject.toml file in /Users/lukas/jiminny/app or its parents
lukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/jiminny/app (JY-20773-fix-automated-reports-user-pilot-tracking) $ dev
root@docker_lamp_1:/home/jiminny# php artisan crm:sync-opportunity --teamId=2 --from='2026-05-01 00:00:00'
Syncing opportunity for Hubspot
Your HubSpot account has become disconnected. Please login to Jiminny to reconnect. skipping...
root@docker_lamp_1:/home/jiminny# php artisan jiminny:token-info -A 1499 -R
----------------------------------------------------------------------------------------------------
access_token => CNeR-JHgMxIZQlNQMl8kQEwrAgwACAkUAhIJBB4BAQEDBxiCiYwCIN7Y_Qwo0qwCMhTnG549n-YtNuc1jgj-2AsLPSmw3DoyQlNQMl8kQEwrAiUACBkGawEFThwBARIBAQEEATEEAQEBAQEBAQEBAQUBEggBAQEBAYlCFPAsBNZxoDp5kAcRyeBlQoE5SM7DSgNuYTFSAFoAYABo3tj9DHAAeAA
----------------------------------------------------------------------------------------------------
access_token_expires_at => 2026-05-07 11:41:20
----------------------------------------------------------------------------------------------------
refresh_token => d5ab04e2-2109-4c0b-b513-8cba1dd54371
----------------------------------------------------------------------------------------------------
refresh_token_expires_at =>
----------------------------------------------------------------------------------------------------
root@docker_lamp_1:/home/jiminny# php artisan crm:sync-opportunity --teamId=2 --from='2026-05-01 00:00:00'
DOCKER
Close Tab
DEV (docker)
Close Tab
APP (-zsh)
Close Tab
-zsh
Close Tab
screenpipe"
Close Tab
-zsh
Close Tab
⌥⌘1
DEV (docker)...
|
NULL
|
NULL
|
NULL
|
NULL
|
|
2707
|
112
|
26
|
2026-05-07T11:36:29.137637+00:00
|
/Users/lukas/.screenpipe/data/data/2026-05-07/1778 /Users/lukas/.screenpipe/data/data/2026-05-07/1778153789137_m2.jpg...
|
iTerm2
|
DEV (docker)
|
True
|
NULL
|
monitor_2
|
NULL
|
NULL
|
NULL
|
NULL
|
Last login: Thu May 7 09:44:56 on ttys006
Poetry Last login: Thu May 7 09:44:56 on ttys006
Poetry could not find a pyproject.toml file in /Users/lukas/jiminny/app or its parents
Poetry could not find a pyproject.toml file in /Users/lukas/jiminny/app or its parents
lukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/jiminny/app (JY-20773-fix-automated-reports-user-pilot-tracking) $ dev
root@docker_lamp_1:/home/jiminny# php artisan crm:sync-opportunity --teamId=2 --from='2026-05-01 00:00:00'
Syncing opportunity for Hubspot
Your HubSpot account has become disconnected. Please login to Jiminny to reconnect. skipping...
root@docker_lamp_1:/home/jiminny# php artisan jiminny:token-info -A 1499 -R
----------------------------------------------------------------------------------------------------
access_token => CNeR-JHgMxIZQlNQMl8kQEwrAgwACAkUAhIJBB4BAQEDBxiCiYwCIN7Y_Qwo0qwCMhTnG549n-YtNuc1jgj-2AsLPSmw3DoyQlNQMl8kQEwrAiUACBkGawEFThwBARIBAQEEATEEAQEBAQEBAQEBAQUBEggBAQEBAYlCFPAsBNZxoDp5kAcRyeBlQoE5SM7DSgNuYTFSAFoAYABo3tj9DHAAeAA
----------------------------------------------------------------------------------------------------
access_token_expires_at => 2026-05-07 11:41:20
----------------------------------------------------------------------------------------------------
refresh_token => d5ab04e2-2109-4c0b-b513-8cba1dd54371
----------------------------------------------------------------------------------------------------
refresh_token_expires_at =>
----------------------------------------------------------------------------------------------------
root@docker_lamp_1:/home/jiminny# php artisan crm:sync-opportunity --teamId=2 --from='2026-05-01 00:00:00'
DOCKER
Close Tab
DEV (docker)
Close Tab
APP (-zsh)
Close Tab
-zsh
Close Tab
screenpipe"
Close Tab
-zsh
Close Tab
⌥⌘1
DEV (docker)...
|
[{"role":"AXTextArea","text [{"role":"AXTextArea","text":"Last login: Thu May 7 09:44:56 on ttys006\n\nPoetry could not find a pyproject.toml file in /Users/lukas/jiminny/app or its parents\n\nPoetry could not find a pyproject.toml file in /Users/lukas/jiminny/app or its parents\nlukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/jiminny/app (JY-20773-fix-automated-reports-user-pilot-tracking) $ dev\nroot@docker_lamp_1:/home/jiminny# php artisan crm:sync-opportunity --teamId=2 --from='2026-05-01 00:00:00'\nSyncing opportunity for Hubspot\nYour HubSpot account has become disconnected. Please login to Jiminny to reconnect. skipping...\nroot@docker_lamp_1:/home/jiminny# php artisan jiminny:token-info -A 1499 -R\n----------------------------------------------------------------------------------------------------\naccess_token => CNeR-JHgMxIZQlNQMl8kQEwrAgwACAkUAhIJBB4BAQEDBxiCiYwCIN7Y_Qwo0qwCMhTnG549n-YtNuc1jgj-2AsLPSmw3DoyQlNQMl8kQEwrAiUACBkGawEFThwBARIBAQEEATEEAQEBAQEBAQEBAQUBEggBAQEBAYlCFPAsBNZxoDp5kAcRyeBlQoE5SM7DSgNuYTFSAFoAYABo3tj9DHAAeAA\n----------------------------------------------------------------------------------------------------\naccess_token_expires_at => 2026-05-07 11:41:20\n----------------------------------------------------------------------------------------------------\nrefresh_token => d5ab04e2-2109-4c0b-b513-8cba1dd54371\n----------------------------------------------------------------------------------------------------\nrefresh_token_expires_at => \n----------------------------------------------------------------------------------------------------\nroot@docker_lamp_1:/home/jiminny# php artisan crm:sync-opportunity --teamId=2 --from='2026-05-01 00:00:00'","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.4800532,"height":-0.06304872},"on_screen":true,"value":"Last login: Thu May 7 09:44:56 on ttys006\n\nPoetry could not find a pyproject.toml file in /Users/lukas/jiminny/app or its parents\n\nPoetry could not find a pyproject.toml file in /Users/lukas/jiminny/app or its parents\nlukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/jiminny/app (JY-20773-fix-automated-reports-user-pilot-tracking) $ dev\nroot@docker_lamp_1:/home/jiminny# php artisan crm:sync-opportunity --teamId=2 --from='2026-05-01 00:00:00'\nSyncing opportunity for Hubspot\nYour HubSpot account has become disconnected. Please login to Jiminny to reconnect. skipping...\nroot@docker_lamp_1:/home/jiminny# php artisan jiminny:token-info -A 1499 -R\n----------------------------------------------------------------------------------------------------\naccess_token => CNeR-JHgMxIZQlNQMl8kQEwrAgwACAkUAhIJBB4BAQEDBxiCiYwCIN7Y_Qwo0qwCMhTnG549n-YtNuc1jgj-2AsLPSmw3DoyQlNQMl8kQEwrAiUACBkGawEFThwBARIBAQEEATEEAQEBAQEBAQEBAQUBEggBAQEBAYlCFPAsBNZxoDp5kAcRyeBlQoE5SM7DSgNuYTFSAFoAYABo3tj9DHAAeAA\n----------------------------------------------------------------------------------------------------\naccess_token_expires_at => 2026-05-07 11:41:20\n----------------------------------------------------------------------------------------------------\nrefresh_token => d5ab04e2-2109-4c0b-b513-8cba1dd54371\n----------------------------------------------------------------------------------------------------\nrefresh_token_expires_at => \n----------------------------------------------------------------------------------------------------\nroot@docker_lamp_1:/home/jiminny# php artisan crm:sync-opportunity --teamId=2 --from='2026-05-01 00:00:00'","is_focused":true},{"role":"AXRadioButton","text":"DOCKER","depth":2,"bounds":{"left":0.27027926,"top":1.0,"width":0.0787899,"height":-0.042298436},"on_screen":true,"role_description":"radio button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Close Tab","depth":3,"bounds":{"left":0.27227393,"top":1.0,"width":0.005319149,"height":-0.04549086},"on_screen":true,"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXRadioButton","text":"DEV (docker)","depth":2,"bounds":{"left":0.34906915,"top":1.0,"width":0.0787899,"height":-0.042298436},"on_screen":true,"role_description":"radio button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Close Tab","depth":3,"bounds":{"left":0.35106382,"top":1.0,"width":0.005319149,"height":-0.04549086},"on_screen":true,"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXRadioButton","text":"APP (-zsh)","depth":2,"bounds":{"left":0.42785904,"top":1.0,"width":0.07862367,"height":-0.042298436},"on_screen":true,"role_description":"radio button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Close Tab","depth":3,"bounds":{"left":0.42985374,"top":1.0,"width":0.005319149,"height":-0.04549086},"on_screen":true,"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXRadioButton","text":"-zsh","depth":2,"bounds":{"left":0.5064827,"top":1.0,"width":0.07862367,"height":-0.042298436},"on_screen":true,"role_description":"radio button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Close Tab","depth":3,"bounds":{"left":0.5084774,"top":1.0,"width":0.005319149,"height":-0.04549086},"on_screen":true,"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXRadioButton","text":"screenpipe\"","depth":2,"bounds":{"left":0.5851064,"top":1.0,"width":0.07862367,"height":-0.042298436},"on_screen":true,"role_description":"radio button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Close Tab","depth":3,"bounds":{"left":0.58710104,"top":1.0,"width":0.005319149,"height":-0.04549086},"on_screen":true,"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXRadioButton","text":"-zsh","depth":2,"bounds":{"left":0.66373,"top":1.0,"width":0.07862367,"height":-0.042298436},"on_screen":true,"role_description":"radio button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Close Tab","depth":3,"bounds":{"left":0.66572475,"top":1.0,"width":0.005319149,"height":-0.04549086},"on_screen":true,"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"⌥⌘1","depth":1,"bounds":{"left":0.7287234,"top":1.0,"width":0.01861702,"height":-0.023144484},"on_screen":true,"automation_id":"_NS:8","role_description":"text"},{"role":"AXStaticText","text":"DEV (docker)","depth":1,"bounds":{"left":0.49534574,"top":1.0,"width":0.029920213,"height":-0.02394259},"on_screen":true,"role_description":"text"}]...
|
-8970535408088379666
|
4648522587191274515
|
click
|
accessibility
|
NULL
|
Last login: Thu May 7 09:44:56 on ttys006
Poetry Last login: Thu May 7 09:44:56 on ttys006
Poetry could not find a pyproject.toml file in /Users/lukas/jiminny/app or its parents
Poetry could not find a pyproject.toml file in /Users/lukas/jiminny/app or its parents
lukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/jiminny/app (JY-20773-fix-automated-reports-user-pilot-tracking) $ dev
root@docker_lamp_1:/home/jiminny# php artisan crm:sync-opportunity --teamId=2 --from='2026-05-01 00:00:00'
Syncing opportunity for Hubspot
Your HubSpot account has become disconnected. Please login to Jiminny to reconnect. skipping...
root@docker_lamp_1:/home/jiminny# php artisan jiminny:token-info -A 1499 -R
----------------------------------------------------------------------------------------------------
access_token => CNeR-JHgMxIZQlNQMl8kQEwrAgwACAkUAhIJBB4BAQEDBxiCiYwCIN7Y_Qwo0qwCMhTnG549n-YtNuc1jgj-2AsLPSmw3DoyQlNQMl8kQEwrAiUACBkGawEFThwBARIBAQEEATEEAQEBAQEBAQEBAQUBEggBAQEBAYlCFPAsBNZxoDp5kAcRyeBlQoE5SM7DSgNuYTFSAFoAYABo3tj9DHAAeAA
----------------------------------------------------------------------------------------------------
access_token_expires_at => 2026-05-07 11:41:20
----------------------------------------------------------------------------------------------------
refresh_token => d5ab04e2-2109-4c0b-b513-8cba1dd54371
----------------------------------------------------------------------------------------------------
refresh_token_expires_at =>
----------------------------------------------------------------------------------------------------
root@docker_lamp_1:/home/jiminny# php artisan crm:sync-opportunity --teamId=2 --from='2026-05-01 00:00:00'
DOCKER
Close Tab
DEV (docker)
Close Tab
APP (-zsh)
Close Tab
-zsh
Close Tab
screenpipe"
Close Tab
-zsh
Close Tab
⌥⌘1
DEV (docker)...
|
2705
|
NULL
|
NULL
|
NULL
|
|
2709
|
111
|
25
|
2026-05-07T11:36:35.424018+00:00
|
/Users/lukas/.screenpipe/data/data/2026-05-07/1778 /Users/lukas/.screenpipe/data/data/2026-05-07/1778153795424_m1.jpg...
|
PhpStorm
|
faVsco.js – laravel.log
|
True
|
NULL
|
monitor_1
|
NULL
|
NULL
|
NULL
|
NULL
|
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
[2026-05-07 11:36:26] local.INFO: Jiminny\Console\Commands\Command::run Memory usage before starting command {"command":"crm:sync-hubspot-objects","memoryBeforeCommandInMb":60.0,"memoryPeakBeforeCommandInMb":99.727} {"correlation_id":"f71ac7c4-e9d4-46ad-be82-47e68a8f1199","trace_id":"5c938a2d-aea9-4025-a7f7-733d8e8e9711"}
[2026-05-07 11:36:26] local.INFO: Jiminny\Console\Commands\Command::run Memory usage for command {"command":"crm:sync-hubspot-objects","memoryBeforeCommandInMb":60.0,"memoryAfterCommandInMB":62.0,"memoryPeakBeforeCommandInMb":99.727,"memoryPeakAfterCommandInMB":99.727} {"correlation_id":"f71ac7c4-e9d4-46ad-be82-47e68a8f1199","trace_id":"5c938a2d-aea9-4025-a7f7-733d8e8e9711"}
[2026-05-07 11:36:27] local.INFO: [SyncHubspotObjects] Starting sync {"team":"abae74b8-bfa8-4383-9a7f-89f4bf2bdbb4","usage":24412984,"real_usage":65011712,"pid":13732} {"correlation_id":"766570a3-89a2-4427-81c2-c70a7c780ee3","trace_id":"5c938a2d-aea9-4025-a7f7-733d8e8e9711"}
[2026-05-07 11:36:27] local.INFO: [SocialAccountService] Fetching token {"socialAccountId":1499,"provider":"hubspot"} {"correlation_id":"766570a3-89a2-4427-81c2-c70a7c780ee3","trace_id":"5c938a2d-aea9-4025-a7f7-733d8e8e9711"}
[2026-05-07 11:36:27] local.INFO: [SocialAccountService] Token needs refreshing {"socialAccountId":1499,"provider":"hubspot"} {"correlation_id":"766570a3-89a2-4427-81c2-c70a7c780ee3","trace_id":"5c938a2d-aea9-4025-a7f7-733d8e8e9711"}
[2026-05-07 11:36:27] local.INFO: [EncryptedTokenManager] Generating access token. {"mode":"legacy"} {"correlation_id":"766570a3-89a2-4427-81c2-c70a7c780ee3","trace_id":"5c938a2d-aea9-4025-a7f7-733d8e8e9711"}
[2026-05-07 11:36:27] local.INFO: [SocialAccountService] Refreshing token from provider {"socialAccountId":1499,"provider":"hubspot","refreshToken":"96f94c623a404e02ebdbf07f1b75707bb6cdbf848cbf45d418baf608c41a8d86","state":"connected"} {"correlation_id":"766570a3-89a2-4427-81c2-c70a7c780ee3","trace_id":"5c938a2d-aea9-4025-a7f7-733d8e8e9711"}
[2026-05-07 11:36:27] local.INFO: [SocialAccountObserver] Saving model {"correlation_id":"766570a3-89a2-4427-81c2-c70a7c780ee3","trace_id":"5c938a2d-aea9-4025-a7f7-733d8e8e9711"}
[2026-05-07 11:36:27] local.INFO: [SocialAccountObserver] Access token was modified, encrypting {"correlation_id":"766570a3-89a2-4427-81c2-c70a7c780ee3","trace_id":"5c938a2d-aea9-4025-a7f7-733d8e8e9711"}
[2026-05-07 11:36:28] local.INFO: [SocialAccountService] Token refreshed {"socialAccountId":1499,"provider":"hubspot","state":"connected"} {"correlation_id":"766570a3-89a2-4427-81c2-c70a7c780ee3","trace_id":"5c938a2d-aea9-4025-a7f7-733d8e8e9711"}
[2026-05-07 11:36:28] local.INFO: [CrmOwnerResolver] Integration owner matched as CRM Owner {"crm_provider":"hubspot","crm_owner":148,"team_id":2} {"correlation_id":"766570a3-89a2-4427-81c2-c70a7c780ee3","trace_id":"5c938a2d-aea9-4025-a7f7-733d8e8e9711"}
[2026-05-07 11:36:28] local.INFO: [HubSpot] Syncing opportunities using strategy: lastModified {"team":2} {"correlation_id":"766570a3-89a2-4427-81c2-c70a7c780ee3","trace_id":"5c938a2d-aea9-4025-a7f7-733d8e8e9711"}
[2026-05-07 11:36:28] local.INFO: [Hubspot] Pagination completed {"team_id":2,"endpoint":"[URL_WITH_CREDENTIALS] CrmFieldOption array{id:string, label:string, value?:string}
*/
class Client extends BaseClient implements HubspotClientInterface
{
public const string MIN_API_VERSION = '2';
public const string BASE_URL = '[URL_WITH_CREDENTIALS] T
* @param callable(): T $apiCall
* @return T
*
* @throws RateLimitException
*/
private function executeRequest(callable $apiCall)
{
if (! $this->rateLimiter->canMakeRequest($this->config)) {
$retryAfter = $this->rateLimiter->requestAvailableIn($this->config);
$this->log->warning('[Hubspot] Rate limit exceeded, deferring request', [
'team_id' => $this->config->team_id,
'config_id' => $this->config->getId(),
'retry_after' => $retryAfter,
]);
throw new RateLimitException(
'Hubspot rate limit reached for configuration ' . $this->config->getId(),
$retryAfter,
);
}
$this->rateLimiter->incrementRequestCount($this->config);
try {
return $apiCall();
} catch (Throwable $e) {
if ($this->isHubspotRateLimit($e)) {
$retryAfter = $this->parseRetryAfter($e);
$this->log->warning('[Hubspot] Received 429 from API', [
'team_id' => $this->config->team_id,
'config_id' => $this->config->getId(),
'retry_after' => $retryAfter,
'reason' => $e->getMessage(),
]);
throw new RateLimitException('Hubspot returned 429', $retryAfter, $e);
}
throw $e;
}
}
private function isHubspotRateLimit(Throwable $e): bool
{
return method_exists($e, 'getCode') && (int) $e->getCode() === 429;
}
private function parseRetryAfter(Throwable $e): int
{
if (method_exists($e, 'getResponseHeaders')) {
$headers = $e->getResponseHeaders() ?: [];
$value = $headers['Retry-After'] ?? $headers['retry-after'] ?? null;
if (is_array($value)) {
$value = $value[0] ?? null;
}
if (is_numeric($value)) {
return (int) $value;
}
}
return 10;
}
public function getMinimumApiVersion(): string
{
return self::MIN_API_VERSION;
}
public function getInstance(): Factory
{
return new Factory([
'key' => $this->accessToken,
'oauth2' => true,
'base_url' => $this->baseUrl,
]);
}
public function getNewInstance(): Discovery
{
return \HubSpot\Factory::createWithAccessToken($this->accessToken);
}
/**
* Secondly and daily limits for Hubspot API
*
* Product Tier: Free & Starter | Professional & Enterprise | API add-on (any tier)
* Burst: 100/10 seconds | 150/10 seconds | 200/10 seconds
* Daily: 250,000 | 500,000 | 1,000,000
*
* Official documentation states: The search endpoints are rate limited to five requests per second.
* Since with 5 RPS were still hitting secondly rate limits we lowered it to 4
*/
public function getPaginatedData(array $payload, string $type, int $offset = 0): array
{
$total = 0;
$lastId = null;
$rows = [];
foreach ($this->getPaginatedDataGenerator($payload, $type, $offset, $total, $lastId) as $row) {
$rows[] = $row;
}
return ['results' => $rows, 'total' => $total, 'last_record' => $lastId];
}
/**
* @throws HubspotException
* @throws SocialAccountTokenInvalidException
* @throws BadRequest
*/
public function getPaginatedDataGenerator(
array $payload,
string $type,
int $offset = 0,
int &$total = 0,
?string &$lastRecordId = null
): \Generator {
return $this->paginationService->getPaginatedDataGenerator(
$this,
$payload,
$type,
$offset,
$total,
$lastRecordId
);
}
/**
* @throws DealApiException
* @throws CrmException
*/
public function getOpportunityById(string $crmId, array $fields): array
{
try {
$deal = $this->executeRequest(fn () => $this->getNewInstance()->crm()->deals()->basicApi()->getById(
$crmId,
implode(',', $fields),
'companies,contacts'
));
} catch (DealApiException $e) {
$this->log->info('[Hubspot] Failed to fetch opportunity', [
'crm_id' => $crmId,
'reason' => $e->getMessage(),
]);
throw $e;
}
if (! $deal instanceof DealWithAssociations) {
throw new CrmException('Deal not found');
}
return [
'id' => $deal->getId(),
'properties' => $deal->getProperties(),
'associations' => $deal->getAssociations(),
];
}
/**
* Generic batch read method for HubSpot objects
*
* @param string $objectType The object type ('deals', 'companies', 'contacts')
* @param array<string> $crmIds Array of HubSpot object IDs (max 100)
* @param array<string> $fields Array of property names to fetch
*
* @return array<string, array> Array keyed by CRM ID with object data
*/
private function batchReadObjects(string $objectType, array $crmIds, array $fields): array
{
if (empty($crmIds)) {
return [];
}
$this->validateBatchSize($objectType, $crmIds);
$this->ensureValidToken();
try {
$batchConfig = $this->createBatchConfiguration($objectType);
$batchReadRequest = $this->prepareBatchRequest($batchConfig, $crmIds, $fields);
$response = $batchConfig['api']->read($batchReadRequest);
$this->validateApiResponse($response, $objectType);
$results = $this->processApiResults($response);
$this->logBatchResults($objectType, $crmIds, $results);
return $results;
} catch (\Throwable $e) {
$this->handleBatchError($e, $objectType, $crmIds);
}
}
private function validateBatchSize(string $objectType, array $crmIds): void
{
if (count($crmIds) > 100) {
throw new \InvalidArgumentException("Batch size cannot exceed 100 {$objectType}");
}
}
private function createBatchConfiguration(string $objectType): array
{
$configurations = [
'deals' => [
'batchReadRequest' => new \HubSpot\Client\Crm\Deals\Model\BatchReadInputSimplePublicObjectId(),
'inputClass' => \HubSpot\Client\Crm\Deals\Model\SimplePublicObjectId::class,
'api' => $this->getNewInstance()->crm()->deals()->batchApi(),
],
'companies' => [
'batchReadRequest' => new \HubSpot\Client\Crm\Companies\Model\BatchReadInputSimplePublicObjectId(),
'inputClass' => \HubSpot\Client\Crm\Companies\Model\SimplePublicObjectId::class,
'api' => $this->getNewInstance()->crm()->companies()->batchApi(),
],
'contacts' => [
'batchReadRequest' => new \HubSpot\Client\Crm\Contacts\Model\BatchReadInputSimplePublicObjectId(),
'inputClass' => \HubSpot\Client\Crm\Contacts\Model\SimplePublicObjectId::class,
'api' => $this->getNewInstance()->crm()->contacts()->batchApi(),
],
];
if (! isset($configurations[$objectType])) {
throw new \InvalidArgumentException("Unsupported object type: {$objectType}");
}
return $configurations[$objectType];
}
private function prepareBatchRequest(array $batchConfig, array $crmIds, array $fields): object
{
$batchReadRequest = $batchConfig['batchReadRequest'];
$inputClass = $batchConfig['inputClass'];
$inputs = array_map(function ($crmId) use ($inputClass) {
$input = new $inputClass();
$input->setId($crmId);
return $input;
}, $crmIds);
$batchReadRequest->setInputs($inputs);
$batchReadRequest->setProperties($fields);
return $batchReadRequest;
}
private function validateApiResponse($response, string $objectType): void
{
if (! $response) {
throw new CrmException("HubSpot API returned null response for {$objectType} batch read");
}
}
private function processApiResults($response): array
{
$results = [];
$responseResults = $response->getResults();
if ($responseResults) {
foreach ($responseResults as $object) {
if ($object && $object->getId()) {
$results[$object->getId()] = [
'id' => $object->getId(),
'properties' => $object->getProperties() ?: [],
];
}
}
}
return $results;
}
private function logBatchResults(string $objectType, array $crmIds, array $results): void
{
$this->log->info("[HubSpot] Batch fetched {$objectType}", [
'requested_count' => count($crmIds),
'returned_count' => count($results),
'crm_ids' => $crmIds,
]);
}
private function handleBatchError(\Throwable $e, string $objectType, array $crmIds): void
{
$errorMessage = $e->getMessage() ?: 'Unknown error';
$errorTrace = $e->getTraceAsString() ?: 'No trace available';
$this->log->error("[HubSpot] Failed to batch fetch {$objectType}", [
'crm_ids' => $crmIds,
'error' => $errorMessage,
'trace' => $errorTrace,
]);
throw new CrmException("Failed to batch fetch {$objectType}: " . $errorMessage);
}
/**
* Batch read multiple opportunities by their CRM IDs
*
* @param array<string> $crmIds Array of HubSpot deal IDs (max 100)
* @param array<string> $fields Array of property names to fetch
*
* @return array<string, array> Array keyed by CRM ID with opportunity data
*/
public function getOpportunitiesByIds(array $crmIds, array $fields): array
{
return $this->batchReadObjects('deals', $crmIds, $fields);
}
/**
* Batch read multiple companies by their CRM IDs
*
* @param array<string> $crmIds Array of HubSpot company IDs (max 100)
* @param array<string> $fields Array of property names to fetch
*
* @return array<string, array> Array keyed by CRM ID with company data
*/
public function getCompaniesByIds(array $crmIds, array $fields): array
{
return $this->batchReadObjects('companies', $crmIds, $fields);
}
/**
* Batch read multiple contacts by their CRM IDs
*
* @param array<string> $crmIds Array of HubSpot contact IDs (max 100)
* @param array<string> $fields Array of property names to fetch
*
* @return array<string, array> Array keyed by CRM ID with contact data
*/
public function getContactsByIds(array $crmIds, array $fields): array
{
return $this->batchReadObjects('contacts', $crmIds, $fields);
}
/**
* @throws CompanyApiException
* @throws CrmException
*/
public function getAccountById(string $crmId, array $fields): array
{
try {
$company = $this->getNewInstance()->crm()->companies()->basicApi()->getById(
$crmId,
implode(',', $fields),
);
} catch (CompanyApiException $e) {
$this->log->info('[Hubspot] Failed to fetch account', [
'crm_id' => $crmId,
'reason' => $e->getMessage(),
]);
throw $e;
}
if (! $company instanceof CompaniesWithAssociations) {
throw new CrmException('Account not found');
}
return [
'id' => $company->getId(),
'properties' => $company->getProperties(),
];
}
/**
* @throws ContactApiException
* @throws CrmException
*/
public function getContactById(string $crmId, array $fields): array
{
try {
$contact = $this->getNewInstance()->crm()->contacts()->basicApi()->getById(
$crmId,
implode(',', $fields)
);
} catch (ContactApiException $e) {
$this->log->info('[Hubspot] Failed to fetch contact', [
'crm_id' => $crmId,
'reason' => $e->getMessage(),
]);
throw $e;
}
if (! $contact instanceof ContactsWithAssociations) {
throw new CrmException('Contact not found');
}
return [
'id' => $contact->getId(),
'properties' => $contact->getProperties(),
];
}
/**
* This is email search request that Hubspot offers as GET (more generous quota)
*/
public function getContactByEmail(string $email, array $fields = []): array
{
try {
$contact = $this->getNewInstance()->crm()->contacts()->basicApi()->getById(
$email,
implode(',', $fields),
null,
false,
'email'
);
return [
'id' => $contact->getId(),
'properties' => $contact->getProperties(),
];
} catch (ContactApiException $e) {
$this->log->info('[Hubspot] Failed to fetch contact', [
'email' => $email,
'reason' => $e->getMessage(),
]);
return [];
}
}
/**
* @throws CrmException
*/
public function fetchProperty(string $objectType, string $propertyId): Property
{
$result = $this->getNewInstance()->crm()->properties()->coreApi()->getByName($objectType, $propertyId);
if (! $result instanceof Property) {
$this->log->error('[Hubspot] Failed to fetch property', [
'object_type' => $objectType,
'property_id' => $propertyId,
'reason' => $result->getMessage(),
]);
throw new CrmException('Failed to fetch property');
}
return $result;
}
/**
* @return array<CrmFieldOption>
*/
public function fetchPropertyOptions(string $objectType, string $propertyId): array
{
/** @var array<CrmFieldOption> */
return $this->fetchProperty($objectType, $propertyId)->getOptions();
}
/**
* @return array<array{id:string, label:string, deleted:bool}>
*/
public function fetchCallDispositions(): array
{
/** @var Response $response */
$response = $this->getInstance()->engagements()->getCallDispositions();
/**
* @var array<array{
* id:string,
* label:string,
* deleted: bool
* }>
*/
return $response->toArray();
}
/**
* @return array<CrmFieldOption>
*/
public function fetchOpportunityPipelineStages(): array
{
$stages = [];
$apiResponse = $this->getNewInstance()->crm()->pipelines()->pipelinesApi()->getAll('deals');
if ($apiResponse instanceof Error) {
$this->log->error('[Hubspot] Failed to fetch opportunity pipelines', [
'reason' => $apiResponse->getMessage(),
]);
return [];
}
foreach ($apiResponse->getResults() as $pipeline) {
$pipelineStages = array_map(
static function (PipelineStage $stage) {
return [
'id' => $stage->getId(),
'label' => $stage->getLabel(),
];
},
$pipeline->getStages()
);
$stages = array_merge($stages, $pipelineStages);
}
return $stages;
}
public function fetchOpportunityPipelines(): array
{
$pipelines = [];
try {
$apiResponse = $this->makeRequest('/crm/v3/pipelines/deals');
} catch (\Exception $e) {
$this->log->info('[Hubspot] Failed to fetch opportunity pipelines', [
'reason' => $e->getMessage(),
]);
return [];
}
$response = $apiResponse->toArray();
foreach ($response['results'] as $pipeline) {
$pipelines[] = [
'id' => $pipeline['id'],
'label' => $pipeline['label'],
];
}
return $pipelines;
}
/**
* @return array<CrmFieldOption>
*/
public function fetchMeetingOutcomeFieldOptions(Field $field): array
{
return $field->getCrmProviderId() === 'meetingOutcome'
? $this->fetchMeetingOutcomeTypes()
: $this->fetchCallActivityTypes();
}
public function fetchMeetingOutcomeTypes(): array
{
return $this->extractMeetingTypeOptions(
'[URL_WITH_CREDENTIALS] Response $response */
$response = $this->getInstance()
->getClient()
->request('GET', $endpoint);
/**
* @var array<array{
* value: string,
* label: string,
* displayOrder: int
* }> $optionData
*/
$optionData = $response->toArray()['options'] ?? [];
$options = [];
foreach ($optionData as $item) {
$options[] = [
'id' => $item['value'],
'value' => $item['value'],
'label' => $item['label'],
'display_order' => $item['displayOrder'],
];
}
return $options;
}
/**
* @return array<CrmFieldOption>
*/
public function fetchDispositionFieldOptions(): array
{
$options = [];
$dispositions = $this->fetchCallDispositions();
foreach ($dispositions as $disposition) {
if ($disposition['deleted'] !== false) {
continue;
}
$option['value'] = $disposition['id'];
$option['id'] = $disposition['id'];
$option['label'] = $disposition['label'];
$options[] = $option;
}
return $options;
}
/**
* @return array<CrmFieldOption>
*/
public function fetchOpportunityFieldOptions(Field $field): array
{
if ($field->isStageField()) {
return $this->fetchOpportunityPipelineStages();
}
if ($field->isPipelineField()) {
return $this->fetchOpportunityPipelines();
}
return $this->fetchPropertyOptions('deals', $field->getCrmProviderId());
}
/**
* @throws BadRequest
* @throws HubspotException
*/
public function makeRequest(string $endpoint, $method = 'GET', $payload = [], ?string $queryString = null)
{
$endpoint = self::BASE_URL . $endpoint;
if ($method === 'GET') {
$response = $this->getInstance()->getClient()?->request(
method: $method,
endpoint: $endpoint,
query_string: $queryString
);
} else {
$response = $this->getInstance()->getClient()->request($method, $endpoint, [
'json' => ($payload),
]);
}
$max = $response->getHeaderLine('X-HubSpot-RateLimit-Max'); // "110"
$remaining = $response->getHeaderLine('X-HubSpot-RateLimit-Remaining'); // "109"
$interval = $response->getHeaderLine('X-HubSpot-RateLimit-Interval-Milliseconds'); // "10000"
$body = json_decode((string) $response->getBody(), true);
\Illuminate\Support\Facades\Log::channel('custom_channel')->info('$max ' . PHP_EOL . print_r($max, true));
\Illuminate\Support\Facades\Log::channel('custom_channel')->info('$remaining ' . PHP_EOL . print_r($remaining, true));
\Illuminate\Support\Facades\Log::channel('custom_channel')->info('$interval ' . PHP_EOL . print_r($interval, true));
\Illuminate\Support\Facades\Log::channel('custom_channel')->info('$body ' . PHP_EOL . print_r($body, true));
return $response;
}
/**
* @throws BadRequest
* @throws HubspotException
*/
public function createMeeting(array $payload): Response
{
$endpoint = '/crm/v3/objects/meetings';
return $this->makeRequest($endpoint, 'POST', $payload);
}
/**
* @throws BadRequest
* @throws HubspotException
*/
public function updateMeeting(string $meetingId, array $payload): Response
{
$endpoint = '/crm/v3/objects/meetings/' . $meetingId;
return $this->makeRequest($endpoint, 'PATCH', $payload);
}
/**
* @throws \Exception
*/
public function createNote(
string $body,
string $ownerId,
int $timestamp,
string $objectId,
NoteObject $noteObject
): ?string {
try {
$noteInput = new SimplePublicObjectInput([
'properties' => [
'hs_note_body' => $body,
'hubspot_owner_id' => $ownerId,
'hs_timestamp' => $timestamp,
],
]);
// Create note
$note = $this->getNewInstance()->crm()->objects()->basicApi()->create('note', $noteInput);
$this->getNewInstance()->crm()->objects()->associationsApi()->create(
'note',
$note->getId(),
$this->getNoteObject($noteObject),
$objectId,
$this->getNoteAssociationType($noteObject),
);
return $note->getId();
} catch (\Exception $e) {
$this->log->error('[Hubspot] Failed to create note', [
'objectId' => $objectId,
'noteObject' => $noteObject->getObjectType(),
'reason' => $e->getMessage(),
]);
\Sentry::captureException($e);
}
return null;
}
public function updateEngagement(string $objectId, array $engagement, array $metadata): void
{
$this->getInstance()->engagements()->update($objectId, $engagement, $metadata);
}
public function getEngagementData(string $engagementId): array
{
$engagement = $this->getInstance()->engagements()->get($engagementId);
return $engagement->toArray();
}
public function createEngagement(array $engagement, array $associations, array $metadata): Response
{
return $this->getInstance()
->engagements()
->create($engagement, $associations, $metadata);
}
public function isUnauthorizedException(\Exception $e): bool
{
// Check for specific HubSpot API exception types first
if ($e instanceof BadRequest) {
// BadRequest can contain 401 status codes
return $e->getCode() === 401;
}
// Check for HTTP client exceptions with status codes
if ($e instanceof \GuzzleHttp\Exception\RequestException && $e->hasResponse()) {
$response = $e->getResponse();
if ($response !== null) {
return $response->getStatusCode() === 401;
}
}
// Check for Guzzle HTTP exceptions
if ($e instanceof \GuzzleHttp\Exception\ClientException) {
return $e->getCode() === 401;
}
// Fallback to string matching as last resort, but be more specific
$message = strtolower($e->getMessage());
return str_contains($message, '401 unauthorized') ||
str_contains($message, 'http 401') ||
str_contains($message, 'status code 401') ||
(preg_match('/\b401\b/', $message) && str_contains($message, 'unauthorized'));
}
/**
* Validates and refreshes the access token if needed before API requests.
* This ensures long-running processes don't fail due to token expiration.
*
* @throws SocialAccountTokenInvalidException
*/
public function ensureValidToken(): void
{
if ($this->oauthAccount === null) {
return;
}
$newToken = $this->tokenManager->ensureValidToken($this->oauthAccount);
if ($newToken !== null) {
$this->accessToken = $newToken;
}
}
public function getConfig()
{
return $this->config;
}
// returns only active (archived=false)
public function getOwners(): array
{
return $this->getNewInstance()->crm()->owners()->getAll();
}
/**
* @param bool $archived
*
* @return array<Owner>|[]
*/
public function getOwnersArchived(bool $archived = true): array
{
$endpoint = '/crm/v3/owners';
$queryParams = [
'archived' => $archived ? 'true' : 'false',
];
$queryString = http_build_query($queryParams);
$owners = [];
try {
$response = $this->makeRequest(endpoint: $endpoint, queryString: $queryString);
$responseData = $response?->toArray();
foreach ($responseData['results'] as $result) {
try {
$owners[] = Owner::create($result);
} catch (Throwable $e) {
$this->log->error('[HubSpot] Failed to process owner data', [
'result' => $result,
'error' => $e->getMessage(),
]);
continue;
}
}
} catch (Throwable $e) {
$this->log->error('HubSpot] Failed to fetch owners', [
'archived' => $archived,
'error' => $e->getMessage(),
]);
return [];
}
return $owners;
}
public function getMeeting(string $engagementId): ObjectWithAssociations
{
return $this->getNewInstance()->crm()->objects()->basicApi()
->getById('meeting', $engagementId, null, 'contact,company,deal');
}
public function deleteEngagement(string $engagementId): void
{
$this->getInstance()->engagements()->delete((int) $engagementId);
}
public function getAssociationsData(array $ids, string $fromObject, string $toObject): array
{
$associationData = [];
$idChunks = array_chunk($ids, self::ASSOCIATIONS_BATCH_SIZE_LIMIT);
foreach ($idChunks as $idChunk) {
try {
$batchInput = new \HubSpot\Client\Crm\Associations\Model\BatchInputPublicObjectId();
$batchInput->setInputs(array_map(function ($id) {
$publicObjectId = new \HubSpot\Client\Crm\Associations\Model\PublicObjectId();
$publicObjectId->setId($id);
return $publicObjectId;
}, $idChunk));
$associatedObjectsData = $this
->getNewInstance()
->crm()
->associations()
->batchApi()
->read($fromObject, $toObject, $batchInput);
if ($associatedObjectsData instanceof \HubSpot\Client\Crm\Associations\Model\BatchResponsePublicAssociationMulti) {
foreach ($associatedObjectsData->getResults() as $association) {
$from = $association->getFrom()->getId();
$toAssociations = $association->getTo();
if (! empty($toAssociations)) {
$associationData[$from] = array_map(function ($item) {
return $item->getId();
}, $toAssociations);
}
}
}
} catch (\Exception $e) {
$this->log->error('[Hubspot] Failed to fetch associations', [
'from_object' => $fromObject,
'to_object' => $toObject,
'reason' => $e->getMessage(),
]);
}
}
return $associationData;
}
/**
* @throws \Exception
*/
private function getNoteAssociationType(NoteObject $noteObject): string
{
return match($noteObject) {
NoteObject::Opportunity => 'note_to_deal',
NoteObject::Lead, NoteObject::Contact => 'note_to_contact', // or 'note_to_lead' if your portal supports it
NoteObject::Account => 'note_to_company',
NoteObject::Call, NoteObject::Event => throw new \Exception('Not supported'),
};
}
/**
* @throws \Exception
*/
private function getNoteObject(NoteObject $noteObject): string
{
return match($noteObject) {
NoteObject::Opportunity => 'deal',
NoteObject::Lead, NoteObject::Contact => 'contact',
NoteObject::Account => 'company',
NoteObject::Call, NoteObject::Event => throw new \Exception('Not supported'),
};
}
public function addAssociations(string $objectType, string $associationType, array $payload): Response
{
$endpoint = "/crm/v4/associations/$objectType/$associationType/batch/create";
return $this->makeRequest($endpoint, 'POST', $payload);
}
public function removeAssociations(string $objectType, string $associationType, array $payload): Response
{
$endpoint = "/crm/v4/associations/$objectType/$associationType/batch/archive";
return $this->makeRequest($endpoint, 'POST', $payload);
}
}
Project
Project
New File or Directory…
Expand Selected
Collapse All
Options
Hide...
|
[{"role":"AXButton","text" [{"role":"AXButton","text":"Project: faVsco.js, menu","depth":5,"on_screen":true,"help_text":"~/jiminny/app","role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"master, menu","depth":5,"on_screen":true,"help_text":"Git Branch: master","role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Start Listening for PHP Debug Connections","depth":5,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"AskJiminnyReportActivityServiceTest","depth":6,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Run 'AskJiminnyReportActivityServiceTest'","depth":6,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Debug 'AskJiminnyReportActivityServiceTest'","depth":6,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"More Actions","depth":6,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"JetBrains AI","depth":5,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Search Everywhere","depth":5,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"IDE and Project Settings","depth":5,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXTextArea","text":"[2026-05-07 11:36:26] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage before starting command {\"command\":\"crm:sync-hubspot-objects\",\"memoryBeforeCommandInMb\":60.0,\"memoryPeakBeforeCommandInMb\":99.727} {\"correlation_id\":\"f71ac7c4-e9d4-46ad-be82-47e68a8f1199\",\"trace_id\":\"5c938a2d-aea9-4025-a7f7-733d8e8e9711\"}\n[2026-05-07 11:36:26] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage for command {\"command\":\"crm:sync-hubspot-objects\",\"memoryBeforeCommandInMb\":60.0,\"memoryAfterCommandInMB\":62.0,\"memoryPeakBeforeCommandInMb\":99.727,\"memoryPeakAfterCommandInMB\":99.727} {\"correlation_id\":\"f71ac7c4-e9d4-46ad-be82-47e68a8f1199\",\"trace_id\":\"5c938a2d-aea9-4025-a7f7-733d8e8e9711\"}\n[2026-05-07 11:36:27] local.INFO: [SyncHubspotObjects] Starting sync {\"team\":\"abae74b8-bfa8-4383-9a7f-89f4bf2bdbb4\",\"usage\":24412984,\"real_usage\":65011712,\"pid\":13732} {\"correlation_id\":\"766570a3-89a2-4427-81c2-c70a7c780ee3\",\"trace_id\":\"5c938a2d-aea9-4025-a7f7-733d8e8e9711\"}\n[2026-05-07 11:36:27] local.INFO: [SocialAccountService] Fetching token {\"socialAccountId\":1499,\"provider\":\"hubspot\"} {\"correlation_id\":\"766570a3-89a2-4427-81c2-c70a7c780ee3\",\"trace_id\":\"5c938a2d-aea9-4025-a7f7-733d8e8e9711\"}\n[2026-05-07 11:36:27] local.INFO: [SocialAccountService] Token needs refreshing {\"socialAccountId\":1499,\"provider\":\"hubspot\"} {\"correlation_id\":\"766570a3-89a2-4427-81c2-c70a7c780ee3\",\"trace_id\":\"5c938a2d-aea9-4025-a7f7-733d8e8e9711\"}\n[2026-05-07 11:36:27] local.INFO: [EncryptedTokenManager] Generating access token. {\"mode\":\"legacy\"} {\"correlation_id\":\"766570a3-89a2-4427-81c2-c70a7c780ee3\",\"trace_id\":\"5c938a2d-aea9-4025-a7f7-733d8e8e9711\"}\n[2026-05-07 11:36:27] local.INFO: [SocialAccountService] Refreshing token from provider {\"socialAccountId\":1499,\"provider\":\"hubspot\",\"refreshToken\":\"96f94c623a404e02ebdbf07f1b75707bb6cdbf848cbf45d418baf608c41a8d86\",\"state\":\"connected\"} {\"correlation_id\":\"766570a3-89a2-4427-81c2-c70a7c780ee3\",\"trace_id\":\"5c938a2d-aea9-4025-a7f7-733d8e8e9711\"}\n[2026-05-07 11:36:27] local.INFO: [SocialAccountObserver] Saving model {\"correlation_id\":\"766570a3-89a2-4427-81c2-c70a7c780ee3\",\"trace_id\":\"5c938a2d-aea9-4025-a7f7-733d8e8e9711\"}\n[2026-05-07 11:36:27] local.INFO: [SocialAccountObserver] Access token was modified, encrypting {\"correlation_id\":\"766570a3-89a2-4427-81c2-c70a7c780ee3\",\"trace_id\":\"5c938a2d-aea9-4025-a7f7-733d8e8e9711\"}\n[2026-05-07 11:36:28] local.INFO: [SocialAccountService] Token refreshed {\"socialAccountId\":1499,\"provider\":\"hubspot\",\"state\":\"connected\"} {\"correlation_id\":\"766570a3-89a2-4427-81c2-c70a7c780ee3\",\"trace_id\":\"5c938a2d-aea9-4025-a7f7-733d8e8e9711\"}\n[2026-05-07 11:36:28] local.INFO: [CrmOwnerResolver] Integration owner matched as CRM Owner {\"crm_provider\":\"hubspot\",\"crm_owner\":148,\"team_id\":2} {\"correlation_id\":\"766570a3-89a2-4427-81c2-c70a7c780ee3\",\"trace_id\":\"5c938a2d-aea9-4025-a7f7-733d8e8e9711\"}\n[2026-05-07 11:36:28] local.INFO: [HubSpot] Syncing opportunities using strategy: lastModified {\"team\":2} {\"correlation_id\":\"766570a3-89a2-4427-81c2-c70a7c780ee3\",\"trace_id\":\"5c938a2d-aea9-4025-a7f7-733d8e8e9711\"}\n[2026-05-07 11:36:28] local.INFO: [Hubspot] Pagination completed {\"team_id\":2,\"endpoint\":\"https://api.hubapi.com/crm/v3/objects/deals/search\",\"total_requests\":1,\"total_records_fetched\":0,\"total_elapsed_seconds\":0.65,\"average_seconds_per_request\":0.65} {\"correlation_id\":\"766570a3-89a2-4427-81c2-c70a7c780ee3\",\"trace_id\":\"5c938a2d-aea9-4025-a7f7-733d8e8e9711\"}\n[2026-05-07 11:36:28] local.INFO: [HubSpot] Synced opportunities {\"team\":2,\"strategies\":\"lastModified\",\"sync_count\":0,\"total\":0,\"last_synced_id\":null,\"duration_ms\":682.84} {\"correlation_id\":\"766570a3-89a2-4427-81c2-c70a7c780ee3\",\"trace_id\":\"5c938a2d-aea9-4025-a7f7-733d8e8e9711\"}\n[2026-05-07 11:36:28] local.INFO: [SyncHubspotObjects] Sync finished {\"team\":\"abae74b8-bfa8-4383-9a7f-89f4bf2bdbb4\",\"provider\":\"hubspot\",\"status\":\"completed\",\"duration_ms\":1640.35,\"usage\":24648760,\"real_usage\":65011712,\"pid\":13732} {\"correlation_id\":\"766570a3-89a2-4427-81c2-c70a7c780ee3\",\"trace_id\":\"5c938a2d-aea9-4025-a7f7-733d8e8e9711\"}\n[2026-05-07 11:36:28] local.INFO: [SyncHubspotObjects] Starting sync {\"team\":\"b2b115eb-93ce-4d1b-929c-173757df8fba\",\"usage\":24623496,\"real_usage\":65011712,\"pid\":13732} {\"correlation_id\":\"cb3b51ca-fd95-4a3f-8366-1dedc565b784\",\"trace_id\":\"5c938a2d-aea9-4025-a7f7-733d8e8e9711\"}\n[2026-05-07 11:36:28] local.WARNING: [HubSpot] Account not connected for user {\"userId\":\"33e34a7a-1c02-4f04-87ac-22c3a385e6e3\",\"account\":{\"Jiminny\\\\Models\\\\SocialAccount\":{\"id\":306,\"sociable_id\":109,\"provider_user_id\":\"11348452\",\"expires\":1701077403,\"refresh_token_expires\":null,\"provider\":\"hubspot\",\"state\":\"full-refresh\",\"auth_scope\":null,\"retry_after\":null,\"created_at\":\"2020-09-01 16:59:04\",\"updated_at\":\"2023-11-27 09:30:03\"}}} {\"correlation_id\":\"cb3b51ca-fd95-4a3f-8366-1dedc565b784\",\"trace_id\":\"5c938a2d-aea9-4025-a7f7-733d8e8e9711\"}\n[2026-05-07 11:36:28] local.INFO: [CrmOwnerResolver] Integration owner is not connected, attempting team members {\"crm_provider\":\"hubspot\",\"crm_owner\":109,\"team_id\":29} {\"correlation_id\":\"cb3b51ca-fd95-4a3f-8366-1dedc565b784\",\"trace_id\":\"5c938a2d-aea9-4025-a7f7-733d8e8e9711\"}\n[2026-05-07 11:36:28] local.INFO: [CrmOwnerResolver] No team members found with active crm connection {\"crm_provider\":\"hubspot\",\"team_id\":29} {\"correlation_id\":\"cb3b51ca-fd95-4a3f-8366-1dedc565b784\",\"trace_id\":\"5c938a2d-aea9-4025-a7f7-733d8e8e9711\"}\n[2026-05-07 11:36:28] local.INFO: [CrmOwnerResolver] No team member found with active crm connection {\"crm_provider\":\"hubspot\",\"team_id\":29} {\"correlation_id\":\"cb3b51ca-fd95-4a3f-8366-1dedc565b784\",\"trace_id\":\"5c938a2d-aea9-4025-a7f7-733d8e8e9711\"}\n[2026-05-07 11:36:28] local.INFO: [SyncHubspotObjects] Sync finished {\"team\":\"b2b115eb-93ce-4d1b-929c-173757df8fba\",\"provider\":\"hubspot\",\"status\":\"disconnected\",\"duration_ms\":64.6,\"usage\":24516544,\"real_usage\":65011712,\"pid\":13732,\"reason\":\"Your HubSpot account has become disconnected. Please login to Jiminny to reconnect.\"} {\"correlation_id\":\"cb3b51ca-fd95-4a3f-8366-1dedc565b784\",\"trace_id\":\"5c938a2d-aea9-4025-a7f7-733d8e8e9711\"}\n[2026-05-07 11:36:28] local.INFO: [SyncHubspotObjects] Starting sync {\"team\":\"c6b9d6b0-b48d-4832-a68c-a57d60651888\",\"usage\":24554912,\"real_usage\":65011712,\"pid\":13732} {\"correlation_id\":\"b5acfa3a-a5cb-43e0-8757-36cc6bc0da54\",\"trace_id\":\"5c938a2d-aea9-4025-a7f7-733d8e8e9711\"}\n[2026-05-07 11:36:28] local.WARNING: [HubSpot] Account not connected for user {\"userId\":\"71e3aac5-fb66-47c5-a236-2d051ae3e319\",\"account\":null} {\"correlation_id\":\"b5acfa3a-a5cb-43e0-8757-36cc6bc0da54\",\"trace_id\":\"5c938a2d-aea9-4025-a7f7-733d8e8e9711\"}\n[2026-05-07 11:36:28] local.INFO: [CrmOwnerResolver] Integration owner is not connected, attempting team members {\"crm_provider\":\"hubspot\",\"crm_owner\":256,\"team_id\":49} {\"correlation_id\":\"b5acfa3a-a5cb-43e0-8757-36cc6bc0da54\",\"trace_id\":\"5c938a2d-aea9-4025-a7f7-733d8e8e9711\"}\n[2026-05-07 11:36:28] local.INFO: [CrmOwnerResolver] No team members found with active crm connection {\"crm_provider\":\"hubspot\",\"team_id\":49} {\"correlation_id\":\"b5acfa3a-a5cb-43e0-8757-36cc6bc0da54\",\"trace_id\":\"5c938a2d-aea9-4025-a7f7-733d8e8e9711\"}\n[2026-05-07 11:36:28] local.INFO: [CrmOwnerResolver] No team member found with active crm connection {\"crm_provider\":\"hubspot\",\"team_id\":49} {\"correlation_id\":\"b5acfa3a-a5cb-43e0-8757-36cc6bc0da54\",\"trace_id\":\"5c938a2d-aea9-4025-a7f7-733d8e8e9711\"}\n[2026-05-07 11:36:28] local.INFO: [SyncHubspotObjects] Sync finished {\"team\":\"c6b9d6b0-b48d-4832-a68c-a57d60651888\",\"provider\":\"hubspot\",\"status\":\"disconnected\",\"duration_ms\":81.21,\"usage\":24510344,\"real_usage\":65011712,\"pid\":13732,\"reason\":\"Social account for HubSpot cannot be found. Please login to Jiminny to connect.\"} {\"correlation_id\":\"b5acfa3a-a5cb-43e0-8757-36cc6bc0da54\",\"trace_id\":\"5c938a2d-aea9-4025-a7f7-733d8e8e9711\"}\n[2026-05-07 11:36:29] local.INFO: [SyncHubspotObjects] Starting sync {\"team\":\"b2d49a54-b645-4637-a7ae-a86cfce6e8e4\",\"usage\":24548712,\"real_usage\":65011712,\"pid\":13732} {\"correlation_id\":\"a40d012c-b10d-4de7-9ea1-ef68239f712b\",\"trace_id\":\"5c938a2d-aea9-4025-a7f7-733d8e8e9711\"}\n[2026-05-07 11:36:29] local.WARNING: [HubSpot] Account not connected for user {\"userId\":\"2ac0447f-3c8c-4ce0-baeb-b63ddb76fa9b\",\"account\":null} {\"correlation_id\":\"a40d012c-b10d-4de7-9ea1-ef68239f712b\",\"trace_id\":\"5c938a2d-aea9-4025-a7f7-733d8e8e9711\"}\n[2026-05-07 11:36:29] local.INFO: [CrmOwnerResolver] Integration owner is not connected, attempting team members {\"crm_provider\":\"hubspot\",\"crm_owner\":130,\"team_id\":42} {\"correlation_id\":\"a40d012c-b10d-4de7-9ea1-ef68239f712b\",\"trace_id\":\"5c938a2d-aea9-4025-a7f7-733d8e8e9711\"}\n[2026-05-07 11:36:29] local.INFO: [CrmOwnerResolver] No team members found with active crm connection {\"crm_provider\":\"hubspot\",\"team_id\":42} {\"correlation_id\":\"a40d012c-b10d-4de7-9ea1-ef68239f712b\",\"trace_id\":\"5c938a2d-aea9-4025-a7f7-733d8e8e9711\"}\n[2026-05-07 11:36:29] local.INFO: [CrmOwnerResolver] No team member found with active crm connection {\"crm_provider\":\"hubspot\",\"team_id\":42} {\"correlation_id\":\"a40d012c-b10d-4de7-9ea1-ef68239f712b\",\"trace_id\":\"5c938a2d-aea9-4025-a7f7-733d8e8e9711\"}\n[2026-05-07 11:36:29] local.INFO: [SyncHubspotObjects] Sync finished {\"team\":\"b2d49a54-b645-4637-a7ae-a86cfce6e8e4\",\"provider\":\"hubspot\",\"status\":\"disconnected\",\"duration_ms\":77.05,\"usage\":24483760,\"real_usage\":65011712,\"pid\":13732,\"reason\":\"Social account for HubSpot cannot be found. Please login to Jiminny to connect.\"} {\"correlation_id\":\"a40d012c-b10d-4de7-9ea1-ef68239f712b\",\"trace_id\":\"5c938a2d-aea9-4025-a7f7-733d8e8e9711\"}\n[2026-05-07 11:36:31] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage before starting command {\"command\":\"activity:notify-not-logged\",\"memoryBeforeCommandInMb\":60.0,\"memoryPeakBeforeCommandInMb\":99.727} {\"correlation_id\":\"cd860d87-3b37-4cc4-b4bc-7788e90ccadc\",\"trace_id\":\"ecfea9bc-0fe5-4224-ba87-0b84ebb94d35\"}\n[2026-05-07 11:36:31] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage for command {\"command\":\"activity:notify-not-logged\",\"memoryBeforeCommandInMb\":60.0,\"memoryAfterCommandInMB\":62.0,\"memoryPeakBeforeCommandInMb\":99.727,\"memoryPeakAfterCommandInMB\":99.727} {\"correlation_id\":\"cd860d87-3b37-4cc4-b4bc-7788e90ccadc\",\"trace_id\":\"ecfea9bc-0fe5-4224-ba87-0b84ebb94d35\"}\n[2026-05-07 11:36:34] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage before starting command {\"command\":\"mailbox:sync\",\"memoryBeforeCommandInMb\":60.0,\"memoryPeakBeforeCommandInMb\":99.727} {\"correlation_id\":\"a179c5a9-e51e-4929-b0a8-717068ede438\",\"trace_id\":\"e12b0454-0027-4fce-8a8f-8572b5c2a51f\"}\n[2026-05-07 11:36:34] local.INFO: [EmailSchedule] STARTING Inbox Sync {\"host\":\"docker_lamp_1\"} {\"correlation_id\":\"a179c5a9-e51e-4929-b0a8-717068ede438\",\"trace_id\":\"e12b0454-0027-4fce-8a8f-8572b5c2a51f\"}\n[2026-05-07 11:36:34] local.INFO: [EmailSchedule] FINISHED Inbox Sync {\"host\":\"docker_lamp_1\",\"events\":2} {\"correlation_id\":\"a179c5a9-e51e-4929-b0a8-717068ede438\",\"trace_id\":\"e12b0454-0027-4fce-8a8f-8572b5c2a51f\"}\n[2026-05-07 11:36:34] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage for command {\"command\":\"mailbox:sync\",\"memoryBeforeCommandInMb\":60.0,\"memoryAfterCommandInMB\":62.0,\"memoryPeakBeforeCommandInMb\":99.727,\"memoryPeakAfterCommandInMB\":99.727} {\"correlation_id\":\"a179c5a9-e51e-4929-b0a8-717068ede438\",\"trace_id\":\"e12b0454-0027-4fce-8a8f-8572b5c2a51f\"}","depth":4,"on_screen":true,"value":"[2026-05-07 11:36:26] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage before starting command {\"command\":\"crm:sync-hubspot-objects\",\"memoryBeforeCommandInMb\":60.0,\"memoryPeakBeforeCommandInMb\":99.727} {\"correlation_id\":\"f71ac7c4-e9d4-46ad-be82-47e68a8f1199\",\"trace_id\":\"5c938a2d-aea9-4025-a7f7-733d8e8e9711\"}\n[2026-05-07 11:36:26] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage for command {\"command\":\"crm:sync-hubspot-objects\",\"memoryBeforeCommandInMb\":60.0,\"memoryAfterCommandInMB\":62.0,\"memoryPeakBeforeCommandInMb\":99.727,\"memoryPeakAfterCommandInMB\":99.727} {\"correlation_id\":\"f71ac7c4-e9d4-46ad-be82-47e68a8f1199\",\"trace_id\":\"5c938a2d-aea9-4025-a7f7-733d8e8e9711\"}\n[2026-05-07 11:36:27] local.INFO: [SyncHubspotObjects] Starting sync {\"team\":\"abae74b8-bfa8-4383-9a7f-89f4bf2bdbb4\",\"usage\":24412984,\"real_usage\":65011712,\"pid\":13732} {\"correlation_id\":\"766570a3-89a2-4427-81c2-c70a7c780ee3\",\"trace_id\":\"5c938a2d-aea9-4025-a7f7-733d8e8e9711\"}\n[2026-05-07 11:36:27] local.INFO: [SocialAccountService] Fetching token {\"socialAccountId\":1499,\"provider\":\"hubspot\"} {\"correlation_id\":\"766570a3-89a2-4427-81c2-c70a7c780ee3\",\"trace_id\":\"5c938a2d-aea9-4025-a7f7-733d8e8e9711\"}\n[2026-05-07 11:36:27] local.INFO: [SocialAccountService] Token needs refreshing {\"socialAccountId\":1499,\"provider\":\"hubspot\"} {\"correlation_id\":\"766570a3-89a2-4427-81c2-c70a7c780ee3\",\"trace_id\":\"5c938a2d-aea9-4025-a7f7-733d8e8e9711\"}\n[2026-05-07 11:36:27] local.INFO: [EncryptedTokenManager] Generating access token. {\"mode\":\"legacy\"} {\"correlation_id\":\"766570a3-89a2-4427-81c2-c70a7c780ee3\",\"trace_id\":\"5c938a2d-aea9-4025-a7f7-733d8e8e9711\"}\n[2026-05-07 11:36:27] local.INFO: [SocialAccountService] Refreshing token from provider {\"socialAccountId\":1499,\"provider\":\"hubspot\",\"refreshToken\":\"96f94c623a404e02ebdbf07f1b75707bb6cdbf848cbf45d418baf608c41a8d86\",\"state\":\"connected\"} {\"correlation_id\":\"766570a3-89a2-4427-81c2-c70a7c780ee3\",\"trace_id\":\"5c938a2d-aea9-4025-a7f7-733d8e8e9711\"}\n[2026-05-07 11:36:27] local.INFO: [SocialAccountObserver] Saving model {\"correlation_id\":\"766570a3-89a2-4427-81c2-c70a7c780ee3\",\"trace_id\":\"5c938a2d-aea9-4025-a7f7-733d8e8e9711\"}\n[2026-05-07 11:36:27] local.INFO: [SocialAccountObserver] Access token was modified, encrypting {\"correlation_id\":\"766570a3-89a2-4427-81c2-c70a7c780ee3\",\"trace_id\":\"5c938a2d-aea9-4025-a7f7-733d8e8e9711\"}\n[2026-05-07 11:36:28] local.INFO: [SocialAccountService] Token refreshed {\"socialAccountId\":1499,\"provider\":\"hubspot\",\"state\":\"connected\"} {\"correlation_id\":\"766570a3-89a2-4427-81c2-c70a7c780ee3\",\"trace_id\":\"5c938a2d-aea9-4025-a7f7-733d8e8e9711\"}\n[2026-05-07 11:36:28] local.INFO: [CrmOwnerResolver] Integration owner matched as CRM Owner {\"crm_provider\":\"hubspot\",\"crm_owner\":148,\"team_id\":2} {\"correlation_id\":\"766570a3-89a2-4427-81c2-c70a7c780ee3\",\"trace_id\":\"5c938a2d-aea9-4025-a7f7-733d8e8e9711\"}\n[2026-05-07 11:36:28] local.INFO: [HubSpot] Syncing opportunities using strategy: lastModified {\"team\":2} {\"correlation_id\":\"766570a3-89a2-4427-81c2-c70a7c780ee3\",\"trace_id\":\"5c938a2d-aea9-4025-a7f7-733d8e8e9711\"}\n[2026-05-07 11:36:28] local.INFO: [Hubspot] Pagination completed {\"team_id\":2,\"endpoint\":\"https://api.hubapi.com/crm/v3/objects/deals/search\",\"total_requests\":1,\"total_records_fetched\":0,\"total_elapsed_seconds\":0.65,\"average_seconds_per_request\":0.65} {\"correlation_id\":\"766570a3-89a2-4427-81c2-c70a7c780ee3\",\"trace_id\":\"5c938a2d-aea9-4025-a7f7-733d8e8e9711\"}\n[2026-05-07 11:36:28] local.INFO: [HubSpot] Synced opportunities {\"team\":2,\"strategies\":\"lastModified\",\"sync_count\":0,\"total\":0,\"last_synced_id\":null,\"duration_ms\":682.84} {\"correlation_id\":\"766570a3-89a2-4427-81c2-c70a7c780ee3\",\"trace_id\":\"5c938a2d-aea9-4025-a7f7-733d8e8e9711\"}\n[2026-05-07 11:36:28] local.INFO: [SyncHubspotObjects] Sync finished {\"team\":\"abae74b8-bfa8-4383-9a7f-89f4bf2bdbb4\",\"provider\":\"hubspot\",\"status\":\"completed\",\"duration_ms\":1640.35,\"usage\":24648760,\"real_usage\":65011712,\"pid\":13732} {\"correlation_id\":\"766570a3-89a2-4427-81c2-c70a7c780ee3\",\"trace_id\":\"5c938a2d-aea9-4025-a7f7-733d8e8e9711\"}\n[2026-05-07 11:36:28] local.INFO: [SyncHubspotObjects] Starting sync {\"team\":\"b2b115eb-93ce-4d1b-929c-173757df8fba\",\"usage\":24623496,\"real_usage\":65011712,\"pid\":13732} {\"correlation_id\":\"cb3b51ca-fd95-4a3f-8366-1dedc565b784\",\"trace_id\":\"5c938a2d-aea9-4025-a7f7-733d8e8e9711\"}\n[2026-05-07 11:36:28] local.WARNING: [HubSpot] Account not connected for user {\"userId\":\"33e34a7a-1c02-4f04-87ac-22c3a385e6e3\",\"account\":{\"Jiminny\\\\Models\\\\SocialAccount\":{\"id\":306,\"sociable_id\":109,\"provider_user_id\":\"11348452\",\"expires\":1701077403,\"refresh_token_expires\":null,\"provider\":\"hubspot\",\"state\":\"full-refresh\",\"auth_scope\":null,\"retry_after\":null,\"created_at\":\"2020-09-01 16:59:04\",\"updated_at\":\"2023-11-27 09:30:03\"}}} {\"correlation_id\":\"cb3b51ca-fd95-4a3f-8366-1dedc565b784\",\"trace_id\":\"5c938a2d-aea9-4025-a7f7-733d8e8e9711\"}\n[2026-05-07 11:36:28] local.INFO: [CrmOwnerResolver] Integration owner is not connected, attempting team members {\"crm_provider\":\"hubspot\",\"crm_owner\":109,\"team_id\":29} {\"correlation_id\":\"cb3b51ca-fd95-4a3f-8366-1dedc565b784\",\"trace_id\":\"5c938a2d-aea9-4025-a7f7-733d8e8e9711\"}\n[2026-05-07 11:36:28] local.INFO: [CrmOwnerResolver] No team members found with active crm connection {\"crm_provider\":\"hubspot\",\"team_id\":29} {\"correlation_id\":\"cb3b51ca-fd95-4a3f-8366-1dedc565b784\",\"trace_id\":\"5c938a2d-aea9-4025-a7f7-733d8e8e9711\"}\n[2026-05-07 11:36:28] local.INFO: [CrmOwnerResolver] No team member found with active crm connection {\"crm_provider\":\"hubspot\",\"team_id\":29} {\"correlation_id\":\"cb3b51ca-fd95-4a3f-8366-1dedc565b784\",\"trace_id\":\"5c938a2d-aea9-4025-a7f7-733d8e8e9711\"}\n[2026-05-07 11:36:28] local.INFO: [SyncHubspotObjects] Sync finished {\"team\":\"b2b115eb-93ce-4d1b-929c-173757df8fba\",\"provider\":\"hubspot\",\"status\":\"disconnected\",\"duration_ms\":64.6,\"usage\":24516544,\"real_usage\":65011712,\"pid\":13732,\"reason\":\"Your HubSpot account has become disconnected. Please login to Jiminny to reconnect.\"} {\"correlation_id\":\"cb3b51ca-fd95-4a3f-8366-1dedc565b784\",\"trace_id\":\"5c938a2d-aea9-4025-a7f7-733d8e8e9711\"}\n[2026-05-07 11:36:28] local.INFO: [SyncHubspotObjects] Starting sync {\"team\":\"c6b9d6b0-b48d-4832-a68c-a57d60651888\",\"usage\":24554912,\"real_usage\":65011712,\"pid\":13732} {\"correlation_id\":\"b5acfa3a-a5cb-43e0-8757-36cc6bc0da54\",\"trace_id\":\"5c938a2d-aea9-4025-a7f7-733d8e8e9711\"}\n[2026-05-07 11:36:28] local.WARNING: [HubSpot] Account not connected for user {\"userId\":\"71e3aac5-fb66-47c5-a236-2d051ae3e319\",\"account\":null} {\"correlation_id\":\"b5acfa3a-a5cb-43e0-8757-36cc6bc0da54\",\"trace_id\":\"5c938a2d-aea9-4025-a7f7-733d8e8e9711\"}\n[2026-05-07 11:36:28] local.INFO: [CrmOwnerResolver] Integration owner is not connected, attempting team members {\"crm_provider\":\"hubspot\",\"crm_owner\":256,\"team_id\":49} {\"correlation_id\":\"b5acfa3a-a5cb-43e0-8757-36cc6bc0da54\",\"trace_id\":\"5c938a2d-aea9-4025-a7f7-733d8e8e9711\"}\n[2026-05-07 11:36:28] local.INFO: [CrmOwnerResolver] No team members found with active crm connection {\"crm_provider\":\"hubspot\",\"team_id\":49} {\"correlation_id\":\"b5acfa3a-a5cb-43e0-8757-36cc6bc0da54\",\"trace_id\":\"5c938a2d-aea9-4025-a7f7-733d8e8e9711\"}\n[2026-05-07 11:36:28] local.INFO: [CrmOwnerResolver] No team member found with active crm connection {\"crm_provider\":\"hubspot\",\"team_id\":49} {\"correlation_id\":\"b5acfa3a-a5cb-43e0-8757-36cc6bc0da54\",\"trace_id\":\"5c938a2d-aea9-4025-a7f7-733d8e8e9711\"}\n[2026-05-07 11:36:28] local.INFO: [SyncHubspotObjects] Sync finished {\"team\":\"c6b9d6b0-b48d-4832-a68c-a57d60651888\",\"provider\":\"hubspot\",\"status\":\"disconnected\",\"duration_ms\":81.21,\"usage\":24510344,\"real_usage\":65011712,\"pid\":13732,\"reason\":\"Social account for HubSpot cannot be found. Please login to Jiminny to connect.\"} {\"correlation_id\":\"b5acfa3a-a5cb-43e0-8757-36cc6bc0da54\",\"trace_id\":\"5c938a2d-aea9-4025-a7f7-733d8e8e9711\"}\n[2026-05-07 11:36:29] local.INFO: [SyncHubspotObjects] Starting sync {\"team\":\"b2d49a54-b645-4637-a7ae-a86cfce6e8e4\",\"usage\":24548712,\"real_usage\":65011712,\"pid\":13732} {\"correlation_id\":\"a40d012c-b10d-4de7-9ea1-ef68239f712b\",\"trace_id\":\"5c938a2d-aea9-4025-a7f7-733d8e8e9711\"}\n[2026-05-07 11:36:29] local.WARNING: [HubSpot] Account not connected for user {\"userId\":\"2ac0447f-3c8c-4ce0-baeb-b63ddb76fa9b\",\"account\":null} {\"correlation_id\":\"a40d012c-b10d-4de7-9ea1-ef68239f712b\",\"trace_id\":\"5c938a2d-aea9-4025-a7f7-733d8e8e9711\"}\n[2026-05-07 11:36:29] local.INFO: [CrmOwnerResolver] Integration owner is not connected, attempting team members {\"crm_provider\":\"hubspot\",\"crm_owner\":130,\"team_id\":42} {\"correlation_id\":\"a40d012c-b10d-4de7-9ea1-ef68239f712b\",\"trace_id\":\"5c938a2d-aea9-4025-a7f7-733d8e8e9711\"}\n[2026-05-07 11:36:29] local.INFO: [CrmOwnerResolver] No team members found with active crm connection {\"crm_provider\":\"hubspot\",\"team_id\":42} {\"correlation_id\":\"a40d012c-b10d-4de7-9ea1-ef68239f712b\",\"trace_id\":\"5c938a2d-aea9-4025-a7f7-733d8e8e9711\"}\n[2026-05-07 11:36:29] local.INFO: [CrmOwnerResolver] No team member found with active crm connection {\"crm_provider\":\"hubspot\",\"team_id\":42} {\"correlation_id\":\"a40d012c-b10d-4de7-9ea1-ef68239f712b\",\"trace_id\":\"5c938a2d-aea9-4025-a7f7-733d8e8e9711\"}\n[2026-05-07 11:36:29] local.INFO: [SyncHubspotObjects] Sync finished {\"team\":\"b2d49a54-b645-4637-a7ae-a86cfce6e8e4\",\"provider\":\"hubspot\",\"status\":\"disconnected\",\"duration_ms\":77.05,\"usage\":24483760,\"real_usage\":65011712,\"pid\":13732,\"reason\":\"Social account for HubSpot cannot be found. Please login to Jiminny to connect.\"} {\"correlation_id\":\"a40d012c-b10d-4de7-9ea1-ef68239f712b\",\"trace_id\":\"5c938a2d-aea9-4025-a7f7-733d8e8e9711\"}\n[2026-05-07 11:36:31] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage before starting command {\"command\":\"activity:notify-not-logged\",\"memoryBeforeCommandInMb\":60.0,\"memoryPeakBeforeCommandInMb\":99.727} {\"correlation_id\":\"cd860d87-3b37-4cc4-b4bc-7788e90ccadc\",\"trace_id\":\"ecfea9bc-0fe5-4224-ba87-0b84ebb94d35\"}\n[2026-05-07 11:36:31] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage for command {\"command\":\"activity:notify-not-logged\",\"memoryBeforeCommandInMb\":60.0,\"memoryAfterCommandInMB\":62.0,\"memoryPeakBeforeCommandInMb\":99.727,\"memoryPeakAfterCommandInMB\":99.727} {\"correlation_id\":\"cd860d87-3b37-4cc4-b4bc-7788e90ccadc\",\"trace_id\":\"ecfea9bc-0fe5-4224-ba87-0b84ebb94d35\"}\n[2026-05-07 11:36:34] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage before starting command {\"command\":\"mailbox:sync\",\"memoryBeforeCommandInMb\":60.0,\"memoryPeakBeforeCommandInMb\":99.727} {\"correlation_id\":\"a179c5a9-e51e-4929-b0a8-717068ede438\",\"trace_id\":\"e12b0454-0027-4fce-8a8f-8572b5c2a51f\"}\n[2026-05-07 11:36:34] local.INFO: [EmailSchedule] STARTING Inbox Sync {\"host\":\"docker_lamp_1\"} {\"correlation_id\":\"a179c5a9-e51e-4929-b0a8-717068ede438\",\"trace_id\":\"e12b0454-0027-4fce-8a8f-8572b5c2a51f\"}\n[2026-05-07 11:36:34] local.INFO: [EmailSchedule] FINISHED Inbox Sync {\"host\":\"docker_lamp_1\",\"events\":2} {\"correlation_id\":\"a179c5a9-e51e-4929-b0a8-717068ede438\",\"trace_id\":\"e12b0454-0027-4fce-8a8f-8572b5c2a51f\"}\n[2026-05-07 11:36:34] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage for command {\"command\":\"mailbox:sync\",\"memoryBeforeCommandInMb\":60.0,\"memoryAfterCommandInMB\":62.0,\"memoryPeakBeforeCommandInMb\":99.727,\"memoryPeakAfterCommandInMB\":99.727} {\"correlation_id\":\"a179c5a9-e51e-4929-b0a8-717068ede438\",\"trace_id\":\"e12b0454-0027-4fce-8a8f-8572b5c2a51f\"}","role_description":"text entry area","is_enabled":true,"is_focused":true,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Sync Changes","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.018055556,"height":0.026666667},"on_screen":false,"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Hide This Notification","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.018055556,"height":0.026666667},"on_screen":false,"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Code changed:","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.088194445,"height":0.027777778},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Hide","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.018055556,"height":0.026666667},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"2","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.016666668,"height":0.02111111},"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"60","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.021527778,"height":0.02111111},"on_screen":false,"role_description":"text"},{"role":"AXButton","text":"Previous Highlighted Error","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.015277778,"height":0.025555555},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Next Highlighted Error","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.014583333,"height":0.025555555},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXTextArea","text":"<?php\n\ndeclare(strict_types=1);\n\nnamespace Jiminny\\Services\\Crm\\Hubspot;\n\nuse HubSpot\\Client\\Crm\\Deals\\ApiException as DealApiException;\nuse HubSpot\\Client\\Crm\\Contacts\\ApiException as ContactApiException;\nuse HubSpot\\Client\\Crm\\Companies\\ApiException as CompanyApiException;\nuse HubSpot\\Client\\Crm\\Contacts\\Model\\SimplePublicObjectWithAssociations as ContactsWithAssociations;\nuse HubSpot\\Client\\Crm\\Companies\\Model\\SimplePublicObjectWithAssociations as CompaniesWithAssociations;\nuse HubSpot\\Client\\Crm\\Deals\\Model\\SimplePublicObjectWithAssociations as DealWithAssociations;\nuse HubSpot\\Client\\Crm\\Objects\\Model\\SimplePublicObjectInput;\nuse HubSpot\\Client\\Crm\\Objects\\Model\\SimplePublicObjectWithAssociations as ObjectWithAssociations;\nuse HubSpot\\Client\\Crm\\Pipelines\\Model\\Error;\nuse HubSpot\\Client\\Crm\\Pipelines\\Model\\PipelineStage;\nuse HubSpot\\Client\\Crm\\Properties\\Model\\Property;\nuse HubSpot\\Discovery\\Discovery;\nuse Jiminny\\Component\\Utility\\Service\\ProviderRateLimiter;\nuse Jiminny\\Exceptions\\CrmException;\nuse Jiminny\\Exceptions\\RateLimitException;\nuse Jiminny\\Exceptions\\SocialAccountTokenInvalidException;\nuse Jiminny\\Jobs\\Crm\\NoteObject;\nuse Jiminny\\Models\\Crm\\Field;\nuse Jiminny\\Services\\Crm\\BaseClient;\nuse Jiminny\\Services\\Crm\\Hubspot\\DTO\\Response\\Owner;\nuse Jiminny\\Services\\SocialAccountService;\nuse SevenShores\\Hubspot\\Exceptions\\BadRequest;\nuse SevenShores\\Hubspot\\Exceptions\\HubspotException;\nuse SevenShores\\Hubspot\\Factory;\nuse SevenShores\\Hubspot\\Http\\Response;\nuse Jiminny\\Services\\Crm\\Hubspot\\Pagination\\HubspotPaginationService;\nuse Throwable;\n\n/**\n * @phpstan-type CrmFieldOption array{id:string, label:string, value?:string}\n */\nclass Client extends BaseClient implements HubspotClientInterface\n{\n public const string MIN_API_VERSION = '2';\n\n public const string BASE_URL = 'https://api.hubapi.com';\n\n public const int ASSOCIATIONS_BATCH_SIZE_LIMIT = 1000;\n\n private HubspotPaginationService $paginationService;\n private HubspotTokenManager $tokenManager;\n private ProviderRateLimiter $rateLimiter;\n\n public function __construct(\n SocialAccountService $socialAccountService,\n HubspotPaginationService $paginationService,\n HubspotTokenManager $tokenManager,\n ProviderRateLimiter $rateLimiter,\n ) {\n parent::__construct($socialAccountService);\n $this->paginationService = $paginationService;\n $this->tokenManager = $tokenManager;\n $this->rateLimiter = $rateLimiter;\n\n $this->setBaseUrl(self::BASE_URL);\n $this->setVersion(self::MIN_API_VERSION);\n }\n\n /**\n * Single entry point for every HubSpot API call. Enforces the per-portal\n * rate limit configured in the rate_limits table (morphed to the current\n * Configuration) and reacts to a real 429 from HubSpot by translating it\n * into a RateLimitException carrying Retry-After.\n *\n * Wrap any outbound HubSpot call (SDK or raw HTTP) like:\n *\n * $this->executeRequest(fn () => $this->getNewInstance()->crm()->...);\n *\n * @template T\n * @param callable(): T $apiCall\n * @return T\n *\n * @throws RateLimitException\n */\n private function executeRequest(callable $apiCall)\n {\n if (! $this->rateLimiter->canMakeRequest($this->config)) {\n $retryAfter = $this->rateLimiter->requestAvailableIn($this->config);\n\n $this->log->warning('[Hubspot] Rate limit exceeded, deferring request', [\n 'team_id' => $this->config->team_id,\n 'config_id' => $this->config->getId(),\n 'retry_after' => $retryAfter,\n ]);\n\n throw new RateLimitException(\n 'Hubspot rate limit reached for configuration ' . $this->config->getId(),\n $retryAfter,\n );\n }\n\n $this->rateLimiter->incrementRequestCount($this->config);\n\n try {\n return $apiCall();\n } catch (Throwable $e) {\n if ($this->isHubspotRateLimit($e)) {\n $retryAfter = $this->parseRetryAfter($e);\n\n $this->log->warning('[Hubspot] Received 429 from API', [\n 'team_id' => $this->config->team_id,\n 'config_id' => $this->config->getId(),\n 'retry_after' => $retryAfter,\n 'reason' => $e->getMessage(),\n ]);\n\n throw new RateLimitException('Hubspot returned 429', $retryAfter, $e);\n }\n\n throw $e;\n }\n }\n\n private function isHubspotRateLimit(Throwable $e): bool\n {\n return method_exists($e, 'getCode') && (int) $e->getCode() === 429;\n }\n\n private function parseRetryAfter(Throwable $e): int\n {\n if (method_exists($e, 'getResponseHeaders')) {\n $headers = $e->getResponseHeaders() ?: [];\n $value = $headers['Retry-After'] ?? $headers['retry-after'] ?? null;\n if (is_array($value)) {\n $value = $value[0] ?? null;\n }\n if (is_numeric($value)) {\n return (int) $value;\n }\n }\n\n return 10;\n }\n\n public function getMinimumApiVersion(): string\n {\n return self::MIN_API_VERSION;\n }\n\n public function getInstance(): Factory\n {\n return new Factory([\n 'key' => $this->accessToken,\n 'oauth2' => true,\n 'base_url' => $this->baseUrl,\n ]);\n }\n\n public function getNewInstance(): Discovery\n {\n return \\HubSpot\\Factory::createWithAccessToken($this->accessToken);\n }\n\n /**\n * Secondly and daily limits for Hubspot API\n *\n * Product Tier: Free & Starter | Professional & Enterprise | API add-on (any tier)\n * Burst: 100/10 seconds | 150/10 seconds | 200/10 seconds\n * Daily: 250,000 | 500,000 | 1,000,000\n *\n * Official documentation states: The search endpoints are rate limited to five requests per second.\n * Since with 5 RPS were still hitting secondly rate limits we lowered it to 4\n */\n public function getPaginatedData(array $payload, string $type, int $offset = 0): array\n {\n $total = 0;\n $lastId = null;\n $rows = [];\n foreach ($this->getPaginatedDataGenerator($payload, $type, $offset, $total, $lastId) as $row) {\n $rows[] = $row;\n }\n\n return ['results' => $rows, 'total' => $total, 'last_record' => $lastId];\n }\n\n /**\n * @throws HubspotException\n * @throws SocialAccountTokenInvalidException\n * @throws BadRequest\n */\n public function getPaginatedDataGenerator(\n array $payload,\n string $type,\n int $offset = 0,\n int &$total = 0,\n ?string &$lastRecordId = null\n ): \\Generator {\n return $this->paginationService->getPaginatedDataGenerator(\n $this,\n $payload,\n $type,\n $offset,\n $total,\n $lastRecordId\n );\n }\n\n /**\n * @throws DealApiException\n * @throws CrmException\n */\n public function getOpportunityById(string $crmId, array $fields): array\n {\n try {\n $deal = $this->executeRequest(fn () => $this->getNewInstance()->crm()->deals()->basicApi()->getById(\n $crmId,\n implode(',', $fields),\n 'companies,contacts'\n ));\n } catch (DealApiException $e) {\n $this->log->info('[Hubspot] Failed to fetch opportunity', [\n 'crm_id' => $crmId,\n 'reason' => $e->getMessage(),\n ]);\n\n throw $e;\n }\n\n if (! $deal instanceof DealWithAssociations) {\n throw new CrmException('Deal not found');\n }\n\n return [\n 'id' => $deal->getId(),\n 'properties' => $deal->getProperties(),\n 'associations' => $deal->getAssociations(),\n ];\n }\n\n /**\n * Generic batch read method for HubSpot objects\n *\n * @param string $objectType The object type ('deals', 'companies', 'contacts')\n * @param array<string> $crmIds Array of HubSpot object IDs (max 100)\n * @param array<string> $fields Array of property names to fetch\n *\n * @return array<string, array> Array keyed by CRM ID with object data\n */\n private function batchReadObjects(string $objectType, array $crmIds, array $fields): array\n {\n if (empty($crmIds)) {\n return [];\n }\n\n $this->validateBatchSize($objectType, $crmIds);\n $this->ensureValidToken();\n\n try {\n $batchConfig = $this->createBatchConfiguration($objectType);\n $batchReadRequest = $this->prepareBatchRequest($batchConfig, $crmIds, $fields);\n $response = $batchConfig['api']->read($batchReadRequest);\n\n $this->validateApiResponse($response, $objectType);\n\n $results = $this->processApiResults($response);\n $this->logBatchResults($objectType, $crmIds, $results);\n\n return $results;\n } catch (\\Throwable $e) {\n $this->handleBatchError($e, $objectType, $crmIds);\n }\n }\n\n private function validateBatchSize(string $objectType, array $crmIds): void\n {\n if (count($crmIds) > 100) {\n throw new \\InvalidArgumentException(\"Batch size cannot exceed 100 {$objectType}\");\n }\n }\n\n private function createBatchConfiguration(string $objectType): array\n {\n $configurations = [\n 'deals' => [\n 'batchReadRequest' => new \\HubSpot\\Client\\Crm\\Deals\\Model\\BatchReadInputSimplePublicObjectId(),\n 'inputClass' => \\HubSpot\\Client\\Crm\\Deals\\Model\\SimplePublicObjectId::class,\n 'api' => $this->getNewInstance()->crm()->deals()->batchApi(),\n ],\n 'companies' => [\n 'batchReadRequest' => new \\HubSpot\\Client\\Crm\\Companies\\Model\\BatchReadInputSimplePublicObjectId(),\n 'inputClass' => \\HubSpot\\Client\\Crm\\Companies\\Model\\SimplePublicObjectId::class,\n 'api' => $this->getNewInstance()->crm()->companies()->batchApi(),\n ],\n 'contacts' => [\n 'batchReadRequest' => new \\HubSpot\\Client\\Crm\\Contacts\\Model\\BatchReadInputSimplePublicObjectId(),\n 'inputClass' => \\HubSpot\\Client\\Crm\\Contacts\\Model\\SimplePublicObjectId::class,\n 'api' => $this->getNewInstance()->crm()->contacts()->batchApi(),\n ],\n ];\n\n if (! isset($configurations[$objectType])) {\n throw new \\InvalidArgumentException(\"Unsupported object type: {$objectType}\");\n }\n\n return $configurations[$objectType];\n }\n\n private function prepareBatchRequest(array $batchConfig, array $crmIds, array $fields): object\n {\n $batchReadRequest = $batchConfig['batchReadRequest'];\n $inputClass = $batchConfig['inputClass'];\n\n $inputs = array_map(function ($crmId) use ($inputClass) {\n $input = new $inputClass();\n $input->setId($crmId);\n\n return $input;\n }, $crmIds);\n\n $batchReadRequest->setInputs($inputs);\n $batchReadRequest->setProperties($fields);\n\n return $batchReadRequest;\n }\n\n private function validateApiResponse($response, string $objectType): void\n {\n if (! $response) {\n throw new CrmException(\"HubSpot API returned null response for {$objectType} batch read\");\n }\n }\n\n private function processApiResults($response): array\n {\n $results = [];\n $responseResults = $response->getResults();\n\n if ($responseResults) {\n foreach ($responseResults as $object) {\n if ($object && $object->getId()) {\n $results[$object->getId()] = [\n 'id' => $object->getId(),\n 'properties' => $object->getProperties() ?: [],\n ];\n }\n }\n }\n\n return $results;\n }\n\n private function logBatchResults(string $objectType, array $crmIds, array $results): void\n {\n $this->log->info(\"[HubSpot] Batch fetched {$objectType}\", [\n 'requested_count' => count($crmIds),\n 'returned_count' => count($results),\n 'crm_ids' => $crmIds,\n ]);\n }\n\n private function handleBatchError(\\Throwable $e, string $objectType, array $crmIds): void\n {\n $errorMessage = $e->getMessage() ?: 'Unknown error';\n $errorTrace = $e->getTraceAsString() ?: 'No trace available';\n\n $this->log->error(\"[HubSpot] Failed to batch fetch {$objectType}\", [\n 'crm_ids' => $crmIds,\n 'error' => $errorMessage,\n 'trace' => $errorTrace,\n ]);\n\n throw new CrmException(\"Failed to batch fetch {$objectType}: \" . $errorMessage);\n }\n\n /**\n * Batch read multiple opportunities by their CRM IDs\n *\n * @param array<string> $crmIds Array of HubSpot deal IDs (max 100)\n * @param array<string> $fields Array of property names to fetch\n *\n * @return array<string, array> Array keyed by CRM ID with opportunity data\n */\n public function getOpportunitiesByIds(array $crmIds, array $fields): array\n {\n return $this->batchReadObjects('deals', $crmIds, $fields);\n }\n\n /**\n * Batch read multiple companies by their CRM IDs\n *\n * @param array<string> $crmIds Array of HubSpot company IDs (max 100)\n * @param array<string> $fields Array of property names to fetch\n *\n * @return array<string, array> Array keyed by CRM ID with company data\n */\n public function getCompaniesByIds(array $crmIds, array $fields): array\n {\n return $this->batchReadObjects('companies', $crmIds, $fields);\n }\n\n /**\n * Batch read multiple contacts by their CRM IDs\n *\n * @param array<string> $crmIds Array of HubSpot contact IDs (max 100)\n * @param array<string> $fields Array of property names to fetch\n *\n * @return array<string, array> Array keyed by CRM ID with contact data\n */\n public function getContactsByIds(array $crmIds, array $fields): array\n {\n return $this->batchReadObjects('contacts', $crmIds, $fields);\n }\n\n /**\n * @throws CompanyApiException\n * @throws CrmException\n */\n public function getAccountById(string $crmId, array $fields): array\n {\n try {\n $company = $this->getNewInstance()->crm()->companies()->basicApi()->getById(\n $crmId,\n implode(',', $fields),\n );\n } catch (CompanyApiException $e) {\n $this->log->info('[Hubspot] Failed to fetch account', [\n 'crm_id' => $crmId,\n 'reason' => $e->getMessage(),\n ]);\n\n throw $e;\n }\n\n if (! $company instanceof CompaniesWithAssociations) {\n throw new CrmException('Account not found');\n }\n\n return [\n 'id' => $company->getId(),\n 'properties' => $company->getProperties(),\n ];\n }\n\n /**\n * @throws ContactApiException\n * @throws CrmException\n */\n public function getContactById(string $crmId, array $fields): array\n {\n try {\n $contact = $this->getNewInstance()->crm()->contacts()->basicApi()->getById(\n $crmId,\n implode(',', $fields)\n );\n } catch (ContactApiException $e) {\n $this->log->info('[Hubspot] Failed to fetch contact', [\n 'crm_id' => $crmId,\n 'reason' => $e->getMessage(),\n ]);\n\n throw $e;\n }\n\n if (! $contact instanceof ContactsWithAssociations) {\n throw new CrmException('Contact not found');\n }\n\n return [\n 'id' => $contact->getId(),\n 'properties' => $contact->getProperties(),\n ];\n }\n\n /**\n * This is email search request that Hubspot offers as GET (more generous quota)\n */\n public function getContactByEmail(string $email, array $fields = []): array\n {\n try {\n $contact = $this->getNewInstance()->crm()->contacts()->basicApi()->getById(\n $email,\n implode(',', $fields),\n null,\n false,\n 'email'\n );\n\n return [\n 'id' => $contact->getId(),\n 'properties' => $contact->getProperties(),\n ];\n } catch (ContactApiException $e) {\n $this->log->info('[Hubspot] Failed to fetch contact', [\n 'email' => $email,\n 'reason' => $e->getMessage(),\n ]);\n\n return [];\n }\n }\n\n /**\n * @throws CrmException\n */\n public function fetchProperty(string $objectType, string $propertyId): Property\n {\n $result = $this->getNewInstance()->crm()->properties()->coreApi()->getByName($objectType, $propertyId);\n\n if (! $result instanceof Property) {\n $this->log->error('[Hubspot] Failed to fetch property', [\n 'object_type' => $objectType,\n 'property_id' => $propertyId,\n 'reason' => $result->getMessage(),\n ]);\n\n throw new CrmException('Failed to fetch property');\n }\n\n return $result;\n }\n\n /**\n * @return array<CrmFieldOption>\n */\n public function fetchPropertyOptions(string $objectType, string $propertyId): array\n {\n /** @var array<CrmFieldOption> */\n return $this->fetchProperty($objectType, $propertyId)->getOptions();\n }\n\n /**\n * @return array<array{id:string, label:string, deleted:bool}>\n */\n public function fetchCallDispositions(): array\n {\n /** @var Response $response */\n $response = $this->getInstance()->engagements()->getCallDispositions();\n\n /**\n * @var array<array{\n * id:string,\n * label:string,\n * deleted: bool\n * }>\n */\n return $response->toArray();\n }\n\n /**\n * @return array<CrmFieldOption>\n */\n public function fetchOpportunityPipelineStages(): array\n {\n $stages = [];\n $apiResponse = $this->getNewInstance()->crm()->pipelines()->pipelinesApi()->getAll('deals');\n\n if ($apiResponse instanceof Error) {\n $this->log->error('[Hubspot] Failed to fetch opportunity pipelines', [\n 'reason' => $apiResponse->getMessage(),\n ]);\n\n return [];\n }\n\n foreach ($apiResponse->getResults() as $pipeline) {\n $pipelineStages = array_map(\n static function (PipelineStage $stage) {\n return [\n 'id' => $stage->getId(),\n 'label' => $stage->getLabel(),\n ];\n },\n $pipeline->getStages()\n );\n\n $stages = array_merge($stages, $pipelineStages);\n }\n\n return $stages;\n }\n\n public function fetchOpportunityPipelines(): array\n {\n $pipelines = [];\n\n try {\n $apiResponse = $this->makeRequest('/crm/v3/pipelines/deals');\n } catch (\\Exception $e) {\n $this->log->info('[Hubspot] Failed to fetch opportunity pipelines', [\n 'reason' => $e->getMessage(),\n ]);\n\n return [];\n }\n\n $response = $apiResponse->toArray();\n\n foreach ($response['results'] as $pipeline) {\n $pipelines[] = [\n 'id' => $pipeline['id'],\n 'label' => $pipeline['label'],\n ];\n }\n\n return $pipelines;\n }\n\n /**\n * @return array<CrmFieldOption>\n */\n public function fetchMeetingOutcomeFieldOptions(Field $field): array\n {\n return $field->getCrmProviderId() === 'meetingOutcome'\n ? $this->fetchMeetingOutcomeTypes()\n : $this->fetchCallActivityTypes();\n }\n\n public function fetchMeetingOutcomeTypes(): array\n {\n return $this->extractMeetingTypeOptions(\n 'https://api.hubapi.com/crm/v3/properties/meeting/hs_meeting_outcome'\n );\n }\n\n public function fetchCallActivityTypes(): array\n {\n return $this->extractMeetingTypeOptions(\n 'https://api.hubapi.com/crm/v3/properties/call/hs_activity_type'\n );\n }\n\n private function extractMeetingTypeOptions(string $endpoint): array\n {\n /** @var Response $response */\n $response = $this->getInstance()\n ->getClient()\n ->request('GET', $endpoint);\n\n /**\n * @var array<array{\n * value: string,\n * label: string,\n * displayOrder: int\n * }> $optionData\n */\n $optionData = $response->toArray()['options'] ?? [];\n\n $options = [];\n foreach ($optionData as $item) {\n $options[] = [\n 'id' => $item['value'],\n 'value' => $item['value'],\n 'label' => $item['label'],\n 'display_order' => $item['displayOrder'],\n ];\n }\n\n return $options;\n }\n\n /**\n * @return array<CrmFieldOption>\n */\n public function fetchDispositionFieldOptions(): array\n {\n $options = [];\n\n $dispositions = $this->fetchCallDispositions();\n\n foreach ($dispositions as $disposition) {\n if ($disposition['deleted'] !== false) {\n continue;\n }\n\n $option['value'] = $disposition['id'];\n $option['id'] = $disposition['id'];\n $option['label'] = $disposition['label'];\n\n $options[] = $option;\n }\n\n return $options;\n }\n\n /**\n * @return array<CrmFieldOption>\n */\n public function fetchOpportunityFieldOptions(Field $field): array\n {\n if ($field->isStageField()) {\n return $this->fetchOpportunityPipelineStages();\n }\n\n if ($field->isPipelineField()) {\n return $this->fetchOpportunityPipelines();\n }\n\n return $this->fetchPropertyOptions('deals', $field->getCrmProviderId());\n }\n\n /**\n * @throws BadRequest\n * @throws HubspotException\n */\n public function makeRequest(string $endpoint, $method = 'GET', $payload = [], ?string $queryString = null)\n {\n $endpoint = self::BASE_URL . $endpoint;\n\n if ($method === 'GET') {\n $response = $this->getInstance()->getClient()?->request(\n method: $method,\n endpoint: $endpoint,\n query_string: $queryString\n );\n } else {\n $response = $this->getInstance()->getClient()->request($method, $endpoint, [\n 'json' => ($payload),\n ]);\n }\n\n $max = $response->getHeaderLine('X-HubSpot-RateLimit-Max'); // \"110\"\n $remaining = $response->getHeaderLine('X-HubSpot-RateLimit-Remaining'); // \"109\"\n $interval = $response->getHeaderLine('X-HubSpot-RateLimit-Interval-Milliseconds'); // \"10000\"\n $body = json_decode((string) $response->getBody(), true);\n\n \\Illuminate\\Support\\Facades\\Log::channel('custom_channel')->info('$max ' . PHP_EOL . print_r($max, true));\n \\Illuminate\\Support\\Facades\\Log::channel('custom_channel')->info('$remaining ' . PHP_EOL . print_r($remaining, true));\n \\Illuminate\\Support\\Facades\\Log::channel('custom_channel')->info('$interval ' . PHP_EOL . print_r($interval, true));\n \\Illuminate\\Support\\Facades\\Log::channel('custom_channel')->info('$body ' . PHP_EOL . print_r($body, true));\n\n return $response;\n }\n\n /**\n * @throws BadRequest\n * @throws HubspotException\n */\n public function createMeeting(array $payload): Response\n {\n $endpoint = '/crm/v3/objects/meetings';\n\n return $this->makeRequest($endpoint, 'POST', $payload);\n }\n\n /**\n * @throws BadRequest\n * @throws HubspotException\n */\n public function updateMeeting(string $meetingId, array $payload): Response\n {\n $endpoint = '/crm/v3/objects/meetings/' . $meetingId;\n\n return $this->makeRequest($endpoint, 'PATCH', $payload);\n }\n\n /**\n * @throws \\Exception\n */\n public function createNote(\n string $body,\n string $ownerId,\n int $timestamp,\n string $objectId,\n NoteObject $noteObject\n ): ?string {\n try {\n $noteInput = new SimplePublicObjectInput([\n 'properties' => [\n 'hs_note_body' => $body,\n 'hubspot_owner_id' => $ownerId,\n 'hs_timestamp' => $timestamp,\n ],\n ]);\n\n // Create note\n $note = $this->getNewInstance()->crm()->objects()->basicApi()->create('note', $noteInput);\n\n $this->getNewInstance()->crm()->objects()->associationsApi()->create(\n 'note',\n $note->getId(),\n $this->getNoteObject($noteObject),\n $objectId,\n $this->getNoteAssociationType($noteObject),\n );\n\n return $note->getId();\n } catch (\\Exception $e) {\n $this->log->error('[Hubspot] Failed to create note', [\n 'objectId' => $objectId,\n 'noteObject' => $noteObject->getObjectType(),\n 'reason' => $e->getMessage(),\n ]);\n\n \\Sentry::captureException($e);\n }\n\n return null;\n }\n\n public function updateEngagement(string $objectId, array $engagement, array $metadata): void\n {\n $this->getInstance()->engagements()->update($objectId, $engagement, $metadata);\n }\n\n public function getEngagementData(string $engagementId): array\n {\n $engagement = $this->getInstance()->engagements()->get($engagementId);\n\n return $engagement->toArray();\n }\n\n public function createEngagement(array $engagement, array $associations, array $metadata): Response\n {\n return $this->getInstance()\n ->engagements()\n ->create($engagement, $associations, $metadata);\n }\n\n public function isUnauthorizedException(\\Exception $e): bool\n {\n // Check for specific HubSpot API exception types first\n if ($e instanceof BadRequest) {\n // BadRequest can contain 401 status codes\n return $e->getCode() === 401;\n }\n\n // Check for HTTP client exceptions with status codes\n if ($e instanceof \\GuzzleHttp\\Exception\\RequestException && $e->hasResponse()) {\n $response = $e->getResponse();\n if ($response !== null) {\n return $response->getStatusCode() === 401;\n }\n }\n\n // Check for Guzzle HTTP exceptions\n if ($e instanceof \\GuzzleHttp\\Exception\\ClientException) {\n return $e->getCode() === 401;\n }\n\n // Fallback to string matching as last resort, but be more specific\n $message = strtolower($e->getMessage());\n\n return str_contains($message, '401 unauthorized') ||\n str_contains($message, 'http 401') ||\n str_contains($message, 'status code 401') ||\n (preg_match('/\\b401\\b/', $message) && str_contains($message, 'unauthorized'));\n }\n\n /**\n * Validates and refreshes the access token if needed before API requests.\n * This ensures long-running processes don't fail due to token expiration.\n *\n * @throws SocialAccountTokenInvalidException\n */\n public function ensureValidToken(): void\n {\n if ($this->oauthAccount === null) {\n return;\n }\n\n $newToken = $this->tokenManager->ensureValidToken($this->oauthAccount);\n if ($newToken !== null) {\n $this->accessToken = $newToken;\n }\n }\n\n public function getConfig()\n {\n return $this->config;\n }\n\n // returns only active (archived=false)\n public function getOwners(): array\n {\n return $this->getNewInstance()->crm()->owners()->getAll();\n }\n\n /**\n * @param bool $archived\n *\n * @return array<Owner>|[]\n */\n public function getOwnersArchived(bool $archived = true): array\n {\n $endpoint = '/crm/v3/owners';\n $queryParams = [\n 'archived' => $archived ? 'true' : 'false',\n ];\n $queryString = http_build_query($queryParams);\n\n $owners = [];\n\n try {\n $response = $this->makeRequest(endpoint: $endpoint, queryString: $queryString);\n $responseData = $response?->toArray();\n\n foreach ($responseData['results'] as $result) {\n try {\n $owners[] = Owner::create($result);\n } catch (Throwable $e) {\n $this->log->error('[HubSpot] Failed to process owner data', [\n 'result' => $result,\n 'error' => $e->getMessage(),\n ]);\n\n continue;\n }\n }\n } catch (Throwable $e) {\n $this->log->error('HubSpot] Failed to fetch owners', [\n 'archived' => $archived,\n 'error' => $e->getMessage(),\n ]);\n\n return [];\n }\n\n return $owners;\n }\n\n public function getMeeting(string $engagementId): ObjectWithAssociations\n {\n return $this->getNewInstance()->crm()->objects()->basicApi()\n ->getById('meeting', $engagementId, null, 'contact,company,deal');\n }\n\n public function deleteEngagement(string $engagementId): void\n {\n $this->getInstance()->engagements()->delete((int) $engagementId);\n }\n\n public function getAssociationsData(array $ids, string $fromObject, string $toObject): array\n {\n $associationData = [];\n $idChunks = array_chunk($ids, self::ASSOCIATIONS_BATCH_SIZE_LIMIT);\n\n foreach ($idChunks as $idChunk) {\n try {\n $batchInput = new \\HubSpot\\Client\\Crm\\Associations\\Model\\BatchInputPublicObjectId();\n $batchInput->setInputs(array_map(function ($id) {\n $publicObjectId = new \\HubSpot\\Client\\Crm\\Associations\\Model\\PublicObjectId();\n $publicObjectId->setId($id);\n\n return $publicObjectId;\n }, $idChunk));\n\n $associatedObjectsData = $this\n ->getNewInstance()\n ->crm()\n ->associations()\n ->batchApi()\n ->read($fromObject, $toObject, $batchInput);\n\n if ($associatedObjectsData instanceof \\HubSpot\\Client\\Crm\\Associations\\Model\\BatchResponsePublicAssociationMulti) {\n foreach ($associatedObjectsData->getResults() as $association) {\n $from = $association->getFrom()->getId();\n $toAssociations = $association->getTo();\n\n if (! empty($toAssociations)) {\n $associationData[$from] = array_map(function ($item) {\n return $item->getId();\n }, $toAssociations);\n }\n }\n }\n } catch (\\Exception $e) {\n $this->log->error('[Hubspot] Failed to fetch associations', [\n 'from_object' => $fromObject,\n 'to_object' => $toObject,\n 'reason' => $e->getMessage(),\n ]);\n }\n }\n\n return $associationData;\n }\n\n /**\n * @throws \\Exception\n */\n private function getNoteAssociationType(NoteObject $noteObject): string\n {\n return match($noteObject) {\n NoteObject::Opportunity => 'note_to_deal',\n NoteObject::Lead, NoteObject::Contact => 'note_to_contact', // or 'note_to_lead' if your portal supports it\n NoteObject::Account => 'note_to_company',\n NoteObject::Call, NoteObject::Event => throw new \\Exception('Not supported'),\n };\n }\n\n /**\n * @throws \\Exception\n */\n private function getNoteObject(NoteObject $noteObject): string\n {\n return match($noteObject) {\n NoteObject::Opportunity => 'deal',\n NoteObject::Lead, NoteObject::Contact => 'contact',\n NoteObject::Account => 'company',\n NoteObject::Call, NoteObject::Event => throw new \\Exception('Not supported'),\n };\n }\n\n public function addAssociations(string $objectType, string $associationType, array $payload): Response\n {\n $endpoint = \"/crm/v4/associations/$objectType/$associationType/batch/create\";\n\n return $this->makeRequest($endpoint, 'POST', $payload);\n }\n\n public function removeAssociations(string $objectType, string $associationType, array $payload): Response\n {\n $endpoint = \"/crm/v4/associations/$objectType/$associationType/batch/archive\";\n\n return $this->makeRequest($endpoint, 'POST', $payload);\n }\n}","depth":4,"on_screen":true,"value":"<?php\n\ndeclare(strict_types=1);\n\nnamespace Jiminny\\Services\\Crm\\Hubspot;\n\nuse HubSpot\\Client\\Crm\\Deals\\ApiException as DealApiException;\nuse HubSpot\\Client\\Crm\\Contacts\\ApiException as ContactApiException;\nuse HubSpot\\Client\\Crm\\Companies\\ApiException as CompanyApiException;\nuse HubSpot\\Client\\Crm\\Contacts\\Model\\SimplePublicObjectWithAssociations as ContactsWithAssociations;\nuse HubSpot\\Client\\Crm\\Companies\\Model\\SimplePublicObjectWithAssociations as CompaniesWithAssociations;\nuse HubSpot\\Client\\Crm\\Deals\\Model\\SimplePublicObjectWithAssociations as DealWithAssociations;\nuse HubSpot\\Client\\Crm\\Objects\\Model\\SimplePublicObjectInput;\nuse HubSpot\\Client\\Crm\\Objects\\Model\\SimplePublicObjectWithAssociations as ObjectWithAssociations;\nuse HubSpot\\Client\\Crm\\Pipelines\\Model\\Error;\nuse HubSpot\\Client\\Crm\\Pipelines\\Model\\PipelineStage;\nuse HubSpot\\Client\\Crm\\Properties\\Model\\Property;\nuse HubSpot\\Discovery\\Discovery;\nuse Jiminny\\Component\\Utility\\Service\\ProviderRateLimiter;\nuse Jiminny\\Exceptions\\CrmException;\nuse Jiminny\\Exceptions\\RateLimitException;\nuse Jiminny\\Exceptions\\SocialAccountTokenInvalidException;\nuse Jiminny\\Jobs\\Crm\\NoteObject;\nuse Jiminny\\Models\\Crm\\Field;\nuse Jiminny\\Services\\Crm\\BaseClient;\nuse Jiminny\\Services\\Crm\\Hubspot\\DTO\\Response\\Owner;\nuse Jiminny\\Services\\SocialAccountService;\nuse SevenShores\\Hubspot\\Exceptions\\BadRequest;\nuse SevenShores\\Hubspot\\Exceptions\\HubspotException;\nuse SevenShores\\Hubspot\\Factory;\nuse SevenShores\\Hubspot\\Http\\Response;\nuse Jiminny\\Services\\Crm\\Hubspot\\Pagination\\HubspotPaginationService;\nuse Throwable;\n\n/**\n * @phpstan-type CrmFieldOption array{id:string, label:string, value?:string}\n */\nclass Client extends BaseClient implements HubspotClientInterface\n{\n public const string MIN_API_VERSION = '2';\n\n public const string BASE_URL = 'https://api.hubapi.com';\n\n public const int ASSOCIATIONS_BATCH_SIZE_LIMIT = 1000;\n\n private HubspotPaginationService $paginationService;\n private HubspotTokenManager $tokenManager;\n private ProviderRateLimiter $rateLimiter;\n\n public function __construct(\n SocialAccountService $socialAccountService,\n HubspotPaginationService $paginationService,\n HubspotTokenManager $tokenManager,\n ProviderRateLimiter $rateLimiter,\n ) {\n parent::__construct($socialAccountService);\n $this->paginationService = $paginationService;\n $this->tokenManager = $tokenManager;\n $this->rateLimiter = $rateLimiter;\n\n $this->setBaseUrl(self::BASE_URL);\n $this->setVersion(self::MIN_API_VERSION);\n }\n\n /**\n * Single entry point for every HubSpot API call. Enforces the per-portal\n * rate limit configured in the rate_limits table (morphed to the current\n * Configuration) and reacts to a real 429 from HubSpot by translating it\n * into a RateLimitException carrying Retry-After.\n *\n * Wrap any outbound HubSpot call (SDK or raw HTTP) like:\n *\n * $this->executeRequest(fn () => $this->getNewInstance()->crm()->...);\n *\n * @template T\n * @param callable(): T $apiCall\n * @return T\n *\n * @throws RateLimitException\n */\n private function executeRequest(callable $apiCall)\n {\n if (! $this->rateLimiter->canMakeRequest($this->config)) {\n $retryAfter = $this->rateLimiter->requestAvailableIn($this->config);\n\n $this->log->warning('[Hubspot] Rate limit exceeded, deferring request', [\n 'team_id' => $this->config->team_id,\n 'config_id' => $this->config->getId(),\n 'retry_after' => $retryAfter,\n ]);\n\n throw new RateLimitException(\n 'Hubspot rate limit reached for configuration ' . $this->config->getId(),\n $retryAfter,\n );\n }\n\n $this->rateLimiter->incrementRequestCount($this->config);\n\n try {\n return $apiCall();\n } catch (Throwable $e) {\n if ($this->isHubspotRateLimit($e)) {\n $retryAfter = $this->parseRetryAfter($e);\n\n $this->log->warning('[Hubspot] Received 429 from API', [\n 'team_id' => $this->config->team_id,\n 'config_id' => $this->config->getId(),\n 'retry_after' => $retryAfter,\n 'reason' => $e->getMessage(),\n ]);\n\n throw new RateLimitException('Hubspot returned 429', $retryAfter, $e);\n }\n\n throw $e;\n }\n }\n\n private function isHubspotRateLimit(Throwable $e): bool\n {\n return method_exists($e, 'getCode') && (int) $e->getCode() === 429;\n }\n\n private function parseRetryAfter(Throwable $e): int\n {\n if (method_exists($e, 'getResponseHeaders')) {\n $headers = $e->getResponseHeaders() ?: [];\n $value = $headers['Retry-After'] ?? $headers['retry-after'] ?? null;\n if (is_array($value)) {\n $value = $value[0] ?? null;\n }\n if (is_numeric($value)) {\n return (int) $value;\n }\n }\n\n return 10;\n }\n\n public function getMinimumApiVersion(): string\n {\n return self::MIN_API_VERSION;\n }\n\n public function getInstance(): Factory\n {\n return new Factory([\n 'key' => $this->accessToken,\n 'oauth2' => true,\n 'base_url' => $this->baseUrl,\n ]);\n }\n\n public function getNewInstance(): Discovery\n {\n return \\HubSpot\\Factory::createWithAccessToken($this->accessToken);\n }\n\n /**\n * Secondly and daily limits for Hubspot API\n *\n * Product Tier: Free & Starter | Professional & Enterprise | API add-on (any tier)\n * Burst: 100/10 seconds | 150/10 seconds | 200/10 seconds\n * Daily: 250,000 | 500,000 | 1,000,000\n *\n * Official documentation states: The search endpoints are rate limited to five requests per second.\n * Since with 5 RPS were still hitting secondly rate limits we lowered it to 4\n */\n public function getPaginatedData(array $payload, string $type, int $offset = 0): array\n {\n $total = 0;\n $lastId = null;\n $rows = [];\n foreach ($this->getPaginatedDataGenerator($payload, $type, $offset, $total, $lastId) as $row) {\n $rows[] = $row;\n }\n\n return ['results' => $rows, 'total' => $total, 'last_record' => $lastId];\n }\n\n /**\n * @throws HubspotException\n * @throws SocialAccountTokenInvalidException\n * @throws BadRequest\n */\n public function getPaginatedDataGenerator(\n array $payload,\n string $type,\n int $offset = 0,\n int &$total = 0,\n ?string &$lastRecordId = null\n ): \\Generator {\n return $this->paginationService->getPaginatedDataGenerator(\n $this,\n $payload,\n $type,\n $offset,\n $total,\n $lastRecordId\n );\n }\n\n /**\n * @throws DealApiException\n * @throws CrmException\n */\n public function getOpportunityById(string $crmId, array $fields): array\n {\n try {\n $deal = $this->executeRequest(fn () => $this->getNewInstance()->crm()->deals()->basicApi()->getById(\n $crmId,\n implode(',', $fields),\n 'companies,contacts'\n ));\n } catch (DealApiException $e) {\n $this->log->info('[Hubspot] Failed to fetch opportunity', [\n 'crm_id' => $crmId,\n 'reason' => $e->getMessage(),\n ]);\n\n throw $e;\n }\n\n if (! $deal instanceof DealWithAssociations) {\n throw new CrmException('Deal not found');\n }\n\n return [\n 'id' => $deal->getId(),\n 'properties' => $deal->getProperties(),\n 'associations' => $deal->getAssociations(),\n ];\n }\n\n /**\n * Generic batch read method for HubSpot objects\n *\n * @param string $objectType The object type ('deals', 'companies', 'contacts')\n * @param array<string> $crmIds Array of HubSpot object IDs (max 100)\n * @param array<string> $fields Array of property names to fetch\n *\n * @return array<string, array> Array keyed by CRM ID with object data\n */\n private function batchReadObjects(string $objectType, array $crmIds, array $fields): array\n {\n if (empty($crmIds)) {\n return [];\n }\n\n $this->validateBatchSize($objectType, $crmIds);\n $this->ensureValidToken();\n\n try {\n $batchConfig = $this->createBatchConfiguration($objectType);\n $batchReadRequest = $this->prepareBatchRequest($batchConfig, $crmIds, $fields);\n $response = $batchConfig['api']->read($batchReadRequest);\n\n $this->validateApiResponse($response, $objectType);\n\n $results = $this->processApiResults($response);\n $this->logBatchResults($objectType, $crmIds, $results);\n\n return $results;\n } catch (\\Throwable $e) {\n $this->handleBatchError($e, $objectType, $crmIds);\n }\n }\n\n private function validateBatchSize(string $objectType, array $crmIds): void\n {\n if (count($crmIds) > 100) {\n throw new \\InvalidArgumentException(\"Batch size cannot exceed 100 {$objectType}\");\n }\n }\n\n private function createBatchConfiguration(string $objectType): array\n {\n $configurations = [\n 'deals' => [\n 'batchReadRequest' => new \\HubSpot\\Client\\Crm\\Deals\\Model\\BatchReadInputSimplePublicObjectId(),\n 'inputClass' => \\HubSpot\\Client\\Crm\\Deals\\Model\\SimplePublicObjectId::class,\n 'api' => $this->getNewInstance()->crm()->deals()->batchApi(),\n ],\n 'companies' => [\n 'batchReadRequest' => new \\HubSpot\\Client\\Crm\\Companies\\Model\\BatchReadInputSimplePublicObjectId(),\n 'inputClass' => \\HubSpot\\Client\\Crm\\Companies\\Model\\SimplePublicObjectId::class,\n 'api' => $this->getNewInstance()->crm()->companies()->batchApi(),\n ],\n 'contacts' => [\n 'batchReadRequest' => new \\HubSpot\\Client\\Crm\\Contacts\\Model\\BatchReadInputSimplePublicObjectId(),\n 'inputClass' => \\HubSpot\\Client\\Crm\\Contacts\\Model\\SimplePublicObjectId::class,\n 'api' => $this->getNewInstance()->crm()->contacts()->batchApi(),\n ],\n ];\n\n if (! isset($configurations[$objectType])) {\n throw new \\InvalidArgumentException(\"Unsupported object type: {$objectType}\");\n }\n\n return $configurations[$objectType];\n }\n\n private function prepareBatchRequest(array $batchConfig, array $crmIds, array $fields): object\n {\n $batchReadRequest = $batchConfig['batchReadRequest'];\n $inputClass = $batchConfig['inputClass'];\n\n $inputs = array_map(function ($crmId) use ($inputClass) {\n $input = new $inputClass();\n $input->setId($crmId);\n\n return $input;\n }, $crmIds);\n\n $batchReadRequest->setInputs($inputs);\n $batchReadRequest->setProperties($fields);\n\n return $batchReadRequest;\n }\n\n private function validateApiResponse($response, string $objectType): void\n {\n if (! $response) {\n throw new CrmException(\"HubSpot API returned null response for {$objectType} batch read\");\n }\n }\n\n private function processApiResults($response): array\n {\n $results = [];\n $responseResults = $response->getResults();\n\n if ($responseResults) {\n foreach ($responseResults as $object) {\n if ($object && $object->getId()) {\n $results[$object->getId()] = [\n 'id' => $object->getId(),\n 'properties' => $object->getProperties() ?: [],\n ];\n }\n }\n }\n\n return $results;\n }\n\n private function logBatchResults(string $objectType, array $crmIds, array $results): void\n {\n $this->log->info(\"[HubSpot] Batch fetched {$objectType}\", [\n 'requested_count' => count($crmIds),\n 'returned_count' => count($results),\n 'crm_ids' => $crmIds,\n ]);\n }\n\n private function handleBatchError(\\Throwable $e, string $objectType, array $crmIds): void\n {\n $errorMessage = $e->getMessage() ?: 'Unknown error';\n $errorTrace = $e->getTraceAsString() ?: 'No trace available';\n\n $this->log->error(\"[HubSpot] Failed to batch fetch {$objectType}\", [\n 'crm_ids' => $crmIds,\n 'error' => $errorMessage,\n 'trace' => $errorTrace,\n ]);\n\n throw new CrmException(\"Failed to batch fetch {$objectType}: \" . $errorMessage);\n }\n\n /**\n * Batch read multiple opportunities by their CRM IDs\n *\n * @param array<string> $crmIds Array of HubSpot deal IDs (max 100)\n * @param array<string> $fields Array of property names to fetch\n *\n * @return array<string, array> Array keyed by CRM ID with opportunity data\n */\n public function getOpportunitiesByIds(array $crmIds, array $fields): array\n {\n return $this->batchReadObjects('deals', $crmIds, $fields);\n }\n\n /**\n * Batch read multiple companies by their CRM IDs\n *\n * @param array<string> $crmIds Array of HubSpot company IDs (max 100)\n * @param array<string> $fields Array of property names to fetch\n *\n * @return array<string, array> Array keyed by CRM ID with company data\n */\n public function getCompaniesByIds(array $crmIds, array $fields): array\n {\n return $this->batchReadObjects('companies', $crmIds, $fields);\n }\n\n /**\n * Batch read multiple contacts by their CRM IDs\n *\n * @param array<string> $crmIds Array of HubSpot contact IDs (max 100)\n * @param array<string> $fields Array of property names to fetch\n *\n * @return array<string, array> Array keyed by CRM ID with contact data\n */\n public function getContactsByIds(array $crmIds, array $fields): array\n {\n return $this->batchReadObjects('contacts', $crmIds, $fields);\n }\n\n /**\n * @throws CompanyApiException\n * @throws CrmException\n */\n public function getAccountById(string $crmId, array $fields): array\n {\n try {\n $company = $this->getNewInstance()->crm()->companies()->basicApi()->getById(\n $crmId,\n implode(',', $fields),\n );\n } catch (CompanyApiException $e) {\n $this->log->info('[Hubspot] Failed to fetch account', [\n 'crm_id' => $crmId,\n 'reason' => $e->getMessage(),\n ]);\n\n throw $e;\n }\n\n if (! $company instanceof CompaniesWithAssociations) {\n throw new CrmException('Account not found');\n }\n\n return [\n 'id' => $company->getId(),\n 'properties' => $company->getProperties(),\n ];\n }\n\n /**\n * @throws ContactApiException\n * @throws CrmException\n */\n public function getContactById(string $crmId, array $fields): array\n {\n try {\n $contact = $this->getNewInstance()->crm()->contacts()->basicApi()->getById(\n $crmId,\n implode(',', $fields)\n );\n } catch (ContactApiException $e) {\n $this->log->info('[Hubspot] Failed to fetch contact', [\n 'crm_id' => $crmId,\n 'reason' => $e->getMessage(),\n ]);\n\n throw $e;\n }\n\n if (! $contact instanceof ContactsWithAssociations) {\n throw new CrmException('Contact not found');\n }\n\n return [\n 'id' => $contact->getId(),\n 'properties' => $contact->getProperties(),\n ];\n }\n\n /**\n * This is email search request that Hubspot offers as GET (more generous quota)\n */\n public function getContactByEmail(string $email, array $fields = []): array\n {\n try {\n $contact = $this->getNewInstance()->crm()->contacts()->basicApi()->getById(\n $email,\n implode(',', $fields),\n null,\n false,\n 'email'\n );\n\n return [\n 'id' => $contact->getId(),\n 'properties' => $contact->getProperties(),\n ];\n } catch (ContactApiException $e) {\n $this->log->info('[Hubspot] Failed to fetch contact', [\n 'email' => $email,\n 'reason' => $e->getMessage(),\n ]);\n\n return [];\n }\n }\n\n /**\n * @throws CrmException\n */\n public function fetchProperty(string $objectType, string $propertyId): Property\n {\n $result = $this->getNewInstance()->crm()->properties()->coreApi()->getByName($objectType, $propertyId);\n\n if (! $result instanceof Property) {\n $this->log->error('[Hubspot] Failed to fetch property', [\n 'object_type' => $objectType,\n 'property_id' => $propertyId,\n 'reason' => $result->getMessage(),\n ]);\n\n throw new CrmException('Failed to fetch property');\n }\n\n return $result;\n }\n\n /**\n * @return array<CrmFieldOption>\n */\n public function fetchPropertyOptions(string $objectType, string $propertyId): array\n {\n /** @var array<CrmFieldOption> */\n return $this->fetchProperty($objectType, $propertyId)->getOptions();\n }\n\n /**\n * @return array<array{id:string, label:string, deleted:bool}>\n */\n public function fetchCallDispositions(): array\n {\n /** @var Response $response */\n $response = $this->getInstance()->engagements()->getCallDispositions();\n\n /**\n * @var array<array{\n * id:string,\n * label:string,\n * deleted: bool\n * }>\n */\n return $response->toArray();\n }\n\n /**\n * @return array<CrmFieldOption>\n */\n public function fetchOpportunityPipelineStages(): array\n {\n $stages = [];\n $apiResponse = $this->getNewInstance()->crm()->pipelines()->pipelinesApi()->getAll('deals');\n\n if ($apiResponse instanceof Error) {\n $this->log->error('[Hubspot] Failed to fetch opportunity pipelines', [\n 'reason' => $apiResponse->getMessage(),\n ]);\n\n return [];\n }\n\n foreach ($apiResponse->getResults() as $pipeline) {\n $pipelineStages = array_map(\n static function (PipelineStage $stage) {\n return [\n 'id' => $stage->getId(),\n 'label' => $stage->getLabel(),\n ];\n },\n $pipeline->getStages()\n );\n\n $stages = array_merge($stages, $pipelineStages);\n }\n\n return $stages;\n }\n\n public function fetchOpportunityPipelines(): array\n {\n $pipelines = [];\n\n try {\n $apiResponse = $this->makeRequest('/crm/v3/pipelines/deals');\n } catch (\\Exception $e) {\n $this->log->info('[Hubspot] Failed to fetch opportunity pipelines', [\n 'reason' => $e->getMessage(),\n ]);\n\n return [];\n }\n\n $response = $apiResponse->toArray();\n\n foreach ($response['results'] as $pipeline) {\n $pipelines[] = [\n 'id' => $pipeline['id'],\n 'label' => $pipeline['label'],\n ];\n }\n\n return $pipelines;\n }\n\n /**\n * @return array<CrmFieldOption>\n */\n public function fetchMeetingOutcomeFieldOptions(Field $field): array\n {\n return $field->getCrmProviderId() === 'meetingOutcome'\n ? $this->fetchMeetingOutcomeTypes()\n : $this->fetchCallActivityTypes();\n }\n\n public function fetchMeetingOutcomeTypes(): array\n {\n return $this->extractMeetingTypeOptions(\n 'https://api.hubapi.com/crm/v3/properties/meeting/hs_meeting_outcome'\n );\n }\n\n public function fetchCallActivityTypes(): array\n {\n return $this->extractMeetingTypeOptions(\n 'https://api.hubapi.com/crm/v3/properties/call/hs_activity_type'\n );\n }\n\n private function extractMeetingTypeOptions(string $endpoint): array\n {\n /** @var Response $response */\n $response = $this->getInstance()\n ->getClient()\n ->request('GET', $endpoint);\n\n /**\n * @var array<array{\n * value: string,\n * label: string,\n * displayOrder: int\n * }> $optionData\n */\n $optionData = $response->toArray()['options'] ?? [];\n\n $options = [];\n foreach ($optionData as $item) {\n $options[] = [\n 'id' => $item['value'],\n 'value' => $item['value'],\n 'label' => $item['label'],\n 'display_order' => $item['displayOrder'],\n ];\n }\n\n return $options;\n }\n\n /**\n * @return array<CrmFieldOption>\n */\n public function fetchDispositionFieldOptions(): array\n {\n $options = [];\n\n $dispositions = $this->fetchCallDispositions();\n\n foreach ($dispositions as $disposition) {\n if ($disposition['deleted'] !== false) {\n continue;\n }\n\n $option['value'] = $disposition['id'];\n $option['id'] = $disposition['id'];\n $option['label'] = $disposition['label'];\n\n $options[] = $option;\n }\n\n return $options;\n }\n\n /**\n * @return array<CrmFieldOption>\n */\n public function fetchOpportunityFieldOptions(Field $field): array\n {\n if ($field->isStageField()) {\n return $this->fetchOpportunityPipelineStages();\n }\n\n if ($field->isPipelineField()) {\n return $this->fetchOpportunityPipelines();\n }\n\n return $this->fetchPropertyOptions('deals', $field->getCrmProviderId());\n }\n\n /**\n * @throws BadRequest\n * @throws HubspotException\n */\n public function makeRequest(string $endpoint, $method = 'GET', $payload = [], ?string $queryString = null)\n {\n $endpoint = self::BASE_URL . $endpoint;\n\n if ($method === 'GET') {\n $response = $this->getInstance()->getClient()?->request(\n method: $method,\n endpoint: $endpoint,\n query_string: $queryString\n );\n } else {\n $response = $this->getInstance()->getClient()->request($method, $endpoint, [\n 'json' => ($payload),\n ]);\n }\n\n $max = $response->getHeaderLine('X-HubSpot-RateLimit-Max'); // \"110\"\n $remaining = $response->getHeaderLine('X-HubSpot-RateLimit-Remaining'); // \"109\"\n $interval = $response->getHeaderLine('X-HubSpot-RateLimit-Interval-Milliseconds'); // \"10000\"\n $body = json_decode((string) $response->getBody(), true);\n\n \\Illuminate\\Support\\Facades\\Log::channel('custom_channel')->info('$max ' . PHP_EOL . print_r($max, true));\n \\Illuminate\\Support\\Facades\\Log::channel('custom_channel')->info('$remaining ' . PHP_EOL . print_r($remaining, true));\n \\Illuminate\\Support\\Facades\\Log::channel('custom_channel')->info('$interval ' . PHP_EOL . print_r($interval, true));\n \\Illuminate\\Support\\Facades\\Log::channel('custom_channel')->info('$body ' . PHP_EOL . print_r($body, true));\n\n return $response;\n }\n\n /**\n * @throws BadRequest\n * @throws HubspotException\n */\n public function createMeeting(array $payload): Response\n {\n $endpoint = '/crm/v3/objects/meetings';\n\n return $this->makeRequest($endpoint, 'POST', $payload);\n }\n\n /**\n * @throws BadRequest\n * @throws HubspotException\n */\n public function updateMeeting(string $meetingId, array $payload): Response\n {\n $endpoint = '/crm/v3/objects/meetings/' . $meetingId;\n\n return $this->makeRequest($endpoint, 'PATCH', $payload);\n }\n\n /**\n * @throws \\Exception\n */\n public function createNote(\n string $body,\n string $ownerId,\n int $timestamp,\n string $objectId,\n NoteObject $noteObject\n ): ?string {\n try {\n $noteInput = new SimplePublicObjectInput([\n 'properties' => [\n 'hs_note_body' => $body,\n 'hubspot_owner_id' => $ownerId,\n 'hs_timestamp' => $timestamp,\n ],\n ]);\n\n // Create note\n $note = $this->getNewInstance()->crm()->objects()->basicApi()->create('note', $noteInput);\n\n $this->getNewInstance()->crm()->objects()->associationsApi()->create(\n 'note',\n $note->getId(),\n $this->getNoteObject($noteObject),\n $objectId,\n $this->getNoteAssociationType($noteObject),\n );\n\n return $note->getId();\n } catch (\\Exception $e) {\n $this->log->error('[Hubspot] Failed to create note', [\n 'objectId' => $objectId,\n 'noteObject' => $noteObject->getObjectType(),\n 'reason' => $e->getMessage(),\n ]);\n\n \\Sentry::captureException($e);\n }\n\n return null;\n }\n\n public function updateEngagement(string $objectId, array $engagement, array $metadata): void\n {\n $this->getInstance()->engagements()->update($objectId, $engagement, $metadata);\n }\n\n public function getEngagementData(string $engagementId): array\n {\n $engagement = $this->getInstance()->engagements()->get($engagementId);\n\n return $engagement->toArray();\n }\n\n public function createEngagement(array $engagement, array $associations, array $metadata): Response\n {\n return $this->getInstance()\n ->engagements()\n ->create($engagement, $associations, $metadata);\n }\n\n public function isUnauthorizedException(\\Exception $e): bool\n {\n // Check for specific HubSpot API exception types first\n if ($e instanceof BadRequest) {\n // BadRequest can contain 401 status codes\n return $e->getCode() === 401;\n }\n\n // Check for HTTP client exceptions with status codes\n if ($e instanceof \\GuzzleHttp\\Exception\\RequestException && $e->hasResponse()) {\n $response = $e->getResponse();\n if ($response !== null) {\n return $response->getStatusCode() === 401;\n }\n }\n\n // Check for Guzzle HTTP exceptions\n if ($e instanceof \\GuzzleHttp\\Exception\\ClientException) {\n return $e->getCode() === 401;\n }\n\n // Fallback to string matching as last resort, but be more specific\n $message = strtolower($e->getMessage());\n\n return str_contains($message, '401 unauthorized') ||\n str_contains($message, 'http 401') ||\n str_contains($message, 'status code 401') ||\n (preg_match('/\\b401\\b/', $message) && str_contains($message, 'unauthorized'));\n }\n\n /**\n * Validates and refreshes the access token if needed before API requests.\n * This ensures long-running processes don't fail due to token expiration.\n *\n * @throws SocialAccountTokenInvalidException\n */\n public function ensureValidToken(): void\n {\n if ($this->oauthAccount === null) {\n return;\n }\n\n $newToken = $this->tokenManager->ensureValidToken($this->oauthAccount);\n if ($newToken !== null) {\n $this->accessToken = $newToken;\n }\n }\n\n public function getConfig()\n {\n return $this->config;\n }\n\n // returns only active (archived=false)\n public function getOwners(): array\n {\n return $this->getNewInstance()->crm()->owners()->getAll();\n }\n\n /**\n * @param bool $archived\n *\n * @return array<Owner>|[]\n */\n public function getOwnersArchived(bool $archived = true): array\n {\n $endpoint = '/crm/v3/owners';\n $queryParams = [\n 'archived' => $archived ? 'true' : 'false',\n ];\n $queryString = http_build_query($queryParams);\n\n $owners = [];\n\n try {\n $response = $this->makeRequest(endpoint: $endpoint, queryString: $queryString);\n $responseData = $response?->toArray();\n\n foreach ($responseData['results'] as $result) {\n try {\n $owners[] = Owner::create($result);\n } catch (Throwable $e) {\n $this->log->error('[HubSpot] Failed to process owner data', [\n 'result' => $result,\n 'error' => $e->getMessage(),\n ]);\n\n continue;\n }\n }\n } catch (Throwable $e) {\n $this->log->error('HubSpot] Failed to fetch owners', [\n 'archived' => $archived,\n 'error' => $e->getMessage(),\n ]);\n\n return [];\n }\n\n return $owners;\n }\n\n public function getMeeting(string $engagementId): ObjectWithAssociations\n {\n return $this->getNewInstance()->crm()->objects()->basicApi()\n ->getById('meeting', $engagementId, null, 'contact,company,deal');\n }\n\n public function deleteEngagement(string $engagementId): void\n {\n $this->getInstance()->engagements()->delete((int) $engagementId);\n }\n\n public function getAssociationsData(array $ids, string $fromObject, string $toObject): array\n {\n $associationData = [];\n $idChunks = array_chunk($ids, self::ASSOCIATIONS_BATCH_SIZE_LIMIT);\n\n foreach ($idChunks as $idChunk) {\n try {\n $batchInput = new \\HubSpot\\Client\\Crm\\Associations\\Model\\BatchInputPublicObjectId();\n $batchInput->setInputs(array_map(function ($id) {\n $publicObjectId = new \\HubSpot\\Client\\Crm\\Associations\\Model\\PublicObjectId();\n $publicObjectId->setId($id);\n\n return $publicObjectId;\n }, $idChunk));\n\n $associatedObjectsData = $this\n ->getNewInstance()\n ->crm()\n ->associations()\n ->batchApi()\n ->read($fromObject, $toObject, $batchInput);\n\n if ($associatedObjectsData instanceof \\HubSpot\\Client\\Crm\\Associations\\Model\\BatchResponsePublicAssociationMulti) {\n foreach ($associatedObjectsData->getResults() as $association) {\n $from = $association->getFrom()->getId();\n $toAssociations = $association->getTo();\n\n if (! empty($toAssociations)) {\n $associationData[$from] = array_map(function ($item) {\n return $item->getId();\n }, $toAssociations);\n }\n }\n }\n } catch (\\Exception $e) {\n $this->log->error('[Hubspot] Failed to fetch associations', [\n 'from_object' => $fromObject,\n 'to_object' => $toObject,\n 'reason' => $e->getMessage(),\n ]);\n }\n }\n\n return $associationData;\n }\n\n /**\n * @throws \\Exception\n */\n private function getNoteAssociationType(NoteObject $noteObject): string\n {\n return match($noteObject) {\n NoteObject::Opportunity => 'note_to_deal',\n NoteObject::Lead, NoteObject::Contact => 'note_to_contact', // or 'note_to_lead' if your portal supports it\n NoteObject::Account => 'note_to_company',\n NoteObject::Call, NoteObject::Event => throw new \\Exception('Not supported'),\n };\n }\n\n /**\n * @throws \\Exception\n */\n private function getNoteObject(NoteObject $noteObject): string\n {\n return match($noteObject) {\n NoteObject::Opportunity => 'deal',\n NoteObject::Lead, NoteObject::Contact => 'contact',\n NoteObject::Account => 'company',\n NoteObject::Call, NoteObject::Event => throw new \\Exception('Not supported'),\n };\n }\n\n public function addAssociations(string $objectType, string $associationType, array $payload): Response\n {\n $endpoint = \"/crm/v4/associations/$objectType/$associationType/batch/create\";\n\n return $this->makeRequest($endpoint, 'POST', $payload);\n }\n\n public function removeAssociations(string $objectType, string $associationType, array $payload): Response\n {\n $endpoint = \"/crm/v4/associations/$objectType/$associationType/batch/archive\";\n\n return $this->makeRequest($endpoint, 'POST', $payload);\n }\n}","role_description":"text entry area","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"Project","depth":3,"on_screen":false,"role_description":"text"},{"role":"AXButton","text":"Project","depth":3,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"New File or Directory…","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.018055556,"height":0.026666667},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Expand Selected","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.018055556,"height":0.026666667},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Collapse All","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.018055556,"height":0.026666667},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Options","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.018055556,"height":0.026666667},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Hide","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.018055556,"height":0.026666667},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false}]...
|
-8123360485970677608
|
5514066055808292964
|
click
|
accessibility
|
NULL
|
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
[2026-05-07 11:36:26] local.INFO: Jiminny\Console\Commands\Command::run Memory usage before starting command {"command":"crm:sync-hubspot-objects","memoryBeforeCommandInMb":60.0,"memoryPeakBeforeCommandInMb":99.727} {"correlation_id":"f71ac7c4-e9d4-46ad-be82-47e68a8f1199","trace_id":"5c938a2d-aea9-4025-a7f7-733d8e8e9711"}
[2026-05-07 11:36:26] local.INFO: Jiminny\Console\Commands\Command::run Memory usage for command {"command":"crm:sync-hubspot-objects","memoryBeforeCommandInMb":60.0,"memoryAfterCommandInMB":62.0,"memoryPeakBeforeCommandInMb":99.727,"memoryPeakAfterCommandInMB":99.727} {"correlation_id":"f71ac7c4-e9d4-46ad-be82-47e68a8f1199","trace_id":"5c938a2d-aea9-4025-a7f7-733d8e8e9711"}
[2026-05-07 11:36:27] local.INFO: [SyncHubspotObjects] Starting sync {"team":"abae74b8-bfa8-4383-9a7f-89f4bf2bdbb4","usage":24412984,"real_usage":65011712,"pid":13732} {"correlation_id":"766570a3-89a2-4427-81c2-c70a7c780ee3","trace_id":"5c938a2d-aea9-4025-a7f7-733d8e8e9711"}
[2026-05-07 11:36:27] local.INFO: [SocialAccountService] Fetching token {"socialAccountId":1499,"provider":"hubspot"} {"correlation_id":"766570a3-89a2-4427-81c2-c70a7c780ee3","trace_id":"5c938a2d-aea9-4025-a7f7-733d8e8e9711"}
[2026-05-07 11:36:27] local.INFO: [SocialAccountService] Token needs refreshing {"socialAccountId":1499,"provider":"hubspot"} {"correlation_id":"766570a3-89a2-4427-81c2-c70a7c780ee3","trace_id":"5c938a2d-aea9-4025-a7f7-733d8e8e9711"}
[2026-05-07 11:36:27] local.INFO: [EncryptedTokenManager] Generating access token. {"mode":"legacy"} {"correlation_id":"766570a3-89a2-4427-81c2-c70a7c780ee3","trace_id":"5c938a2d-aea9-4025-a7f7-733d8e8e9711"}
[2026-05-07 11:36:27] local.INFO: [SocialAccountService] Refreshing token from provider {"socialAccountId":1499,"provider":"hubspot","refreshToken":"96f94c623a404e02ebdbf07f1b75707bb6cdbf848cbf45d418baf608c41a8d86","state":"connected"} {"correlation_id":"766570a3-89a2-4427-81c2-c70a7c780ee3","trace_id":"5c938a2d-aea9-4025-a7f7-733d8e8e9711"}
[2026-05-07 11:36:27] local.INFO: [SocialAccountObserver] Saving model {"correlation_id":"766570a3-89a2-4427-81c2-c70a7c780ee3","trace_id":"5c938a2d-aea9-4025-a7f7-733d8e8e9711"}
[2026-05-07 11:36:27] local.INFO: [SocialAccountObserver] Access token was modified, encrypting {"correlation_id":"766570a3-89a2-4427-81c2-c70a7c780ee3","trace_id":"5c938a2d-aea9-4025-a7f7-733d8e8e9711"}
[2026-05-07 11:36:28] local.INFO: [SocialAccountService] Token refreshed {"socialAccountId":1499,"provider":"hubspot","state":"connected"} {"correlation_id":"766570a3-89a2-4427-81c2-c70a7c780ee3","trace_id":"5c938a2d-aea9-4025-a7f7-733d8e8e9711"}
[2026-05-07 11:36:28] local.INFO: [CrmOwnerResolver] Integration owner matched as CRM Owner {"crm_provider":"hubspot","crm_owner":148,"team_id":2} {"correlation_id":"766570a3-89a2-4427-81c2-c70a7c780ee3","trace_id":"5c938a2d-aea9-4025-a7f7-733d8e8e9711"}
[2026-05-07 11:36:28] local.INFO: [HubSpot] Syncing opportunities using strategy: lastModified {"team":2} {"correlation_id":"766570a3-89a2-4427-81c2-c70a7c780ee3","trace_id":"5c938a2d-aea9-4025-a7f7-733d8e8e9711"}
[2026-05-07 11:36:28] local.INFO: [Hubspot] Pagination completed {"team_id":2,"endpoint":"[URL_WITH_CREDENTIALS] CrmFieldOption array{id:string, label:string, value?:string}
*/
class Client extends BaseClient implements HubspotClientInterface
{
public const string MIN_API_VERSION = '2';
public const string BASE_URL = '[URL_WITH_CREDENTIALS] T
* @param callable(): T $apiCall
* @return T
*
* @throws RateLimitException
*/
private function executeRequest(callable $apiCall)
{
if (! $this->rateLimiter->canMakeRequest($this->config)) {
$retryAfter = $this->rateLimiter->requestAvailableIn($this->config);
$this->log->warning('[Hubspot] Rate limit exceeded, deferring request', [
'team_id' => $this->config->team_id,
'config_id' => $this->config->getId(),
'retry_after' => $retryAfter,
]);
throw new RateLimitException(
'Hubspot rate limit reached for configuration ' . $this->config->getId(),
$retryAfter,
);
}
$this->rateLimiter->incrementRequestCount($this->config);
try {
return $apiCall();
} catch (Throwable $e) {
if ($this->isHubspotRateLimit($e)) {
$retryAfter = $this->parseRetryAfter($e);
$this->log->warning('[Hubspot] Received 429 from API', [
'team_id' => $this->config->team_id,
'config_id' => $this->config->getId(),
'retry_after' => $retryAfter,
'reason' => $e->getMessage(),
]);
throw new RateLimitException('Hubspot returned 429', $retryAfter, $e);
}
throw $e;
}
}
private function isHubspotRateLimit(Throwable $e): bool
{
return method_exists($e, 'getCode') && (int) $e->getCode() === 429;
}
private function parseRetryAfter(Throwable $e): int
{
if (method_exists($e, 'getResponseHeaders')) {
$headers = $e->getResponseHeaders() ?: [];
$value = $headers['Retry-After'] ?? $headers['retry-after'] ?? null;
if (is_array($value)) {
$value = $value[0] ?? null;
}
if (is_numeric($value)) {
return (int) $value;
}
}
return 10;
}
public function getMinimumApiVersion(): string
{
return self::MIN_API_VERSION;
}
public function getInstance(): Factory
{
return new Factory([
'key' => $this->accessToken,
'oauth2' => true,
'base_url' => $this->baseUrl,
]);
}
public function getNewInstance(): Discovery
{
return \HubSpot\Factory::createWithAccessToken($this->accessToken);
}
/**
* Secondly and daily limits for Hubspot API
*
* Product Tier: Free & Starter | Professional & Enterprise | API add-on (any tier)
* Burst: 100/10 seconds | 150/10 seconds | 200/10 seconds
* Daily: 250,000 | 500,000 | 1,000,000
*
* Official documentation states: The search endpoints are rate limited to five requests per second.
* Since with 5 RPS were still hitting secondly rate limits we lowered it to 4
*/
public function getPaginatedData(array $payload, string $type, int $offset = 0): array
{
$total = 0;
$lastId = null;
$rows = [];
foreach ($this->getPaginatedDataGenerator($payload, $type, $offset, $total, $lastId) as $row) {
$rows[] = $row;
}
return ['results' => $rows, 'total' => $total, 'last_record' => $lastId];
}
/**
* @throws HubspotException
* @throws SocialAccountTokenInvalidException
* @throws BadRequest
*/
public function getPaginatedDataGenerator(
array $payload,
string $type,
int $offset = 0,
int &$total = 0,
?string &$lastRecordId = null
): \Generator {
return $this->paginationService->getPaginatedDataGenerator(
$this,
$payload,
$type,
$offset,
$total,
$lastRecordId
);
}
/**
* @throws DealApiException
* @throws CrmException
*/
public function getOpportunityById(string $crmId, array $fields): array
{
try {
$deal = $this->executeRequest(fn () => $this->getNewInstance()->crm()->deals()->basicApi()->getById(
$crmId,
implode(',', $fields),
'companies,contacts'
));
} catch (DealApiException $e) {
$this->log->info('[Hubspot] Failed to fetch opportunity', [
'crm_id' => $crmId,
'reason' => $e->getMessage(),
]);
throw $e;
}
if (! $deal instanceof DealWithAssociations) {
throw new CrmException('Deal not found');
}
return [
'id' => $deal->getId(),
'properties' => $deal->getProperties(),
'associations' => $deal->getAssociations(),
];
}
/**
* Generic batch read method for HubSpot objects
*
* @param string $objectType The object type ('deals', 'companies', 'contacts')
* @param array<string> $crmIds Array of HubSpot object IDs (max 100)
* @param array<string> $fields Array of property names to fetch
*
* @return array<string, array> Array keyed by CRM ID with object data
*/
private function batchReadObjects(string $objectType, array $crmIds, array $fields): array
{
if (empty($crmIds)) {
return [];
}
$this->validateBatchSize($objectType, $crmIds);
$this->ensureValidToken();
try {
$batchConfig = $this->createBatchConfiguration($objectType);
$batchReadRequest = $this->prepareBatchRequest($batchConfig, $crmIds, $fields);
$response = $batchConfig['api']->read($batchReadRequest);
$this->validateApiResponse($response, $objectType);
$results = $this->processApiResults($response);
$this->logBatchResults($objectType, $crmIds, $results);
return $results;
} catch (\Throwable $e) {
$this->handleBatchError($e, $objectType, $crmIds);
}
}
private function validateBatchSize(string $objectType, array $crmIds): void
{
if (count($crmIds) > 100) {
throw new \InvalidArgumentException("Batch size cannot exceed 100 {$objectType}");
}
}
private function createBatchConfiguration(string $objectType): array
{
$configurations = [
'deals' => [
'batchReadRequest' => new \HubSpot\Client\Crm\Deals\Model\BatchReadInputSimplePublicObjectId(),
'inputClass' => \HubSpot\Client\Crm\Deals\Model\SimplePublicObjectId::class,
'api' => $this->getNewInstance()->crm()->deals()->batchApi(),
],
'companies' => [
'batchReadRequest' => new \HubSpot\Client\Crm\Companies\Model\BatchReadInputSimplePublicObjectId(),
'inputClass' => \HubSpot\Client\Crm\Companies\Model\SimplePublicObjectId::class,
'api' => $this->getNewInstance()->crm()->companies()->batchApi(),
],
'contacts' => [
'batchReadRequest' => new \HubSpot\Client\Crm\Contacts\Model\BatchReadInputSimplePublicObjectId(),
'inputClass' => \HubSpot\Client\Crm\Contacts\Model\SimplePublicObjectId::class,
'api' => $this->getNewInstance()->crm()->contacts()->batchApi(),
],
];
if (! isset($configurations[$objectType])) {
throw new \InvalidArgumentException("Unsupported object type: {$objectType}");
}
return $configurations[$objectType];
}
private function prepareBatchRequest(array $batchConfig, array $crmIds, array $fields): object
{
$batchReadRequest = $batchConfig['batchReadRequest'];
$inputClass = $batchConfig['inputClass'];
$inputs = array_map(function ($crmId) use ($inputClass) {
$input = new $inputClass();
$input->setId($crmId);
return $input;
}, $crmIds);
$batchReadRequest->setInputs($inputs);
$batchReadRequest->setProperties($fields);
return $batchReadRequest;
}
private function validateApiResponse($response, string $objectType): void
{
if (! $response) {
throw new CrmException("HubSpot API returned null response for {$objectType} batch read");
}
}
private function processApiResults($response): array
{
$results = [];
$responseResults = $response->getResults();
if ($responseResults) {
foreach ($responseResults as $object) {
if ($object && $object->getId()) {
$results[$object->getId()] = [
'id' => $object->getId(),
'properties' => $object->getProperties() ?: [],
];
}
}
}
return $results;
}
private function logBatchResults(string $objectType, array $crmIds, array $results): void
{
$this->log->info("[HubSpot] Batch fetched {$objectType}", [
'requested_count' => count($crmIds),
'returned_count' => count($results),
'crm_ids' => $crmIds,
]);
}
private function handleBatchError(\Throwable $e, string $objectType, array $crmIds): void
{
$errorMessage = $e->getMessage() ?: 'Unknown error';
$errorTrace = $e->getTraceAsString() ?: 'No trace available';
$this->log->error("[HubSpot] Failed to batch fetch {$objectType}", [
'crm_ids' => $crmIds,
'error' => $errorMessage,
'trace' => $errorTrace,
]);
throw new CrmException("Failed to batch fetch {$objectType}: " . $errorMessage);
}
/**
* Batch read multiple opportunities by their CRM IDs
*
* @param array<string> $crmIds Array of HubSpot deal IDs (max 100)
* @param array<string> $fields Array of property names to fetch
*
* @return array<string, array> Array keyed by CRM ID with opportunity data
*/
public function getOpportunitiesByIds(array $crmIds, array $fields): array
{
return $this->batchReadObjects('deals', $crmIds, $fields);
}
/**
* Batch read multiple companies by their CRM IDs
*
* @param array<string> $crmIds Array of HubSpot company IDs (max 100)
* @param array<string> $fields Array of property names to fetch
*
* @return array<string, array> Array keyed by CRM ID with company data
*/
public function getCompaniesByIds(array $crmIds, array $fields): array
{
return $this->batchReadObjects('companies', $crmIds, $fields);
}
/**
* Batch read multiple contacts by their CRM IDs
*
* @param array<string> $crmIds Array of HubSpot contact IDs (max 100)
* @param array<string> $fields Array of property names to fetch
*
* @return array<string, array> Array keyed by CRM ID with contact data
*/
public function getContactsByIds(array $crmIds, array $fields): array
{
return $this->batchReadObjects('contacts', $crmIds, $fields);
}
/**
* @throws CompanyApiException
* @throws CrmException
*/
public function getAccountById(string $crmId, array $fields): array
{
try {
$company = $this->getNewInstance()->crm()->companies()->basicApi()->getById(
$crmId,
implode(',', $fields),
);
} catch (CompanyApiException $e) {
$this->log->info('[Hubspot] Failed to fetch account', [
'crm_id' => $crmId,
'reason' => $e->getMessage(),
]);
throw $e;
}
if (! $company instanceof CompaniesWithAssociations) {
throw new CrmException('Account not found');
}
return [
'id' => $company->getId(),
'properties' => $company->getProperties(),
];
}
/**
* @throws ContactApiException
* @throws CrmException
*/
public function getContactById(string $crmId, array $fields): array
{
try {
$contact = $this->getNewInstance()->crm()->contacts()->basicApi()->getById(
$crmId,
implode(',', $fields)
);
} catch (ContactApiException $e) {
$this->log->info('[Hubspot] Failed to fetch contact', [
'crm_id' => $crmId,
'reason' => $e->getMessage(),
]);
throw $e;
}
if (! $contact instanceof ContactsWithAssociations) {
throw new CrmException('Contact not found');
}
return [
'id' => $contact->getId(),
'properties' => $contact->getProperties(),
];
}
/**
* This is email search request that Hubspot offers as GET (more generous quota)
*/
public function getContactByEmail(string $email, array $fields = []): array
{
try {
$contact = $this->getNewInstance()->crm()->contacts()->basicApi()->getById(
$email,
implode(',', $fields),
null,
false,
'email'
);
return [
'id' => $contact->getId(),
'properties' => $contact->getProperties(),
];
} catch (ContactApiException $e) {
$this->log->info('[Hubspot] Failed to fetch contact', [
'email' => $email,
'reason' => $e->getMessage(),
]);
return [];
}
}
/**
* @throws CrmException
*/
public function fetchProperty(string $objectType, string $propertyId): Property
{
$result = $this->getNewInstance()->crm()->properties()->coreApi()->getByName($objectType, $propertyId);
if (! $result instanceof Property) {
$this->log->error('[Hubspot] Failed to fetch property', [
'object_type' => $objectType,
'property_id' => $propertyId,
'reason' => $result->getMessage(),
]);
throw new CrmException('Failed to fetch property');
}
return $result;
}
/**
* @return array<CrmFieldOption>
*/
public function fetchPropertyOptions(string $objectType, string $propertyId): array
{
/** @var array<CrmFieldOption> */
return $this->fetchProperty($objectType, $propertyId)->getOptions();
}
/**
* @return array<array{id:string, label:string, deleted:bool}>
*/
public function fetchCallDispositions(): array
{
/** @var Response $response */
$response = $this->getInstance()->engagements()->getCallDispositions();
/**
* @var array<array{
* id:string,
* label:string,
* deleted: bool
* }>
*/
return $response->toArray();
}
/**
* @return array<CrmFieldOption>
*/
public function fetchOpportunityPipelineStages(): array
{
$stages = [];
$apiResponse = $this->getNewInstance()->crm()->pipelines()->pipelinesApi()->getAll('deals');
if ($apiResponse instanceof Error) {
$this->log->error('[Hubspot] Failed to fetch opportunity pipelines', [
'reason' => $apiResponse->getMessage(),
]);
return [];
}
foreach ($apiResponse->getResults() as $pipeline) {
$pipelineStages = array_map(
static function (PipelineStage $stage) {
return [
'id' => $stage->getId(),
'label' => $stage->getLabel(),
];
},
$pipeline->getStages()
);
$stages = array_merge($stages, $pipelineStages);
}
return $stages;
}
public function fetchOpportunityPipelines(): array
{
$pipelines = [];
try {
$apiResponse = $this->makeRequest('/crm/v3/pipelines/deals');
} catch (\Exception $e) {
$this->log->info('[Hubspot] Failed to fetch opportunity pipelines', [
'reason' => $e->getMessage(),
]);
return [];
}
$response = $apiResponse->toArray();
foreach ($response['results'] as $pipeline) {
$pipelines[] = [
'id' => $pipeline['id'],
'label' => $pipeline['label'],
];
}
return $pipelines;
}
/**
* @return array<CrmFieldOption>
*/
public function fetchMeetingOutcomeFieldOptions(Field $field): array
{
return $field->getCrmProviderId() === 'meetingOutcome'
? $this->fetchMeetingOutcomeTypes()
: $this->fetchCallActivityTypes();
}
public function fetchMeetingOutcomeTypes(): array
{
return $this->extractMeetingTypeOptions(
'[URL_WITH_CREDENTIALS] Response $response */
$response = $this->getInstance()
->getClient()
->request('GET', $endpoint);
/**
* @var array<array{
* value: string,
* label: string,
* displayOrder: int
* }> $optionData
*/
$optionData = $response->toArray()['options'] ?? [];
$options = [];
foreach ($optionData as $item) {
$options[] = [
'id' => $item['value'],
'value' => $item['value'],
'label' => $item['label'],
'display_order' => $item['displayOrder'],
];
}
return $options;
}
/**
* @return array<CrmFieldOption>
*/
public function fetchDispositionFieldOptions(): array
{
$options = [];
$dispositions = $this->fetchCallDispositions();
foreach ($dispositions as $disposition) {
if ($disposition['deleted'] !== false) {
continue;
}
$option['value'] = $disposition['id'];
$option['id'] = $disposition['id'];
$option['label'] = $disposition['label'];
$options[] = $option;
}
return $options;
}
/**
* @return array<CrmFieldOption>
*/
public function fetchOpportunityFieldOptions(Field $field): array
{
if ($field->isStageField()) {
return $this->fetchOpportunityPipelineStages();
}
if ($field->isPipelineField()) {
return $this->fetchOpportunityPipelines();
}
return $this->fetchPropertyOptions('deals', $field->getCrmProviderId());
}
/**
* @throws BadRequest
* @throws HubspotException
*/
public function makeRequest(string $endpoint, $method = 'GET', $payload = [], ?string $queryString = null)
{
$endpoint = self::BASE_URL . $endpoint;
if ($method === 'GET') {
$response = $this->getInstance()->getClient()?->request(
method: $method,
endpoint: $endpoint,
query_string: $queryString
);
} else {
$response = $this->getInstance()->getClient()->request($method, $endpoint, [
'json' => ($payload),
]);
}
$max = $response->getHeaderLine('X-HubSpot-RateLimit-Max'); // "110"
$remaining = $response->getHeaderLine('X-HubSpot-RateLimit-Remaining'); // "109"
$interval = $response->getHeaderLine('X-HubSpot-RateLimit-Interval-Milliseconds'); // "10000"
$body = json_decode((string) $response->getBody(), true);
\Illuminate\Support\Facades\Log::channel('custom_channel')->info('$max ' . PHP_EOL . print_r($max, true));
\Illuminate\Support\Facades\Log::channel('custom_channel')->info('$remaining ' . PHP_EOL . print_r($remaining, true));
\Illuminate\Support\Facades\Log::channel('custom_channel')->info('$interval ' . PHP_EOL . print_r($interval, true));
\Illuminate\Support\Facades\Log::channel('custom_channel')->info('$body ' . PHP_EOL . print_r($body, true));
return $response;
}
/**
* @throws BadRequest
* @throws HubspotException
*/
public function createMeeting(array $payload): Response
{
$endpoint = '/crm/v3/objects/meetings';
return $this->makeRequest($endpoint, 'POST', $payload);
}
/**
* @throws BadRequest
* @throws HubspotException
*/
public function updateMeeting(string $meetingId, array $payload): Response
{
$endpoint = '/crm/v3/objects/meetings/' . $meetingId;
return $this->makeRequest($endpoint, 'PATCH', $payload);
}
/**
* @throws \Exception
*/
public function createNote(
string $body,
string $ownerId,
int $timestamp,
string $objectId,
NoteObject $noteObject
): ?string {
try {
$noteInput = new SimplePublicObjectInput([
'properties' => [
'hs_note_body' => $body,
'hubspot_owner_id' => $ownerId,
'hs_timestamp' => $timestamp,
],
]);
// Create note
$note = $this->getNewInstance()->crm()->objects()->basicApi()->create('note', $noteInput);
$this->getNewInstance()->crm()->objects()->associationsApi()->create(
'note',
$note->getId(),
$this->getNoteObject($noteObject),
$objectId,
$this->getNoteAssociationType($noteObject),
);
return $note->getId();
} catch (\Exception $e) {
$this->log->error('[Hubspot] Failed to create note', [
'objectId' => $objectId,
'noteObject' => $noteObject->getObjectType(),
'reason' => $e->getMessage(),
]);
\Sentry::captureException($e);
}
return null;
}
public function updateEngagement(string $objectId, array $engagement, array $metadata): void
{
$this->getInstance()->engagements()->update($objectId, $engagement, $metadata);
}
public function getEngagementData(string $engagementId): array
{
$engagement = $this->getInstance()->engagements()->get($engagementId);
return $engagement->toArray();
}
public function createEngagement(array $engagement, array $associations, array $metadata): Response
{
return $this->getInstance()
->engagements()
->create($engagement, $associations, $metadata);
}
public function isUnauthorizedException(\Exception $e): bool
{
// Check for specific HubSpot API exception types first
if ($e instanceof BadRequest) {
// BadRequest can contain 401 status codes
return $e->getCode() === 401;
}
// Check for HTTP client exceptions with status codes
if ($e instanceof \GuzzleHttp\Exception\RequestException && $e->hasResponse()) {
$response = $e->getResponse();
if ($response !== null) {
return $response->getStatusCode() === 401;
}
}
// Check for Guzzle HTTP exceptions
if ($e instanceof \GuzzleHttp\Exception\ClientException) {
return $e->getCode() === 401;
}
// Fallback to string matching as last resort, but be more specific
$message = strtolower($e->getMessage());
return str_contains($message, '401 unauthorized') ||
str_contains($message, 'http 401') ||
str_contains($message, 'status code 401') ||
(preg_match('/\b401\b/', $message) && str_contains($message, 'unauthorized'));
}
/**
* Validates and refreshes the access token if needed before API requests.
* This ensures long-running processes don't fail due to token expiration.
*
* @throws SocialAccountTokenInvalidException
*/
public function ensureValidToken(): void
{
if ($this->oauthAccount === null) {
return;
}
$newToken = $this->tokenManager->ensureValidToken($this->oauthAccount);
if ($newToken !== null) {
$this->accessToken = $newToken;
}
}
public function getConfig()
{
return $this->config;
}
// returns only active (archived=false)
public function getOwners(): array
{
return $this->getNewInstance()->crm()->owners()->getAll();
}
/**
* @param bool $archived
*
* @return array<Owner>|[]
*/
public function getOwnersArchived(bool $archived = true): array
{
$endpoint = '/crm/v3/owners';
$queryParams = [
'archived' => $archived ? 'true' : 'false',
];
$queryString = http_build_query($queryParams);
$owners = [];
try {
$response = $this->makeRequest(endpoint: $endpoint, queryString: $queryString);
$responseData = $response?->toArray();
foreach ($responseData['results'] as $result) {
try {
$owners[] = Owner::create($result);
} catch (Throwable $e) {
$this->log->error('[HubSpot] Failed to process owner data', [
'result' => $result,
'error' => $e->getMessage(),
]);
continue;
}
}
} catch (Throwable $e) {
$this->log->error('HubSpot] Failed to fetch owners', [
'archived' => $archived,
'error' => $e->getMessage(),
]);
return [];
}
return $owners;
}
public function getMeeting(string $engagementId): ObjectWithAssociations
{
return $this->getNewInstance()->crm()->objects()->basicApi()
->getById('meeting', $engagementId, null, 'contact,company,deal');
}
public function deleteEngagement(string $engagementId): void
{
$this->getInstance()->engagements()->delete((int) $engagementId);
}
public function getAssociationsData(array $ids, string $fromObject, string $toObject): array
{
$associationData = [];
$idChunks = array_chunk($ids, self::ASSOCIATIONS_BATCH_SIZE_LIMIT);
foreach ($idChunks as $idChunk) {
try {
$batchInput = new \HubSpot\Client\Crm\Associations\Model\BatchInputPublicObjectId();
$batchInput->setInputs(array_map(function ($id) {
$publicObjectId = new \HubSpot\Client\Crm\Associations\Model\PublicObjectId();
$publicObjectId->setId($id);
return $publicObjectId;
}, $idChunk));
$associatedObjectsData = $this
->getNewInstance()
->crm()
->associations()
->batchApi()
->read($fromObject, $toObject, $batchInput);
if ($associatedObjectsData instanceof \HubSpot\Client\Crm\Associations\Model\BatchResponsePublicAssociationMulti) {
foreach ($associatedObjectsData->getResults() as $association) {
$from = $association->getFrom()->getId();
$toAssociations = $association->getTo();
if (! empty($toAssociations)) {
$associationData[$from] = array_map(function ($item) {
return $item->getId();
}, $toAssociations);
}
}
}
} catch (\Exception $e) {
$this->log->error('[Hubspot] Failed to fetch associations', [
'from_object' => $fromObject,
'to_object' => $toObject,
'reason' => $e->getMessage(),
]);
}
}
return $associationData;
}
/**
* @throws \Exception
*/
private function getNoteAssociationType(NoteObject $noteObject): string
{
return match($noteObject) {
NoteObject::Opportunity => 'note_to_deal',
NoteObject::Lead, NoteObject::Contact => 'note_to_contact', // or 'note_to_lead' if your portal supports it
NoteObject::Account => 'note_to_company',
NoteObject::Call, NoteObject::Event => throw new \Exception('Not supported'),
};
}
/**
* @throws \Exception
*/
private function getNoteObject(NoteObject $noteObject): string
{
return match($noteObject) {
NoteObject::Opportunity => 'deal',
NoteObject::Lead, NoteObject::Contact => 'contact',
NoteObject::Account => 'company',
NoteObject::Call, NoteObject::Event => throw new \Exception('Not supported'),
};
}
public function addAssociations(string $objectType, string $associationType, array $payload): Response
{
$endpoint = "/crm/v4/associations/$objectType/$associationType/batch/create";
return $this->makeRequest($endpoint, 'POST', $payload);
}
public function removeAssociations(string $objectType, string $associationType, array $payload): Response
{
$endpoint = "/crm/v4/associations/$objectType/$associationType/batch/archive";
return $this->makeRequest($endpoint, 'POST', $payload);
}
}
Project
Project
New File or Directory…
Expand Selected
Collapse All
Options
Hide...
|
NULL
|
NULL
|
NULL
|
NULL
|
|
2710
|
112
|
27
|
2026-05-07T11:36:35.455775+00:00
|
/Users/lukas/.screenpipe/data/data/2026-05-07/1778 /Users/lukas/.screenpipe/data/data/2026-05-07/1778153795455_m2.jpg...
|
PhpStorm
|
faVsco.js – laravel.log
|
True
|
NULL
|
monitor_2
|
NULL
|
NULL
|
NULL
|
NULL
|
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
[2026-05-07 11:36:26] local.INFO: Jiminny\Console\Commands\Command::run Memory usage before starting command {"command":"crm:sync-hubspot-objects","memoryBeforeCommandInMb":60.0,"memoryPeakBeforeCommandInMb":99.727} {"correlation_id":"f71ac7c4-e9d4-46ad-be82-47e68a8f1199","trace_id":"5c938a2d-aea9-4025-a7f7-733d8e8e9711"}
[2026-05-07 11:36:26] local.INFO: Jiminny\Console\Commands\Command::run Memory usage for command {"command":"crm:sync-hubspot-objects","memoryBeforeCommandInMb":60.0,"memoryAfterCommandInMB":62.0,"memoryPeakBeforeCommandInMb":99.727,"memoryPeakAfterCommandInMB":99.727} {"correlation_id":"f71ac7c4-e9d4-46ad-be82-47e68a8f1199","trace_id":"5c938a2d-aea9-4025-a7f7-733d8e8e9711"}
[2026-05-07 11:36:27] local.INFO: [SyncHubspotObjects] Starting sync {"team":"abae74b8-bfa8-4383-9a7f-89f4bf2bdbb4","usage":24412984,"real_usage":65011712,"pid":13732} {"correlation_id":"766570a3-89a2-4427-81c2-c70a7c780ee3","trace_id":"5c938a2d-aea9-4025-a7f7-733d8e8e9711"}
[2026-05-07 11:36:27] local.INFO: [SocialAccountService] Fetching token {"socialAccountId":1499,"provider":"hubspot"} {"correlation_id":"766570a3-89a2-4427-81c2-c70a7c780ee3","trace_id":"5c938a2d-aea9-4025-a7f7-733d8e8e9711"}
[2026-05-07 11:36:27] local.INFO: [SocialAccountService] Token needs refreshing {"socialAccountId":1499,"provider":"hubspot"} {"correlation_id":"766570a3-89a2-4427-81c2-c70a7c780ee3","trace_id":"5c938a2d-aea9-4025-a7f7-733d8e8e9711"}
[2026-05-07 11:36:27] local.INFO: [EncryptedTokenManager] Generating access token. {"mode":"legacy"} {"correlation_id":"766570a3-89a2-4427-81c2-c70a7c780ee3","trace_id":"5c938a2d-aea9-4025-a7f7-733d8e8e9711"}
[2026-05-07 11:36:27] local.INFO: [SocialAccountService] Refreshing token from provider {"socialAccountId":1499,"provider":"hubspot","refreshToken":"96f94c623a404e02ebdbf07f1b75707bb6cdbf848cbf45d418baf608c41a8d86","state":"connected"} {"correlation_id":"766570a3-89a2-4427-81c2-c70a7c780ee3","trace_id":"5c938a2d-aea9-4025-a7f7-733d8e8e9711"}
[2026-05-07 11:36:27] local.INFO: [SocialAccountObserver] Saving model {"correlation_id":"766570a3-89a2-4427-81c2-c70a7c780ee3","trace_id":"5c938a2d-aea9-4025-a7f7-733d8e8e9711"}
[2026-05-07 11:36:27] local.INFO: [SocialAccountObserver] Access token was modified, encrypting {"correlation_id":"766570a3-89a2-4427-81c2-c70a7c780ee3","trace_id":"5c938a2d-aea9-4025-a7f7-733d8e8e9711"}
[2026-05-07 11:36:28] local.INFO: [SocialAccountService] Token refreshed {"socialAccountId":1499,"provider":"hubspot","state":"connected"} {"correlation_id":"766570a3-89a2-4427-81c2-c70a7c780ee3","trace_id":"5c938a2d-aea9-4025-a7f7-733d8e8e9711"}
[2026-05-07 11:36:28] local.INFO: [CrmOwnerResolver] Integration owner matched as CRM Owner {"crm_provider":"hubspot","crm_owner":148,"team_id":2} {"correlation_id":"766570a3-89a2-4427-81c2-c70a7c780ee3","trace_id":"5c938a2d-aea9-4025-a7f7-733d8e8e9711"}
[2026-05-07 11:36:28] local.INFO: [HubSpot] Syncing opportunities using strategy: lastModified {"team":2} {"correlation_id":"766570a3-89a2-4427-81c2-c70a7c780ee3","trace_id":"5c938a2d-aea9-4025-a7f7-733d8e8e9711"}
[2026-05-07 11:36:28] local.INFO: [Hubspot] Pagination completed {"team_id":2,"endpoint":"[URL_WITH_CREDENTIALS] CrmFieldOption array{id:string, label:string, value?:string}
*/
class Client extends BaseClient implements HubspotClientInterface
{
public const string MIN_API_VERSION = '2';
public const string BASE_URL = '[URL_WITH_CREDENTIALS] T
* @param callable(): T $apiCall
* @return T
*
* @throws RateLimitException
*/
private function executeRequest(callable $apiCall)
{
if (! $this->rateLimiter->canMakeRequest($this->config)) {
$retryAfter = $this->rateLimiter->requestAvailableIn($this->config);
$this->log->warning('[Hubspot] Rate limit exceeded, deferring request', [
'team_id' => $this->config->team_id,
'config_id' => $this->config->getId(),
'retry_after' => $retryAfter,
]);
throw new RateLimitException(
'Hubspot rate limit reached for configuration ' . $this->config->getId(),
$retryAfter,
);
}
$this->rateLimiter->incrementRequestCount($this->config);
try {
return $apiCall();
} catch (Throwable $e) {
if ($this->isHubspotRateLimit($e)) {
$retryAfter = $this->parseRetryAfter($e);
$this->log->warning('[Hubspot] Received 429 from API', [
'team_id' => $this->config->team_id,
'config_id' => $this->config->getId(),
'retry_after' => $retryAfter,
'reason' => $e->getMessage(),
]);
throw new RateLimitException('Hubspot returned 429', $retryAfter, $e);
}
throw $e;
}
}
private function isHubspotRateLimit(Throwable $e): bool
{
return method_exists($e, 'getCode') && (int) $e->getCode() === 429;
}
private function parseRetryAfter(Throwable $e): int
{
if (method_exists($e, 'getResponseHeaders')) {
$headers = $e->getResponseHeaders() ?: [];
$value = $headers['Retry-After'] ?? $headers['retry-after'] ?? null;
if (is_array($value)) {
$value = $value[0] ?? null;
}
if (is_numeric($value)) {
return (int) $value;
}
}
return 10;
}
public function getMinimumApiVersion(): string
{
return self::MIN_API_VERSION;
}
public function getInstance(): Factory
{
return new Factory([
'key' => $this->accessToken,
'oauth2' => true,
'base_url' => $this->baseUrl,
]);
}
public function getNewInstance(): Discovery
{
return \HubSpot\Factory::createWithAccessToken($this->accessToken);
}
/**
* Secondly and daily limits for Hubspot API
*
* Product Tier: Free & Starter | Professional & Enterprise | API add-on (any tier)
* Burst: 100/10 seconds | 150/10 seconds | 200/10 seconds
* Daily: 250,000 | 500,000 | 1,000,000
*
* Official documentation states: The search endpoints are rate limited to five requests per second.
* Since with 5 RPS were still hitting secondly rate limits we lowered it to 4
*/
public function getPaginatedData(array $payload, string $type, int $offset = 0): array
{
$total = 0;
$lastId = null;
$rows = [];
foreach ($this->getPaginatedDataGenerator($payload, $type, $offset, $total, $lastId) as $row) {
$rows[] = $row;
}
return ['results' => $rows, 'total' => $total, 'last_record' => $lastId];
}
/**
* @throws HubspotException
* @throws SocialAccountTokenInvalidException
* @throws BadRequest
*/
public function getPaginatedDataGenerator(
array $payload,
string $type,
int $offset = 0,
int &$total = 0,
?string &$lastRecordId = null
): \Generator {
return $this->paginationService->getPaginatedDataGenerator(
$this,
$payload,
$type,
$offset,
$total,
$lastRecordId
);
}
/**
* @throws DealApiException
* @throws CrmException
*/
public function getOpportunityById(string $crmId, array $fields): array
{
try {
$deal = $this->executeRequest(fn () => $this->getNewInstance()->crm()->deals()->basicApi()->getById(
$crmId,
implode(',', $fields),
'companies,contacts'
));
} catch (DealApiException $e) {
$this->log->info('[Hubspot] Failed to fetch opportunity', [
'crm_id' => $crmId,
'reason' => $e->getMessage(),
]);
throw $e;
}
if (! $deal instanceof DealWithAssociations) {
throw new CrmException('Deal not found');
}
return [
'id' => $deal->getId(),
'properties' => $deal->getProperties(),
'associations' => $deal->getAssociations(),
];
}
/**
* Generic batch read method for HubSpot objects
*
* @param string $objectType The object type ('deals', 'companies', 'contacts')
* @param array<string> $crmIds Array of HubSpot object IDs (max 100)
* @param array<string> $fields Array of property names to fetch
*
* @return array<string, array> Array keyed by CRM ID with object data
*/
private function batchReadObjects(string $objectType, array $crmIds, array $fields): array
{
if (empty($crmIds)) {
return [];
}
$this->validateBatchSize($objectType, $crmIds);
$this->ensureValidToken();
try {
$batchConfig = $this->createBatchConfiguration($objectType);
$batchReadRequest = $this->prepareBatchRequest($batchConfig, $crmIds, $fields);
$response = $batchConfig['api']->read($batchReadRequest);
$this->validateApiResponse($response, $objectType);
$results = $this->processApiResults($response);
$this->logBatchResults($objectType, $crmIds, $results);
return $results;
} catch (\Throwable $e) {
$this->handleBatchError($e, $objectType, $crmIds);
}
}
private function validateBatchSize(string $objectType, array $crmIds): void
{
if (count($crmIds) > 100) {
throw new \InvalidArgumentException("Batch size cannot exceed 100 {$objectType}");
}
}
private function createBatchConfiguration(string $objectType): array
{
$configurations = [
'deals' => [
'batchReadRequest' => new \HubSpot\Client\Crm\Deals\Model\BatchReadInputSimplePublicObjectId(),
'inputClass' => \HubSpot\Client\Crm\Deals\Model\SimplePublicObjectId::class,
'api' => $this->getNewInstance()->crm()->deals()->batchApi(),
],
'companies' => [
'batchReadRequest' => new \HubSpot\Client\Crm\Companies\Model\BatchReadInputSimplePublicObjectId(),
'inputClass' => \HubSpot\Client\Crm\Companies\Model\SimplePublicObjectId::class,
'api' => $this->getNewInstance()->crm()->companies()->batchApi(),
],
'contacts' => [
'batchReadRequest' => new \HubSpot\Client\Crm\Contacts\Model\BatchReadInputSimplePublicObjectId(),
'inputClass' => \HubSpot\Client\Crm\Contacts\Model\SimplePublicObjectId::class,
'api' => $this->getNewInstance()->crm()->contacts()->batchApi(),
],
];
if (! isset($configurations[$objectType])) {
throw new \InvalidArgumentException("Unsupported object type: {$objectType}");
}
return $configurations[$objectType];
}
private function prepareBatchRequest(array $batchConfig, array $crmIds, array $fields): object
{
$batchReadRequest = $batchConfig['batchReadRequest'];
$inputClass = $batchConfig['inputClass'];
$inputs = array_map(function ($crmId) use ($inputClass) {
$input = new $inputClass();
$input->setId($crmId);
return $input;
}, $crmIds);
$batchReadRequest->setInputs($inputs);
$batchReadRequest->setProperties($fields);
return $batchReadRequest;
}
private function validateApiResponse($response, string $objectType): void
{
if (! $response) {
throw new CrmException("HubSpot API returned null response for {$objectType} batch read");
}
}
private function processApiResults($response): array
{
$results = [];
$responseResults = $response->getResults();
if ($responseResults) {
foreach ($responseResults as $object) {
if ($object && $object->getId()) {
$results[$object->getId()] = [
'id' => $object->getId(),
'properties' => $object->getProperties() ?: [],
];
}
}
}
return $results;
}
private function logBatchResults(string $objectType, array $crmIds, array $results): void
{
$this->log->info("[HubSpot] Batch fetched {$objectType}", [
'requested_count' => count($crmIds),
'returned_count' => count($results),
'crm_ids' => $crmIds,
]);
}
private function handleBatchError(\Throwable $e, string $objectType, array $crmIds): void
{
$errorMessage = $e->getMessage() ?: 'Unknown error';
$errorTrace = $e->getTraceAsString() ?: 'No trace available';
$this->log->error("[HubSpot] Failed to batch fetch {$objectType}", [
'crm_ids' => $crmIds,
'error' => $errorMessage,
'trace' => $errorTrace,
]);
throw new CrmException("Failed to batch fetch {$objectType}: " . $errorMessage);
}
/**
* Batch read multiple opportunities by their CRM IDs
*
* @param array<string> $crmIds Array of HubSpot deal IDs (max 100)
* @param array<string> $fields Array of property names to fetch
*
* @return array<string, array> Array keyed by CRM ID with opportunity data
*/
public function getOpportunitiesByIds(array $crmIds, array $fields): array
{
return $this->batchReadObjects('deals', $crmIds, $fields);
}
/**
* Batch read multiple companies by their CRM IDs
*
* @param array<string> $crmIds Array of HubSpot company IDs (max 100)
* @param array<string> $fields Array of property names to fetch
*
* @return array<string, array> Array keyed by CRM ID with company data
*/
public function getCompaniesByIds(array $crmIds, array $fields): array
{
return $this->batchReadObjects('companies', $crmIds, $fields);
}
/**
* Batch read multiple contacts by their CRM IDs
*
* @param array<string> $crmIds Array of HubSpot contact IDs (max 100)
* @param array<string> $fields Array of property names to fetch
*
* @return array<string, array> Array keyed by CRM ID with contact data
*/
public function getContactsByIds(array $crmIds, array $fields): array
{
return $this->batchReadObjects('contacts', $crmIds, $fields);
}
/**
* @throws CompanyApiException
* @throws CrmException
*/
public function getAccountById(string $crmId, array $fields): array
{
try {
$company = $this->getNewInstance()->crm()->companies()->basicApi()->getById(
$crmId,
implode(',', $fields),
);
} catch (CompanyApiException $e) {
$this->log->info('[Hubspot] Failed to fetch account', [
'crm_id' => $crmId,
'reason' => $e->getMessage(),
]);
throw $e;
}
if (! $company instanceof CompaniesWithAssociations) {
throw new CrmException('Account not found');
}
return [
'id' => $company->getId(),
'properties' => $company->getProperties(),
];
}
/**
* @throws ContactApiException
* @throws CrmException
*/
public function getContactById(string $crmId, array $fields): array
{
try {
$contact = $this->getNewInstance()->crm()->contacts()->basicApi()->getById(
$crmId,
implode(',', $fields)
);
} catch (ContactApiException $e) {
$this->log->info('[Hubspot] Failed to fetch contact', [
'crm_id' => $crmId,
'reason' => $e->getMessage(),
]);
throw $e;
}
if (! $contact instanceof ContactsWithAssociations) {
throw new CrmException('Contact not found');
}
return [
'id' => $contact->getId(),
'properties' => $contact->getProperties(),
];
}
/**
* This is email search request that Hubspot offers as GET (more generous quota)
*/
public function getContactByEmail(string $email, array $fields = []): array
{
try {
$contact = $this->getNewInstance()->crm()->contacts()->basicApi()->getById(
$email,
implode(',', $fields),
null,
false,
'email'
);
return [
'id' => $contact->getId(),
'properties' => $contact->getProperties(),
];
} catch (ContactApiException $e) {
$this->log->info('[Hubspot] Failed to fetch contact', [
'email' => $email,
'reason' => $e->getMessage(),
]);
return [];
}
}
/**
* @throws CrmException
*/
public function fetchProperty(string $objectType, string $propertyId): Property
{
$result = $this->getNewInstance()->crm()->properties()->coreApi()->getByName($objectType, $propertyId);
if (! $result instanceof Property) {
$this->log->error('[Hubspot] Failed to fetch property', [
'object_type' => $objectType,
'property_id' => $propertyId,
'reason' => $result->getMessage(),
]);
throw new CrmException('Failed to fetch property');
}
return $result;
}
/**
* @return array<CrmFieldOption>
*/
public function fetchPropertyOptions(string $objectType, string $propertyId): array
{
/** @var array<CrmFieldOption> */
return $this->fetchProperty($objectType, $propertyId)->getOptions();
}
/**
* @return array<array{id:string, label:string, deleted:bool}>
*/
public function fetchCallDispositions(): array
{
/** @var Response $response */
$response = $this->getInstance()->engagements()->getCallDispositions();
/**
* @var array<array{
* id:string,
* label:string,
* deleted: bool
* }>
*/
return $response->toArray();
}
/**
* @return array<CrmFieldOption>
*/
public function fetchOpportunityPipelineStages(): array
{
$stages = [];
$apiResponse = $this->getNewInstance()->crm()->pipelines()->pipelinesApi()->getAll('deals');
if ($apiResponse instanceof Error) {
$this->log->error('[Hubspot] Failed to fetch opportunity pipelines', [
'reason' => $apiResponse->getMessage(),
]);
return [];
}
foreach ($apiResponse->getResults() as $pipeline) {
$pipelineStages = array_map(
static function (PipelineStage $stage) {
return [
'id' => $stage->getId(),
'label' => $stage->getLabel(),
];
},
$pipeline->getStages()
);
$stages = array_merge($stages, $pipelineStages);
}
return $stages;
}
public function fetchOpportunityPipelines(): array
{
$pipelines = [];
try {
$apiResponse = $this->makeRequest('/crm/v3/pipelines/deals');
} catch (\Exception $e) {
$this->log->info('[Hubspot] Failed to fetch opportunity pipelines', [
'reason' => $e->getMessage(),
]);
return [];
}
$response = $apiResponse->toArray();
foreach ($response['results'] as $pipeline) {
$pipelines[] = [
'id' => $pipeline['id'],
'label' => $pipeline['label'],
];
}
return $pipelines;
}
/**
* @return array<CrmFieldOption>
*/
public function fetchMeetingOutcomeFieldOptions(Field $field): array
{
return $field->getCrmProviderId() === 'meetingOutcome'
? $this->fetchMeetingOutcomeTypes()
: $this->fetchCallActivityTypes();
}
public function fetchMeetingOutcomeTypes(): array
{
return $this->extractMeetingTypeOptions(
'[URL_WITH_CREDENTIALS] Response $response */
$response = $this->getInstance()
->getClient()
->request('GET', $endpoint);
/**
* @var array<array{
* value: string,
* label: string,
* displayOrder: int
* }> $optionData
*/
$optionData = $response->toArray()['options'] ?? [];
$options = [];
foreach ($optionData as $item) {
$options[] = [
'id' => $item['value'],
'value' => $item['value'],
'label' => $item['label'],
'display_order' => $item['displayOrder'],
];
}
return $options;
}
/**
* @return array<CrmFieldOption>
*/
public function fetchDispositionFieldOptions(): array
{
$options = [];
$dispositions = $this->fetchCallDispositions();
foreach ($dispositions as $disposition) {
if ($disposition['deleted'] !== false) {
continue;
}
$option['value'] = $disposition['id'];
$option['id'] = $disposition['id'];
$option['label'] = $disposition['label'];
$options[] = $option;
}
return $options;
}
/**
* @return array<CrmFieldOption>
*/
public function fetchOpportunityFieldOptions(Field $field): array
{
if ($field->isStageField()) {
return $this->fetchOpportunityPipelineStages();
}
if ($field->isPipelineField()) {
return $this->fetchOpportunityPipelines();
}
return $this->fetchPropertyOptions('deals', $field->getCrmProviderId());
}
/**
* @throws BadRequest
* @throws HubspotException
*/
public function makeRequest(string $endpoint, $method = 'GET', $payload = [], ?string $queryString = null)
{
$endpoint = self::BASE_URL . $endpoint;
if ($method === 'GET') {
$response = $this->getInstance()->getClient()?->request(
method: $method,
endpoint: $endpoint,
query_string: $queryString
);
} else {
$response = $this->getInstance()->getClient()->request($method, $endpoint, [
'json' => ($payload),
]);
}
$max = $response->getHeaderLine('X-HubSpot-RateLimit-Max'); // "110"
$remaining = $response->getHeaderLine('X-HubSpot-RateLimit-Remaining'); // "109"
$interval = $response->getHeaderLine('X-HubSpot-RateLimit-Interval-Milliseconds'); // "10000"
$body = json_decode((string) $response->getBody(), true);
\Illuminate\Support\Facades\Log::channel('custom_channel')->info('$max ' . PHP_EOL . print_r($max, true));
\Illuminate\Support\Facades\Log::channel('custom_channel')->info('$remaining ' . PHP_EOL . print_r($remaining, true));
\Illuminate\Support\Facades\Log::channel('custom_channel')->info('$interval ' . PHP_EOL . print_r($interval, true));
\Illuminate\Support\Facades\Log::channel('custom_channel')->info('$body ' . PHP_EOL . print_r($body, true));
return $response;
}
/**
* @throws BadRequest
* @throws HubspotException
*/
public function createMeeting(array $payload): Response
{
$endpoint = '/crm/v3/objects/meetings';
return $this->makeRequest($endpoint, 'POST', $payload);
}
/**
* @throws BadRequest
* @throws HubspotException
*/
public function updateMeeting(string $meetingId, array $payload): Response
{
$endpoint = '/crm/v3/objects/meetings/' . $meetingId;
return $this->makeRequest($endpoint, 'PATCH', $payload);
}
/**
* @throws \Exception
*/
public function createNote(
string $body,
string $ownerId,
int $timestamp,
string $objectId,
NoteObject $noteObject
): ?string {
try {
$noteInput = new SimplePublicObjectInput([
'properties' => [
'hs_note_body' => $body,
'hubspot_owner_id' => $ownerId,
'hs_timestamp' => $timestamp,
],
]);
// Create note
$note = $this->getNewInstance()->crm()->objects()->basicApi()->create('note', $noteInput);
$this->getNewInstance()->crm()->objects()->associationsApi()->create(
'note',
$note->getId(),
$this->getNoteObject($noteObject),
$objectId,
$this->getNoteAssociationType($noteObject),
);
return $note->getId();
} catch (\Exception $e) {
$this->log->error('[Hubspot] Failed to create note', [
'objectId' => $objectId,
'noteObject' => $noteObject->getObjectType(),
'reason' => $e->getMessage(),
]);
\Sentry::captureException($e);
}
return null;
}
public function updateEngagement(string $objectId, array $engagement, array $metadata): void
{
$this->getInstance()->engagements()->update($objectId, $engagement, $metadata);
}
public function getEngagementData(string $engagementId): array
{
$engagement = $this->getInstance()->engagements()->get($engagementId);
return $engagement->toArray();
}
public function createEngagement(array $engagement, array $associations, array $metadata): Response
{
return $this->getInstance()
->engagements()
->create($engagement, $associations, $metadata);
}
public function isUnauthorizedException(\Exception $e): bool
{
// Check for specific HubSpot API exception types first
if ($e instanceof BadRequest) {
// BadRequest can contain 401 status codes
return $e->getCode() === 401;
}
// Check for HTTP client exceptions with status codes
if ($e instanceof \GuzzleHttp\Exception\RequestException && $e->hasResponse()) {
$response = $e->getResponse();
if ($response !== null) {
return $response->getStatusCode() === 401;
}
}
// Check for Guzzle HTTP exceptions
if ($e instanceof \GuzzleHttp\Exception\ClientException) {
return $e->getCode() === 401;
}
// Fallback to string matching as last resort, but be more specific
$message = strtolower($e->getMessage());
return str_contains($message, '401 unauthorized') ||
str_contains($message, 'http 401') ||
str_contains($message, 'status code 401') ||
(preg_match('/\b401\b/', $message) && str_contains($message, 'unauthorized'));
}
/**
* Validates and refreshes the access token if needed before API requests.
* This ensures long-running processes don't fail due to token expiration.
*
* @throws SocialAccountTokenInvalidException
*/
public function ensureValidToken(): void
{
if ($this->oauthAccount === null) {
return;
}
$newToken = $this->tokenManager->ensureValidToken($this->oauthAccount);
if ($newToken !== null) {
$this->accessToken = $newToken;
}
}
public function getConfig()
{
return $this->config;
}
// returns only active (archived=false)
public function getOwners(): array
{
return $this->getNewInstance()->crm()->owners()->getAll();
}
/**
* @param bool $archived
*
* @return array<Owner>|[]
*/
public function getOwnersArchived(bool $archived = true): array
{
$endpoint = '/crm/v3/owners';
$queryParams = [
'archived' => $archived ? 'true' : 'false',
];
$queryString = http_build_query($queryParams);
$owners = [];
try {
$response = $this->makeRequest(endpoint: $endpoint, queryString: $queryString);
$responseData = $response?->toArray();
foreach ($responseData['results'] as $result) {
try {
$owners[] = Owner::create($result);
} catch (Throwable $e) {
$this->log->error('[HubSpot] Failed to process owner data', [
'result' => $result,
'error' => $e->getMessage(),
]);
continue;
}
}
} catch (Throwable $e) {
$this->log->error('HubSpot] Failed to fetch owners', [
'archived' => $archived,
'error' => $e->getMessage(),
]);
return [];
}
return $owners;
}
public function getMeeting(string $engagementId): ObjectWithAssociations
{
return $this->getNewInstance()->crm()->objects()->basicApi()
->getById('meeting', $engagementId, null, 'contact,company,deal');
}
public function deleteEngagement(string $engagementId): void
{
$this->getInstance()->engagements()->delete((int) $engagementId);
}
public function getAssociationsData(array $ids, string $fromObject, string $toObject): array
{
$associationData = [];
$idChunks = array_chunk($ids, self::ASSOCIATIONS_BATCH_SIZE_LIMIT);
foreach ($idChunks as $idChunk) {
try {
$batchInput = new \HubSpot\Client\Crm\Associations\Model\BatchInputPublicObjectId();
$batchInput->setInputs(array_map(function ($id) {
$publicObjectId = new \HubSpot\Client\Crm\Associations\Model\PublicObjectId();
$publicObjectId->setId($id);
return $publicObjectId;
}, $idChunk));
$associatedObjectsData = $this
->getNewInstance()
->crm()
->associations()
->batchApi()
->read($fromObject, $toObject, $batchInput);
if ($associatedObjectsData instanceof \HubSpot\Client\Crm\Associations\Model\BatchResponsePublicAssociationMulti) {
foreach ($associatedObjectsData->getResults() as $association) {
$from = $association->getFrom()->getId();
$toAssociations = $association->getTo();
if (! empty($toAssociations)) {
$associationData[$from] = array_map(function ($item) {
return $item->getId();
}, $toAssociations);
}
}
}
} catch (\Exception $e) {
$this->log->error('[Hubspot] Failed to fetch associations', [
'from_object' => $fromObject,
'to_object' => $toObject,
'reason' => $e->getMessage(),
]);
}
}
return $associationData;
}
/**
* @throws \Exception
*/
private function getNoteAssociationType(NoteObject $noteObject): string
{
return match($noteObject) {
NoteObject::Opportunity => 'note_to_deal',
NoteObject::Lead, NoteObject::Contact => 'note_to_contact', // or 'note_to_lead' if your portal supports it
NoteObject::Account => 'note_to_company',
NoteObject::Call, NoteObject::Event => throw new \Exception('Not supported'),
};
}
/**
* @throws \Exception
*/
private function getNoteObject(NoteObject $noteObject): string
{
return match($noteObject) {
NoteObject::Opportunity => 'deal',
NoteObject::Lead, NoteObject::Contact => 'contact',
NoteObject::Account => 'company',
NoteObject::Call, NoteObject::Event => throw new \Exception('Not supported'),
};
}
public function addAssociations(string $objectType, string $associationType, array $payload): Response
{
$endpoint = "/crm/v4/associations/$objectType/$associationType/batch/create";
return $this->makeRequest($endpoint, 'POST', $payload);
}
public function removeAssociations(string $objectType, string $associationType, array $payload): Response
{
$endpoint = "/crm/v4/associations/$objectType/$associationType/batch/archive";
return $this->makeRequest($endpoint, 'POST', $payload);
}
}
Project
Project
New File or Directory…
Expand Selected
Collapse All
Options
Hide...
|
[{"role":"AXButton","text" [{"role":"AXButton","text":"Project: faVsco.js, menu","depth":5,"bounds":{"left":0.025930852,"top":0.019952115,"width":0.03856383,"height":0.025538707},"on_screen":true,"help_text":"~/jiminny/app","role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"master, menu","depth":5,"bounds":{"left":0.064494684,"top":0.019952115,"width":0.034242023,"height":0.025538707},"on_screen":true,"help_text":"Git Branch: master","role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Start Listening for PHP Debug Connections","depth":5,"bounds":{"left":0.8081782,"top":0.019952115,"width":0.011303191,"height":0.025538707},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"AskJiminnyReportActivityServiceTest","depth":6,"bounds":{"left":0.8234708,"top":0.019952115,"width":0.09208777,"height":0.025538707},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Run 'AskJiminnyReportActivityServiceTest'","depth":6,"bounds":{"left":0.9155585,"top":0.019952115,"width":0.011303191,"height":0.025538707},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Debug 'AskJiminnyReportActivityServiceTest'","depth":6,"bounds":{"left":0.9268617,"top":0.019952115,"width":0.011303191,"height":0.025538707},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"More Actions","depth":6,"bounds":{"left":0.9381649,"top":0.019952115,"width":0.011303191,"height":0.025538707},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"JetBrains AI","depth":5,"bounds":{"left":0.96609044,"top":0.019952115,"width":0.011303191,"height":0.025538707},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Search Everywhere","depth":5,"bounds":{"left":0.9773936,"top":0.019952115,"width":0.011303191,"height":0.025538707},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"IDE and Project Settings","depth":5,"bounds":{"left":0.9886968,"top":0.019952115,"width":0.011303186,"height":0.025538707},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXTextArea","text":"[2026-05-07 11:36:26] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage before starting command {\"command\":\"crm:sync-hubspot-objects\",\"memoryBeforeCommandInMb\":60.0,\"memoryPeakBeforeCommandInMb\":99.727} {\"correlation_id\":\"f71ac7c4-e9d4-46ad-be82-47e68a8f1199\",\"trace_id\":\"5c938a2d-aea9-4025-a7f7-733d8e8e9711\"}\n[2026-05-07 11:36:26] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage for command {\"command\":\"crm:sync-hubspot-objects\",\"memoryBeforeCommandInMb\":60.0,\"memoryAfterCommandInMB\":62.0,\"memoryPeakBeforeCommandInMb\":99.727,\"memoryPeakAfterCommandInMB\":99.727} {\"correlation_id\":\"f71ac7c4-e9d4-46ad-be82-47e68a8f1199\",\"trace_id\":\"5c938a2d-aea9-4025-a7f7-733d8e8e9711\"}\n[2026-05-07 11:36:27] local.INFO: [SyncHubspotObjects] Starting sync {\"team\":\"abae74b8-bfa8-4383-9a7f-89f4bf2bdbb4\",\"usage\":24412984,\"real_usage\":65011712,\"pid\":13732} {\"correlation_id\":\"766570a3-89a2-4427-81c2-c70a7c780ee3\",\"trace_id\":\"5c938a2d-aea9-4025-a7f7-733d8e8e9711\"}\n[2026-05-07 11:36:27] local.INFO: [SocialAccountService] Fetching token {\"socialAccountId\":1499,\"provider\":\"hubspot\"} {\"correlation_id\":\"766570a3-89a2-4427-81c2-c70a7c780ee3\",\"trace_id\":\"5c938a2d-aea9-4025-a7f7-733d8e8e9711\"}\n[2026-05-07 11:36:27] local.INFO: [SocialAccountService] Token needs refreshing {\"socialAccountId\":1499,\"provider\":\"hubspot\"} {\"correlation_id\":\"766570a3-89a2-4427-81c2-c70a7c780ee3\",\"trace_id\":\"5c938a2d-aea9-4025-a7f7-733d8e8e9711\"}\n[2026-05-07 11:36:27] local.INFO: [EncryptedTokenManager] Generating access token. {\"mode\":\"legacy\"} {\"correlation_id\":\"766570a3-89a2-4427-81c2-c70a7c780ee3\",\"trace_id\":\"5c938a2d-aea9-4025-a7f7-733d8e8e9711\"}\n[2026-05-07 11:36:27] local.INFO: [SocialAccountService] Refreshing token from provider {\"socialAccountId\":1499,\"provider\":\"hubspot\",\"refreshToken\":\"96f94c623a404e02ebdbf07f1b75707bb6cdbf848cbf45d418baf608c41a8d86\",\"state\":\"connected\"} {\"correlation_id\":\"766570a3-89a2-4427-81c2-c70a7c780ee3\",\"trace_id\":\"5c938a2d-aea9-4025-a7f7-733d8e8e9711\"}\n[2026-05-07 11:36:27] local.INFO: [SocialAccountObserver] Saving model {\"correlation_id\":\"766570a3-89a2-4427-81c2-c70a7c780ee3\",\"trace_id\":\"5c938a2d-aea9-4025-a7f7-733d8e8e9711\"}\n[2026-05-07 11:36:27] local.INFO: [SocialAccountObserver] Access token was modified, encrypting {\"correlation_id\":\"766570a3-89a2-4427-81c2-c70a7c780ee3\",\"trace_id\":\"5c938a2d-aea9-4025-a7f7-733d8e8e9711\"}\n[2026-05-07 11:36:28] local.INFO: [SocialAccountService] Token refreshed {\"socialAccountId\":1499,\"provider\":\"hubspot\",\"state\":\"connected\"} {\"correlation_id\":\"766570a3-89a2-4427-81c2-c70a7c780ee3\",\"trace_id\":\"5c938a2d-aea9-4025-a7f7-733d8e8e9711\"}\n[2026-05-07 11:36:28] local.INFO: [CrmOwnerResolver] Integration owner matched as CRM Owner {\"crm_provider\":\"hubspot\",\"crm_owner\":148,\"team_id\":2} {\"correlation_id\":\"766570a3-89a2-4427-81c2-c70a7c780ee3\",\"trace_id\":\"5c938a2d-aea9-4025-a7f7-733d8e8e9711\"}\n[2026-05-07 11:36:28] local.INFO: [HubSpot] Syncing opportunities using strategy: lastModified {\"team\":2} {\"correlation_id\":\"766570a3-89a2-4427-81c2-c70a7c780ee3\",\"trace_id\":\"5c938a2d-aea9-4025-a7f7-733d8e8e9711\"}\n[2026-05-07 11:36:28] local.INFO: [Hubspot] Pagination completed {\"team_id\":2,\"endpoint\":\"https://api.hubapi.com/crm/v3/objects/deals/search\",\"total_requests\":1,\"total_records_fetched\":0,\"total_elapsed_seconds\":0.65,\"average_seconds_per_request\":0.65} {\"correlation_id\":\"766570a3-89a2-4427-81c2-c70a7c780ee3\",\"trace_id\":\"5c938a2d-aea9-4025-a7f7-733d8e8e9711\"}\n[2026-05-07 11:36:28] local.INFO: [HubSpot] Synced opportunities {\"team\":2,\"strategies\":\"lastModified\",\"sync_count\":0,\"total\":0,\"last_synced_id\":null,\"duration_ms\":682.84} {\"correlation_id\":\"766570a3-89a2-4427-81c2-c70a7c780ee3\",\"trace_id\":\"5c938a2d-aea9-4025-a7f7-733d8e8e9711\"}\n[2026-05-07 11:36:28] local.INFO: [SyncHubspotObjects] Sync finished {\"team\":\"abae74b8-bfa8-4383-9a7f-89f4bf2bdbb4\",\"provider\":\"hubspot\",\"status\":\"completed\",\"duration_ms\":1640.35,\"usage\":24648760,\"real_usage\":65011712,\"pid\":13732} {\"correlation_id\":\"766570a3-89a2-4427-81c2-c70a7c780ee3\",\"trace_id\":\"5c938a2d-aea9-4025-a7f7-733d8e8e9711\"}\n[2026-05-07 11:36:28] local.INFO: [SyncHubspotObjects] Starting sync {\"team\":\"b2b115eb-93ce-4d1b-929c-173757df8fba\",\"usage\":24623496,\"real_usage\":65011712,\"pid\":13732} {\"correlation_id\":\"cb3b51ca-fd95-4a3f-8366-1dedc565b784\",\"trace_id\":\"5c938a2d-aea9-4025-a7f7-733d8e8e9711\"}\n[2026-05-07 11:36:28] local.WARNING: [HubSpot] Account not connected for user {\"userId\":\"33e34a7a-1c02-4f04-87ac-22c3a385e6e3\",\"account\":{\"Jiminny\\\\Models\\\\SocialAccount\":{\"id\":306,\"sociable_id\":109,\"provider_user_id\":\"11348452\",\"expires\":1701077403,\"refresh_token_expires\":null,\"provider\":\"hubspot\",\"state\":\"full-refresh\",\"auth_scope\":null,\"retry_after\":null,\"created_at\":\"2020-09-01 16:59:04\",\"updated_at\":\"2023-11-27 09:30:03\"}}} {\"correlation_id\":\"cb3b51ca-fd95-4a3f-8366-1dedc565b784\",\"trace_id\":\"5c938a2d-aea9-4025-a7f7-733d8e8e9711\"}\n[2026-05-07 11:36:28] local.INFO: [CrmOwnerResolver] Integration owner is not connected, attempting team members {\"crm_provider\":\"hubspot\",\"crm_owner\":109,\"team_id\":29} {\"correlation_id\":\"cb3b51ca-fd95-4a3f-8366-1dedc565b784\",\"trace_id\":\"5c938a2d-aea9-4025-a7f7-733d8e8e9711\"}\n[2026-05-07 11:36:28] local.INFO: [CrmOwnerResolver] No team members found with active crm connection {\"crm_provider\":\"hubspot\",\"team_id\":29} {\"correlation_id\":\"cb3b51ca-fd95-4a3f-8366-1dedc565b784\",\"trace_id\":\"5c938a2d-aea9-4025-a7f7-733d8e8e9711\"}\n[2026-05-07 11:36:28] local.INFO: [CrmOwnerResolver] No team member found with active crm connection {\"crm_provider\":\"hubspot\",\"team_id\":29} {\"correlation_id\":\"cb3b51ca-fd95-4a3f-8366-1dedc565b784\",\"trace_id\":\"5c938a2d-aea9-4025-a7f7-733d8e8e9711\"}\n[2026-05-07 11:36:28] local.INFO: [SyncHubspotObjects] Sync finished {\"team\":\"b2b115eb-93ce-4d1b-929c-173757df8fba\",\"provider\":\"hubspot\",\"status\":\"disconnected\",\"duration_ms\":64.6,\"usage\":24516544,\"real_usage\":65011712,\"pid\":13732,\"reason\":\"Your HubSpot account has become disconnected. Please login to Jiminny to reconnect.\"} {\"correlation_id\":\"cb3b51ca-fd95-4a3f-8366-1dedc565b784\",\"trace_id\":\"5c938a2d-aea9-4025-a7f7-733d8e8e9711\"}\n[2026-05-07 11:36:28] local.INFO: [SyncHubspotObjects] Starting sync {\"team\":\"c6b9d6b0-b48d-4832-a68c-a57d60651888\",\"usage\":24554912,\"real_usage\":65011712,\"pid\":13732} {\"correlation_id\":\"b5acfa3a-a5cb-43e0-8757-36cc6bc0da54\",\"trace_id\":\"5c938a2d-aea9-4025-a7f7-733d8e8e9711\"}\n[2026-05-07 11:36:28] local.WARNING: [HubSpot] Account not connected for user {\"userId\":\"71e3aac5-fb66-47c5-a236-2d051ae3e319\",\"account\":null} {\"correlation_id\":\"b5acfa3a-a5cb-43e0-8757-36cc6bc0da54\",\"trace_id\":\"5c938a2d-aea9-4025-a7f7-733d8e8e9711\"}\n[2026-05-07 11:36:28] local.INFO: [CrmOwnerResolver] Integration owner is not connected, attempting team members {\"crm_provider\":\"hubspot\",\"crm_owner\":256,\"team_id\":49} {\"correlation_id\":\"b5acfa3a-a5cb-43e0-8757-36cc6bc0da54\",\"trace_id\":\"5c938a2d-aea9-4025-a7f7-733d8e8e9711\"}\n[2026-05-07 11:36:28] local.INFO: [CrmOwnerResolver] No team members found with active crm connection {\"crm_provider\":\"hubspot\",\"team_id\":49} {\"correlation_id\":\"b5acfa3a-a5cb-43e0-8757-36cc6bc0da54\",\"trace_id\":\"5c938a2d-aea9-4025-a7f7-733d8e8e9711\"}\n[2026-05-07 11:36:28] local.INFO: [CrmOwnerResolver] No team member found with active crm connection {\"crm_provider\":\"hubspot\",\"team_id\":49} {\"correlation_id\":\"b5acfa3a-a5cb-43e0-8757-36cc6bc0da54\",\"trace_id\":\"5c938a2d-aea9-4025-a7f7-733d8e8e9711\"}\n[2026-05-07 11:36:28] local.INFO: [SyncHubspotObjects] Sync finished {\"team\":\"c6b9d6b0-b48d-4832-a68c-a57d60651888\",\"provider\":\"hubspot\",\"status\":\"disconnected\",\"duration_ms\":81.21,\"usage\":24510344,\"real_usage\":65011712,\"pid\":13732,\"reason\":\"Social account for HubSpot cannot be found. Please login to Jiminny to connect.\"} {\"correlation_id\":\"b5acfa3a-a5cb-43e0-8757-36cc6bc0da54\",\"trace_id\":\"5c938a2d-aea9-4025-a7f7-733d8e8e9711\"}\n[2026-05-07 11:36:29] local.INFO: [SyncHubspotObjects] Starting sync {\"team\":\"b2d49a54-b645-4637-a7ae-a86cfce6e8e4\",\"usage\":24548712,\"real_usage\":65011712,\"pid\":13732} {\"correlation_id\":\"a40d012c-b10d-4de7-9ea1-ef68239f712b\",\"trace_id\":\"5c938a2d-aea9-4025-a7f7-733d8e8e9711\"}\n[2026-05-07 11:36:29] local.WARNING: [HubSpot] Account not connected for user {\"userId\":\"2ac0447f-3c8c-4ce0-baeb-b63ddb76fa9b\",\"account\":null} {\"correlation_id\":\"a40d012c-b10d-4de7-9ea1-ef68239f712b\",\"trace_id\":\"5c938a2d-aea9-4025-a7f7-733d8e8e9711\"}\n[2026-05-07 11:36:29] local.INFO: [CrmOwnerResolver] Integration owner is not connected, attempting team members {\"crm_provider\":\"hubspot\",\"crm_owner\":130,\"team_id\":42} {\"correlation_id\":\"a40d012c-b10d-4de7-9ea1-ef68239f712b\",\"trace_id\":\"5c938a2d-aea9-4025-a7f7-733d8e8e9711\"}\n[2026-05-07 11:36:29] local.INFO: [CrmOwnerResolver] No team members found with active crm connection {\"crm_provider\":\"hubspot\",\"team_id\":42} {\"correlation_id\":\"a40d012c-b10d-4de7-9ea1-ef68239f712b\",\"trace_id\":\"5c938a2d-aea9-4025-a7f7-733d8e8e9711\"}\n[2026-05-07 11:36:29] local.INFO: [CrmOwnerResolver] No team member found with active crm connection {\"crm_provider\":\"hubspot\",\"team_id\":42} {\"correlation_id\":\"a40d012c-b10d-4de7-9ea1-ef68239f712b\",\"trace_id\":\"5c938a2d-aea9-4025-a7f7-733d8e8e9711\"}\n[2026-05-07 11:36:29] local.INFO: [SyncHubspotObjects] Sync finished {\"team\":\"b2d49a54-b645-4637-a7ae-a86cfce6e8e4\",\"provider\":\"hubspot\",\"status\":\"disconnected\",\"duration_ms\":77.05,\"usage\":24483760,\"real_usage\":65011712,\"pid\":13732,\"reason\":\"Social account for HubSpot cannot be found. Please login to Jiminny to connect.\"} {\"correlation_id\":\"a40d012c-b10d-4de7-9ea1-ef68239f712b\",\"trace_id\":\"5c938a2d-aea9-4025-a7f7-733d8e8e9711\"}\n[2026-05-07 11:36:31] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage before starting command {\"command\":\"activity:notify-not-logged\",\"memoryBeforeCommandInMb\":60.0,\"memoryPeakBeforeCommandInMb\":99.727} {\"correlation_id\":\"cd860d87-3b37-4cc4-b4bc-7788e90ccadc\",\"trace_id\":\"ecfea9bc-0fe5-4224-ba87-0b84ebb94d35\"}\n[2026-05-07 11:36:31] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage for command {\"command\":\"activity:notify-not-logged\",\"memoryBeforeCommandInMb\":60.0,\"memoryAfterCommandInMB\":62.0,\"memoryPeakBeforeCommandInMb\":99.727,\"memoryPeakAfterCommandInMB\":99.727} {\"correlation_id\":\"cd860d87-3b37-4cc4-b4bc-7788e90ccadc\",\"trace_id\":\"ecfea9bc-0fe5-4224-ba87-0b84ebb94d35\"}\n[2026-05-07 11:36:34] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage before starting command {\"command\":\"mailbox:sync\",\"memoryBeforeCommandInMb\":60.0,\"memoryPeakBeforeCommandInMb\":99.727} {\"correlation_id\":\"a179c5a9-e51e-4929-b0a8-717068ede438\",\"trace_id\":\"e12b0454-0027-4fce-8a8f-8572b5c2a51f\"}\n[2026-05-07 11:36:34] local.INFO: [EmailSchedule] STARTING Inbox Sync {\"host\":\"docker_lamp_1\"} {\"correlation_id\":\"a179c5a9-e51e-4929-b0a8-717068ede438\",\"trace_id\":\"e12b0454-0027-4fce-8a8f-8572b5c2a51f\"}\n[2026-05-07 11:36:34] local.INFO: [EmailSchedule] FINISHED Inbox Sync {\"host\":\"docker_lamp_1\",\"events\":2} {\"correlation_id\":\"a179c5a9-e51e-4929-b0a8-717068ede438\",\"trace_id\":\"e12b0454-0027-4fce-8a8f-8572b5c2a51f\"}\n[2026-05-07 11:36:34] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage for command {\"command\":\"mailbox:sync\",\"memoryBeforeCommandInMb\":60.0,\"memoryAfterCommandInMB\":62.0,\"memoryPeakBeforeCommandInMb\":99.727,\"memoryPeakAfterCommandInMB\":99.727} {\"correlation_id\":\"a179c5a9-e51e-4929-b0a8-717068ede438\",\"trace_id\":\"e12b0454-0027-4fce-8a8f-8572b5c2a51f\"}","depth":4,"bounds":{"left":0.52859044,"top":0.0726257,"width":0.45977393,"height":0.9273743},"on_screen":true,"value":"[2026-05-07 11:36:26] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage before starting command {\"command\":\"crm:sync-hubspot-objects\",\"memoryBeforeCommandInMb\":60.0,\"memoryPeakBeforeCommandInMb\":99.727} {\"correlation_id\":\"f71ac7c4-e9d4-46ad-be82-47e68a8f1199\",\"trace_id\":\"5c938a2d-aea9-4025-a7f7-733d8e8e9711\"}\n[2026-05-07 11:36:26] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage for command {\"command\":\"crm:sync-hubspot-objects\",\"memoryBeforeCommandInMb\":60.0,\"memoryAfterCommandInMB\":62.0,\"memoryPeakBeforeCommandInMb\":99.727,\"memoryPeakAfterCommandInMB\":99.727} {\"correlation_id\":\"f71ac7c4-e9d4-46ad-be82-47e68a8f1199\",\"trace_id\":\"5c938a2d-aea9-4025-a7f7-733d8e8e9711\"}\n[2026-05-07 11:36:27] local.INFO: [SyncHubspotObjects] Starting sync {\"team\":\"abae74b8-bfa8-4383-9a7f-89f4bf2bdbb4\",\"usage\":24412984,\"real_usage\":65011712,\"pid\":13732} {\"correlation_id\":\"766570a3-89a2-4427-81c2-c70a7c780ee3\",\"trace_id\":\"5c938a2d-aea9-4025-a7f7-733d8e8e9711\"}\n[2026-05-07 11:36:27] local.INFO: [SocialAccountService] Fetching token {\"socialAccountId\":1499,\"provider\":\"hubspot\"} {\"correlation_id\":\"766570a3-89a2-4427-81c2-c70a7c780ee3\",\"trace_id\":\"5c938a2d-aea9-4025-a7f7-733d8e8e9711\"}\n[2026-05-07 11:36:27] local.INFO: [SocialAccountService] Token needs refreshing {\"socialAccountId\":1499,\"provider\":\"hubspot\"} {\"correlation_id\":\"766570a3-89a2-4427-81c2-c70a7c780ee3\",\"trace_id\":\"5c938a2d-aea9-4025-a7f7-733d8e8e9711\"}\n[2026-05-07 11:36:27] local.INFO: [EncryptedTokenManager] Generating access token. {\"mode\":\"legacy\"} {\"correlation_id\":\"766570a3-89a2-4427-81c2-c70a7c780ee3\",\"trace_id\":\"5c938a2d-aea9-4025-a7f7-733d8e8e9711\"}\n[2026-05-07 11:36:27] local.INFO: [SocialAccountService] Refreshing token from provider {\"socialAccountId\":1499,\"provider\":\"hubspot\",\"refreshToken\":\"96f94c623a404e02ebdbf07f1b75707bb6cdbf848cbf45d418baf608c41a8d86\",\"state\":\"connected\"} {\"correlation_id\":\"766570a3-89a2-4427-81c2-c70a7c780ee3\",\"trace_id\":\"5c938a2d-aea9-4025-a7f7-733d8e8e9711\"}\n[2026-05-07 11:36:27] local.INFO: [SocialAccountObserver] Saving model {\"correlation_id\":\"766570a3-89a2-4427-81c2-c70a7c780ee3\",\"trace_id\":\"5c938a2d-aea9-4025-a7f7-733d8e8e9711\"}\n[2026-05-07 11:36:27] local.INFO: [SocialAccountObserver] Access token was modified, encrypting {\"correlation_id\":\"766570a3-89a2-4427-81c2-c70a7c780ee3\",\"trace_id\":\"5c938a2d-aea9-4025-a7f7-733d8e8e9711\"}\n[2026-05-07 11:36:28] local.INFO: [SocialAccountService] Token refreshed {\"socialAccountId\":1499,\"provider\":\"hubspot\",\"state\":\"connected\"} {\"correlation_id\":\"766570a3-89a2-4427-81c2-c70a7c780ee3\",\"trace_id\":\"5c938a2d-aea9-4025-a7f7-733d8e8e9711\"}\n[2026-05-07 11:36:28] local.INFO: [CrmOwnerResolver] Integration owner matched as CRM Owner {\"crm_provider\":\"hubspot\",\"crm_owner\":148,\"team_id\":2} {\"correlation_id\":\"766570a3-89a2-4427-81c2-c70a7c780ee3\",\"trace_id\":\"5c938a2d-aea9-4025-a7f7-733d8e8e9711\"}\n[2026-05-07 11:36:28] local.INFO: [HubSpot] Syncing opportunities using strategy: lastModified {\"team\":2} {\"correlation_id\":\"766570a3-89a2-4427-81c2-c70a7c780ee3\",\"trace_id\":\"5c938a2d-aea9-4025-a7f7-733d8e8e9711\"}\n[2026-05-07 11:36:28] local.INFO: [Hubspot] Pagination completed {\"team_id\":2,\"endpoint\":\"https://api.hubapi.com/crm/v3/objects/deals/search\",\"total_requests\":1,\"total_records_fetched\":0,\"total_elapsed_seconds\":0.65,\"average_seconds_per_request\":0.65} {\"correlation_id\":\"766570a3-89a2-4427-81c2-c70a7c780ee3\",\"trace_id\":\"5c938a2d-aea9-4025-a7f7-733d8e8e9711\"}\n[2026-05-07 11:36:28] local.INFO: [HubSpot] Synced opportunities {\"team\":2,\"strategies\":\"lastModified\",\"sync_count\":0,\"total\":0,\"last_synced_id\":null,\"duration_ms\":682.84} {\"correlation_id\":\"766570a3-89a2-4427-81c2-c70a7c780ee3\",\"trace_id\":\"5c938a2d-aea9-4025-a7f7-733d8e8e9711\"}\n[2026-05-07 11:36:28] local.INFO: [SyncHubspotObjects] Sync finished {\"team\":\"abae74b8-bfa8-4383-9a7f-89f4bf2bdbb4\",\"provider\":\"hubspot\",\"status\":\"completed\",\"duration_ms\":1640.35,\"usage\":24648760,\"real_usage\":65011712,\"pid\":13732} {\"correlation_id\":\"766570a3-89a2-4427-81c2-c70a7c780ee3\",\"trace_id\":\"5c938a2d-aea9-4025-a7f7-733d8e8e9711\"}\n[2026-05-07 11:36:28] local.INFO: [SyncHubspotObjects] Starting sync {\"team\":\"b2b115eb-93ce-4d1b-929c-173757df8fba\",\"usage\":24623496,\"real_usage\":65011712,\"pid\":13732} {\"correlation_id\":\"cb3b51ca-fd95-4a3f-8366-1dedc565b784\",\"trace_id\":\"5c938a2d-aea9-4025-a7f7-733d8e8e9711\"}\n[2026-05-07 11:36:28] local.WARNING: [HubSpot] Account not connected for user {\"userId\":\"33e34a7a-1c02-4f04-87ac-22c3a385e6e3\",\"account\":{\"Jiminny\\\\Models\\\\SocialAccount\":{\"id\":306,\"sociable_id\":109,\"provider_user_id\":\"11348452\",\"expires\":1701077403,\"refresh_token_expires\":null,\"provider\":\"hubspot\",\"state\":\"full-refresh\",\"auth_scope\":null,\"retry_after\":null,\"created_at\":\"2020-09-01 16:59:04\",\"updated_at\":\"2023-11-27 09:30:03\"}}} {\"correlation_id\":\"cb3b51ca-fd95-4a3f-8366-1dedc565b784\",\"trace_id\":\"5c938a2d-aea9-4025-a7f7-733d8e8e9711\"}\n[2026-05-07 11:36:28] local.INFO: [CrmOwnerResolver] Integration owner is not connected, attempting team members {\"crm_provider\":\"hubspot\",\"crm_owner\":109,\"team_id\":29} {\"correlation_id\":\"cb3b51ca-fd95-4a3f-8366-1dedc565b784\",\"trace_id\":\"5c938a2d-aea9-4025-a7f7-733d8e8e9711\"}\n[2026-05-07 11:36:28] local.INFO: [CrmOwnerResolver] No team members found with active crm connection {\"crm_provider\":\"hubspot\",\"team_id\":29} {\"correlation_id\":\"cb3b51ca-fd95-4a3f-8366-1dedc565b784\",\"trace_id\":\"5c938a2d-aea9-4025-a7f7-733d8e8e9711\"}\n[2026-05-07 11:36:28] local.INFO: [CrmOwnerResolver] No team member found with active crm connection {\"crm_provider\":\"hubspot\",\"team_id\":29} {\"correlation_id\":\"cb3b51ca-fd95-4a3f-8366-1dedc565b784\",\"trace_id\":\"5c938a2d-aea9-4025-a7f7-733d8e8e9711\"}\n[2026-05-07 11:36:28] local.INFO: [SyncHubspotObjects] Sync finished {\"team\":\"b2b115eb-93ce-4d1b-929c-173757df8fba\",\"provider\":\"hubspot\",\"status\":\"disconnected\",\"duration_ms\":64.6,\"usage\":24516544,\"real_usage\":65011712,\"pid\":13732,\"reason\":\"Your HubSpot account has become disconnected. Please login to Jiminny to reconnect.\"} {\"correlation_id\":\"cb3b51ca-fd95-4a3f-8366-1dedc565b784\",\"trace_id\":\"5c938a2d-aea9-4025-a7f7-733d8e8e9711\"}\n[2026-05-07 11:36:28] local.INFO: [SyncHubspotObjects] Starting sync {\"team\":\"c6b9d6b0-b48d-4832-a68c-a57d60651888\",\"usage\":24554912,\"real_usage\":65011712,\"pid\":13732} {\"correlation_id\":\"b5acfa3a-a5cb-43e0-8757-36cc6bc0da54\",\"trace_id\":\"5c938a2d-aea9-4025-a7f7-733d8e8e9711\"}\n[2026-05-07 11:36:28] local.WARNING: [HubSpot] Account not connected for user {\"userId\":\"71e3aac5-fb66-47c5-a236-2d051ae3e319\",\"account\":null} {\"correlation_id\":\"b5acfa3a-a5cb-43e0-8757-36cc6bc0da54\",\"trace_id\":\"5c938a2d-aea9-4025-a7f7-733d8e8e9711\"}\n[2026-05-07 11:36:28] local.INFO: [CrmOwnerResolver] Integration owner is not connected, attempting team members {\"crm_provider\":\"hubspot\",\"crm_owner\":256,\"team_id\":49} {\"correlation_id\":\"b5acfa3a-a5cb-43e0-8757-36cc6bc0da54\",\"trace_id\":\"5c938a2d-aea9-4025-a7f7-733d8e8e9711\"}\n[2026-05-07 11:36:28] local.INFO: [CrmOwnerResolver] No team members found with active crm connection {\"crm_provider\":\"hubspot\",\"team_id\":49} {\"correlation_id\":\"b5acfa3a-a5cb-43e0-8757-36cc6bc0da54\",\"trace_id\":\"5c938a2d-aea9-4025-a7f7-733d8e8e9711\"}\n[2026-05-07 11:36:28] local.INFO: [CrmOwnerResolver] No team member found with active crm connection {\"crm_provider\":\"hubspot\",\"team_id\":49} {\"correlation_id\":\"b5acfa3a-a5cb-43e0-8757-36cc6bc0da54\",\"trace_id\":\"5c938a2d-aea9-4025-a7f7-733d8e8e9711\"}\n[2026-05-07 11:36:28] local.INFO: [SyncHubspotObjects] Sync finished {\"team\":\"c6b9d6b0-b48d-4832-a68c-a57d60651888\",\"provider\":\"hubspot\",\"status\":\"disconnected\",\"duration_ms\":81.21,\"usage\":24510344,\"real_usage\":65011712,\"pid\":13732,\"reason\":\"Social account for HubSpot cannot be found. Please login to Jiminny to connect.\"} {\"correlation_id\":\"b5acfa3a-a5cb-43e0-8757-36cc6bc0da54\",\"trace_id\":\"5c938a2d-aea9-4025-a7f7-733d8e8e9711\"}\n[2026-05-07 11:36:29] local.INFO: [SyncHubspotObjects] Starting sync {\"team\":\"b2d49a54-b645-4637-a7ae-a86cfce6e8e4\",\"usage\":24548712,\"real_usage\":65011712,\"pid\":13732} {\"correlation_id\":\"a40d012c-b10d-4de7-9ea1-ef68239f712b\",\"trace_id\":\"5c938a2d-aea9-4025-a7f7-733d8e8e9711\"}\n[2026-05-07 11:36:29] local.WARNING: [HubSpot] Account not connected for user {\"userId\":\"2ac0447f-3c8c-4ce0-baeb-b63ddb76fa9b\",\"account\":null} {\"correlation_id\":\"a40d012c-b10d-4de7-9ea1-ef68239f712b\",\"trace_id\":\"5c938a2d-aea9-4025-a7f7-733d8e8e9711\"}\n[2026-05-07 11:36:29] local.INFO: [CrmOwnerResolver] Integration owner is not connected, attempting team members {\"crm_provider\":\"hubspot\",\"crm_owner\":130,\"team_id\":42} {\"correlation_id\":\"a40d012c-b10d-4de7-9ea1-ef68239f712b\",\"trace_id\":\"5c938a2d-aea9-4025-a7f7-733d8e8e9711\"}\n[2026-05-07 11:36:29] local.INFO: [CrmOwnerResolver] No team members found with active crm connection {\"crm_provider\":\"hubspot\",\"team_id\":42} {\"correlation_id\":\"a40d012c-b10d-4de7-9ea1-ef68239f712b\",\"trace_id\":\"5c938a2d-aea9-4025-a7f7-733d8e8e9711\"}\n[2026-05-07 11:36:29] local.INFO: [CrmOwnerResolver] No team member found with active crm connection {\"crm_provider\":\"hubspot\",\"team_id\":42} {\"correlation_id\":\"a40d012c-b10d-4de7-9ea1-ef68239f712b\",\"trace_id\":\"5c938a2d-aea9-4025-a7f7-733d8e8e9711\"}\n[2026-05-07 11:36:29] local.INFO: [SyncHubspotObjects] Sync finished {\"team\":\"b2d49a54-b645-4637-a7ae-a86cfce6e8e4\",\"provider\":\"hubspot\",\"status\":\"disconnected\",\"duration_ms\":77.05,\"usage\":24483760,\"real_usage\":65011712,\"pid\":13732,\"reason\":\"Social account for HubSpot cannot be found. Please login to Jiminny to connect.\"} {\"correlation_id\":\"a40d012c-b10d-4de7-9ea1-ef68239f712b\",\"trace_id\":\"5c938a2d-aea9-4025-a7f7-733d8e8e9711\"}\n[2026-05-07 11:36:31] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage before starting command {\"command\":\"activity:notify-not-logged\",\"memoryBeforeCommandInMb\":60.0,\"memoryPeakBeforeCommandInMb\":99.727} {\"correlation_id\":\"cd860d87-3b37-4cc4-b4bc-7788e90ccadc\",\"trace_id\":\"ecfea9bc-0fe5-4224-ba87-0b84ebb94d35\"}\n[2026-05-07 11:36:31] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage for command {\"command\":\"activity:notify-not-logged\",\"memoryBeforeCommandInMb\":60.0,\"memoryAfterCommandInMB\":62.0,\"memoryPeakBeforeCommandInMb\":99.727,\"memoryPeakAfterCommandInMB\":99.727} {\"correlation_id\":\"cd860d87-3b37-4cc4-b4bc-7788e90ccadc\",\"trace_id\":\"ecfea9bc-0fe5-4224-ba87-0b84ebb94d35\"}\n[2026-05-07 11:36:34] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage before starting command {\"command\":\"mailbox:sync\",\"memoryBeforeCommandInMb\":60.0,\"memoryPeakBeforeCommandInMb\":99.727} {\"correlation_id\":\"a179c5a9-e51e-4929-b0a8-717068ede438\",\"trace_id\":\"e12b0454-0027-4fce-8a8f-8572b5c2a51f\"}\n[2026-05-07 11:36:34] local.INFO: [EmailSchedule] STARTING Inbox Sync {\"host\":\"docker_lamp_1\"} {\"correlation_id\":\"a179c5a9-e51e-4929-b0a8-717068ede438\",\"trace_id\":\"e12b0454-0027-4fce-8a8f-8572b5c2a51f\"}\n[2026-05-07 11:36:34] local.INFO: [EmailSchedule] FINISHED Inbox Sync {\"host\":\"docker_lamp_1\",\"events\":2} {\"correlation_id\":\"a179c5a9-e51e-4929-b0a8-717068ede438\",\"trace_id\":\"e12b0454-0027-4fce-8a8f-8572b5c2a51f\"}\n[2026-05-07 11:36:34] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage for command {\"command\":\"mailbox:sync\",\"memoryBeforeCommandInMb\":60.0,\"memoryAfterCommandInMB\":62.0,\"memoryPeakBeforeCommandInMb\":99.727,\"memoryPeakAfterCommandInMB\":99.727} {\"correlation_id\":\"a179c5a9-e51e-4929-b0a8-717068ede438\",\"trace_id\":\"e12b0454-0027-4fce-8a8f-8572b5c2a51f\"}","role_description":"text entry area","is_enabled":true,"is_focused":true,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Sync Changes","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.008643617,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Hide This Notification","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.008643617,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Code changed:","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.042220745,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Hide","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.008643617,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"2","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.007978723,"height":0.0},"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"60","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.010305851,"height":0.0},"on_screen":false,"role_description":"text"},{"role":"AXButton","text":"Previous Highlighted Error","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.00731383,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Next Highlighted Error","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.006981383,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXTextArea","text":"<?php\n\ndeclare(strict_types=1);\n\nnamespace Jiminny\\Services\\Crm\\Hubspot;\n\nuse HubSpot\\Client\\Crm\\Deals\\ApiException as DealApiException;\nuse HubSpot\\Client\\Crm\\Contacts\\ApiException as ContactApiException;\nuse HubSpot\\Client\\Crm\\Companies\\ApiException as CompanyApiException;\nuse HubSpot\\Client\\Crm\\Contacts\\Model\\SimplePublicObjectWithAssociations as ContactsWithAssociations;\nuse HubSpot\\Client\\Crm\\Companies\\Model\\SimplePublicObjectWithAssociations as CompaniesWithAssociations;\nuse HubSpot\\Client\\Crm\\Deals\\Model\\SimplePublicObjectWithAssociations as DealWithAssociations;\nuse HubSpot\\Client\\Crm\\Objects\\Model\\SimplePublicObjectInput;\nuse HubSpot\\Client\\Crm\\Objects\\Model\\SimplePublicObjectWithAssociations as ObjectWithAssociations;\nuse HubSpot\\Client\\Crm\\Pipelines\\Model\\Error;\nuse HubSpot\\Client\\Crm\\Pipelines\\Model\\PipelineStage;\nuse HubSpot\\Client\\Crm\\Properties\\Model\\Property;\nuse HubSpot\\Discovery\\Discovery;\nuse Jiminny\\Component\\Utility\\Service\\ProviderRateLimiter;\nuse Jiminny\\Exceptions\\CrmException;\nuse Jiminny\\Exceptions\\RateLimitException;\nuse Jiminny\\Exceptions\\SocialAccountTokenInvalidException;\nuse Jiminny\\Jobs\\Crm\\NoteObject;\nuse Jiminny\\Models\\Crm\\Field;\nuse Jiminny\\Services\\Crm\\BaseClient;\nuse Jiminny\\Services\\Crm\\Hubspot\\DTO\\Response\\Owner;\nuse Jiminny\\Services\\SocialAccountService;\nuse SevenShores\\Hubspot\\Exceptions\\BadRequest;\nuse SevenShores\\Hubspot\\Exceptions\\HubspotException;\nuse SevenShores\\Hubspot\\Factory;\nuse SevenShores\\Hubspot\\Http\\Response;\nuse Jiminny\\Services\\Crm\\Hubspot\\Pagination\\HubspotPaginationService;\nuse Throwable;\n\n/**\n * @phpstan-type CrmFieldOption array{id:string, label:string, value?:string}\n */\nclass Client extends BaseClient implements HubspotClientInterface\n{\n public const string MIN_API_VERSION = '2';\n\n public const string BASE_URL = 'https://api.hubapi.com';\n\n public const int ASSOCIATIONS_BATCH_SIZE_LIMIT = 1000;\n\n private HubspotPaginationService $paginationService;\n private HubspotTokenManager $tokenManager;\n private ProviderRateLimiter $rateLimiter;\n\n public function __construct(\n SocialAccountService $socialAccountService,\n HubspotPaginationService $paginationService,\n HubspotTokenManager $tokenManager,\n ProviderRateLimiter $rateLimiter,\n ) {\n parent::__construct($socialAccountService);\n $this->paginationService = $paginationService;\n $this->tokenManager = $tokenManager;\n $this->rateLimiter = $rateLimiter;\n\n $this->setBaseUrl(self::BASE_URL);\n $this->setVersion(self::MIN_API_VERSION);\n }\n\n /**\n * Single entry point for every HubSpot API call. Enforces the per-portal\n * rate limit configured in the rate_limits table (morphed to the current\n * Configuration) and reacts to a real 429 from HubSpot by translating it\n * into a RateLimitException carrying Retry-After.\n *\n * Wrap any outbound HubSpot call (SDK or raw HTTP) like:\n *\n * $this->executeRequest(fn () => $this->getNewInstance()->crm()->...);\n *\n * @template T\n * @param callable(): T $apiCall\n * @return T\n *\n * @throws RateLimitException\n */\n private function executeRequest(callable $apiCall)\n {\n if (! $this->rateLimiter->canMakeRequest($this->config)) {\n $retryAfter = $this->rateLimiter->requestAvailableIn($this->config);\n\n $this->log->warning('[Hubspot] Rate limit exceeded, deferring request', [\n 'team_id' => $this->config->team_id,\n 'config_id' => $this->config->getId(),\n 'retry_after' => $retryAfter,\n ]);\n\n throw new RateLimitException(\n 'Hubspot rate limit reached for configuration ' . $this->config->getId(),\n $retryAfter,\n );\n }\n\n $this->rateLimiter->incrementRequestCount($this->config);\n\n try {\n return $apiCall();\n } catch (Throwable $e) {\n if ($this->isHubspotRateLimit($e)) {\n $retryAfter = $this->parseRetryAfter($e);\n\n $this->log->warning('[Hubspot] Received 429 from API', [\n 'team_id' => $this->config->team_id,\n 'config_id' => $this->config->getId(),\n 'retry_after' => $retryAfter,\n 'reason' => $e->getMessage(),\n ]);\n\n throw new RateLimitException('Hubspot returned 429', $retryAfter, $e);\n }\n\n throw $e;\n }\n }\n\n private function isHubspotRateLimit(Throwable $e): bool\n {\n return method_exists($e, 'getCode') && (int) $e->getCode() === 429;\n }\n\n private function parseRetryAfter(Throwable $e): int\n {\n if (method_exists($e, 'getResponseHeaders')) {\n $headers = $e->getResponseHeaders() ?: [];\n $value = $headers['Retry-After'] ?? $headers['retry-after'] ?? null;\n if (is_array($value)) {\n $value = $value[0] ?? null;\n }\n if (is_numeric($value)) {\n return (int) $value;\n }\n }\n\n return 10;\n }\n\n public function getMinimumApiVersion(): string\n {\n return self::MIN_API_VERSION;\n }\n\n public function getInstance(): Factory\n {\n return new Factory([\n 'key' => $this->accessToken,\n 'oauth2' => true,\n 'base_url' => $this->baseUrl,\n ]);\n }\n\n public function getNewInstance(): Discovery\n {\n return \\HubSpot\\Factory::createWithAccessToken($this->accessToken);\n }\n\n /**\n * Secondly and daily limits for Hubspot API\n *\n * Product Tier: Free & Starter | Professional & Enterprise | API add-on (any tier)\n * Burst: 100/10 seconds | 150/10 seconds | 200/10 seconds\n * Daily: 250,000 | 500,000 | 1,000,000\n *\n * Official documentation states: The search endpoints are rate limited to five requests per second.\n * Since with 5 RPS were still hitting secondly rate limits we lowered it to 4\n */\n public function getPaginatedData(array $payload, string $type, int $offset = 0): array\n {\n $total = 0;\n $lastId = null;\n $rows = [];\n foreach ($this->getPaginatedDataGenerator($payload, $type, $offset, $total, $lastId) as $row) {\n $rows[] = $row;\n }\n\n return ['results' => $rows, 'total' => $total, 'last_record' => $lastId];\n }\n\n /**\n * @throws HubspotException\n * @throws SocialAccountTokenInvalidException\n * @throws BadRequest\n */\n public function getPaginatedDataGenerator(\n array $payload,\n string $type,\n int $offset = 0,\n int &$total = 0,\n ?string &$lastRecordId = null\n ): \\Generator {\n return $this->paginationService->getPaginatedDataGenerator(\n $this,\n $payload,\n $type,\n $offset,\n $total,\n $lastRecordId\n );\n }\n\n /**\n * @throws DealApiException\n * @throws CrmException\n */\n public function getOpportunityById(string $crmId, array $fields): array\n {\n try {\n $deal = $this->executeRequest(fn () => $this->getNewInstance()->crm()->deals()->basicApi()->getById(\n $crmId,\n implode(',', $fields),\n 'companies,contacts'\n ));\n } catch (DealApiException $e) {\n $this->log->info('[Hubspot] Failed to fetch opportunity', [\n 'crm_id' => $crmId,\n 'reason' => $e->getMessage(),\n ]);\n\n throw $e;\n }\n\n if (! $deal instanceof DealWithAssociations) {\n throw new CrmException('Deal not found');\n }\n\n return [\n 'id' => $deal->getId(),\n 'properties' => $deal->getProperties(),\n 'associations' => $deal->getAssociations(),\n ];\n }\n\n /**\n * Generic batch read method for HubSpot objects\n *\n * @param string $objectType The object type ('deals', 'companies', 'contacts')\n * @param array<string> $crmIds Array of HubSpot object IDs (max 100)\n * @param array<string> $fields Array of property names to fetch\n *\n * @return array<string, array> Array keyed by CRM ID with object data\n */\n private function batchReadObjects(string $objectType, array $crmIds, array $fields): array\n {\n if (empty($crmIds)) {\n return [];\n }\n\n $this->validateBatchSize($objectType, $crmIds);\n $this->ensureValidToken();\n\n try {\n $batchConfig = $this->createBatchConfiguration($objectType);\n $batchReadRequest = $this->prepareBatchRequest($batchConfig, $crmIds, $fields);\n $response = $batchConfig['api']->read($batchReadRequest);\n\n $this->validateApiResponse($response, $objectType);\n\n $results = $this->processApiResults($response);\n $this->logBatchResults($objectType, $crmIds, $results);\n\n return $results;\n } catch (\\Throwable $e) {\n $this->handleBatchError($e, $objectType, $crmIds);\n }\n }\n\n private function validateBatchSize(string $objectType, array $crmIds): void\n {\n if (count($crmIds) > 100) {\n throw new \\InvalidArgumentException(\"Batch size cannot exceed 100 {$objectType}\");\n }\n }\n\n private function createBatchConfiguration(string $objectType): array\n {\n $configurations = [\n 'deals' => [\n 'batchReadRequest' => new \\HubSpot\\Client\\Crm\\Deals\\Model\\BatchReadInputSimplePublicObjectId(),\n 'inputClass' => \\HubSpot\\Client\\Crm\\Deals\\Model\\SimplePublicObjectId::class,\n 'api' => $this->getNewInstance()->crm()->deals()->batchApi(),\n ],\n 'companies' => [\n 'batchReadRequest' => new \\HubSpot\\Client\\Crm\\Companies\\Model\\BatchReadInputSimplePublicObjectId(),\n 'inputClass' => \\HubSpot\\Client\\Crm\\Companies\\Model\\SimplePublicObjectId::class,\n 'api' => $this->getNewInstance()->crm()->companies()->batchApi(),\n ],\n 'contacts' => [\n 'batchReadRequest' => new \\HubSpot\\Client\\Crm\\Contacts\\Model\\BatchReadInputSimplePublicObjectId(),\n 'inputClass' => \\HubSpot\\Client\\Crm\\Contacts\\Model\\SimplePublicObjectId::class,\n 'api' => $this->getNewInstance()->crm()->contacts()->batchApi(),\n ],\n ];\n\n if (! isset($configurations[$objectType])) {\n throw new \\InvalidArgumentException(\"Unsupported object type: {$objectType}\");\n }\n\n return $configurations[$objectType];\n }\n\n private function prepareBatchRequest(array $batchConfig, array $crmIds, array $fields): object\n {\n $batchReadRequest = $batchConfig['batchReadRequest'];\n $inputClass = $batchConfig['inputClass'];\n\n $inputs = array_map(function ($crmId) use ($inputClass) {\n $input = new $inputClass();\n $input->setId($crmId);\n\n return $input;\n }, $crmIds);\n\n $batchReadRequest->setInputs($inputs);\n $batchReadRequest->setProperties($fields);\n\n return $batchReadRequest;\n }\n\n private function validateApiResponse($response, string $objectType): void\n {\n if (! $response) {\n throw new CrmException(\"HubSpot API returned null response for {$objectType} batch read\");\n }\n }\n\n private function processApiResults($response): array\n {\n $results = [];\n $responseResults = $response->getResults();\n\n if ($responseResults) {\n foreach ($responseResults as $object) {\n if ($object && $object->getId()) {\n $results[$object->getId()] = [\n 'id' => $object->getId(),\n 'properties' => $object->getProperties() ?: [],\n ];\n }\n }\n }\n\n return $results;\n }\n\n private function logBatchResults(string $objectType, array $crmIds, array $results): void\n {\n $this->log->info(\"[HubSpot] Batch fetched {$objectType}\", [\n 'requested_count' => count($crmIds),\n 'returned_count' => count($results),\n 'crm_ids' => $crmIds,\n ]);\n }\n\n private function handleBatchError(\\Throwable $e, string $objectType, array $crmIds): void\n {\n $errorMessage = $e->getMessage() ?: 'Unknown error';\n $errorTrace = $e->getTraceAsString() ?: 'No trace available';\n\n $this->log->error(\"[HubSpot] Failed to batch fetch {$objectType}\", [\n 'crm_ids' => $crmIds,\n 'error' => $errorMessage,\n 'trace' => $errorTrace,\n ]);\n\n throw new CrmException(\"Failed to batch fetch {$objectType}: \" . $errorMessage);\n }\n\n /**\n * Batch read multiple opportunities by their CRM IDs\n *\n * @param array<string> $crmIds Array of HubSpot deal IDs (max 100)\n * @param array<string> $fields Array of property names to fetch\n *\n * @return array<string, array> Array keyed by CRM ID with opportunity data\n */\n public function getOpportunitiesByIds(array $crmIds, array $fields): array\n {\n return $this->batchReadObjects('deals', $crmIds, $fields);\n }\n\n /**\n * Batch read multiple companies by their CRM IDs\n *\n * @param array<string> $crmIds Array of HubSpot company IDs (max 100)\n * @param array<string> $fields Array of property names to fetch\n *\n * @return array<string, array> Array keyed by CRM ID with company data\n */\n public function getCompaniesByIds(array $crmIds, array $fields): array\n {\n return $this->batchReadObjects('companies', $crmIds, $fields);\n }\n\n /**\n * Batch read multiple contacts by their CRM IDs\n *\n * @param array<string> $crmIds Array of HubSpot contact IDs (max 100)\n * @param array<string> $fields Array of property names to fetch\n *\n * @return array<string, array> Array keyed by CRM ID with contact data\n */\n public function getContactsByIds(array $crmIds, array $fields): array\n {\n return $this->batchReadObjects('contacts', $crmIds, $fields);\n }\n\n /**\n * @throws CompanyApiException\n * @throws CrmException\n */\n public function getAccountById(string $crmId, array $fields): array\n {\n try {\n $company = $this->getNewInstance()->crm()->companies()->basicApi()->getById(\n $crmId,\n implode(',', $fields),\n );\n } catch (CompanyApiException $e) {\n $this->log->info('[Hubspot] Failed to fetch account', [\n 'crm_id' => $crmId,\n 'reason' => $e->getMessage(),\n ]);\n\n throw $e;\n }\n\n if (! $company instanceof CompaniesWithAssociations) {\n throw new CrmException('Account not found');\n }\n\n return [\n 'id' => $company->getId(),\n 'properties' => $company->getProperties(),\n ];\n }\n\n /**\n * @throws ContactApiException\n * @throws CrmException\n */\n public function getContactById(string $crmId, array $fields): array\n {\n try {\n $contact = $this->getNewInstance()->crm()->contacts()->basicApi()->getById(\n $crmId,\n implode(',', $fields)\n );\n } catch (ContactApiException $e) {\n $this->log->info('[Hubspot] Failed to fetch contact', [\n 'crm_id' => $crmId,\n 'reason' => $e->getMessage(),\n ]);\n\n throw $e;\n }\n\n if (! $contact instanceof ContactsWithAssociations) {\n throw new CrmException('Contact not found');\n }\n\n return [\n 'id' => $contact->getId(),\n 'properties' => $contact->getProperties(),\n ];\n }\n\n /**\n * This is email search request that Hubspot offers as GET (more generous quota)\n */\n public function getContactByEmail(string $email, array $fields = []): array\n {\n try {\n $contact = $this->getNewInstance()->crm()->contacts()->basicApi()->getById(\n $email,\n implode(',', $fields),\n null,\n false,\n 'email'\n );\n\n return [\n 'id' => $contact->getId(),\n 'properties' => $contact->getProperties(),\n ];\n } catch (ContactApiException $e) {\n $this->log->info('[Hubspot] Failed to fetch contact', [\n 'email' => $email,\n 'reason' => $e->getMessage(),\n ]);\n\n return [];\n }\n }\n\n /**\n * @throws CrmException\n */\n public function fetchProperty(string $objectType, string $propertyId): Property\n {\n $result = $this->getNewInstance()->crm()->properties()->coreApi()->getByName($objectType, $propertyId);\n\n if (! $result instanceof Property) {\n $this->log->error('[Hubspot] Failed to fetch property', [\n 'object_type' => $objectType,\n 'property_id' => $propertyId,\n 'reason' => $result->getMessage(),\n ]);\n\n throw new CrmException('Failed to fetch property');\n }\n\n return $result;\n }\n\n /**\n * @return array<CrmFieldOption>\n */\n public function fetchPropertyOptions(string $objectType, string $propertyId): array\n {\n /** @var array<CrmFieldOption> */\n return $this->fetchProperty($objectType, $propertyId)->getOptions();\n }\n\n /**\n * @return array<array{id:string, label:string, deleted:bool}>\n */\n public function fetchCallDispositions(): array\n {\n /** @var Response $response */\n $response = $this->getInstance()->engagements()->getCallDispositions();\n\n /**\n * @var array<array{\n * id:string,\n * label:string,\n * deleted: bool\n * }>\n */\n return $response->toArray();\n }\n\n /**\n * @return array<CrmFieldOption>\n */\n public function fetchOpportunityPipelineStages(): array\n {\n $stages = [];\n $apiResponse = $this->getNewInstance()->crm()->pipelines()->pipelinesApi()->getAll('deals');\n\n if ($apiResponse instanceof Error) {\n $this->log->error('[Hubspot] Failed to fetch opportunity pipelines', [\n 'reason' => $apiResponse->getMessage(),\n ]);\n\n return [];\n }\n\n foreach ($apiResponse->getResults() as $pipeline) {\n $pipelineStages = array_map(\n static function (PipelineStage $stage) {\n return [\n 'id' => $stage->getId(),\n 'label' => $stage->getLabel(),\n ];\n },\n $pipeline->getStages()\n );\n\n $stages = array_merge($stages, $pipelineStages);\n }\n\n return $stages;\n }\n\n public function fetchOpportunityPipelines(): array\n {\n $pipelines = [];\n\n try {\n $apiResponse = $this->makeRequest('/crm/v3/pipelines/deals');\n } catch (\\Exception $e) {\n $this->log->info('[Hubspot] Failed to fetch opportunity pipelines', [\n 'reason' => $e->getMessage(),\n ]);\n\n return [];\n }\n\n $response = $apiResponse->toArray();\n\n foreach ($response['results'] as $pipeline) {\n $pipelines[] = [\n 'id' => $pipeline['id'],\n 'label' => $pipeline['label'],\n ];\n }\n\n return $pipelines;\n }\n\n /**\n * @return array<CrmFieldOption>\n */\n public function fetchMeetingOutcomeFieldOptions(Field $field): array\n {\n return $field->getCrmProviderId() === 'meetingOutcome'\n ? $this->fetchMeetingOutcomeTypes()\n : $this->fetchCallActivityTypes();\n }\n\n public function fetchMeetingOutcomeTypes(): array\n {\n return $this->extractMeetingTypeOptions(\n 'https://api.hubapi.com/crm/v3/properties/meeting/hs_meeting_outcome'\n );\n }\n\n public function fetchCallActivityTypes(): array\n {\n return $this->extractMeetingTypeOptions(\n 'https://api.hubapi.com/crm/v3/properties/call/hs_activity_type'\n );\n }\n\n private function extractMeetingTypeOptions(string $endpoint): array\n {\n /** @var Response $response */\n $response = $this->getInstance()\n ->getClient()\n ->request('GET', $endpoint);\n\n /**\n * @var array<array{\n * value: string,\n * label: string,\n * displayOrder: int\n * }> $optionData\n */\n $optionData = $response->toArray()['options'] ?? [];\n\n $options = [];\n foreach ($optionData as $item) {\n $options[] = [\n 'id' => $item['value'],\n 'value' => $item['value'],\n 'label' => $item['label'],\n 'display_order' => $item['displayOrder'],\n ];\n }\n\n return $options;\n }\n\n /**\n * @return array<CrmFieldOption>\n */\n public function fetchDispositionFieldOptions(): array\n {\n $options = [];\n\n $dispositions = $this->fetchCallDispositions();\n\n foreach ($dispositions as $disposition) {\n if ($disposition['deleted'] !== false) {\n continue;\n }\n\n $option['value'] = $disposition['id'];\n $option['id'] = $disposition['id'];\n $option['label'] = $disposition['label'];\n\n $options[] = $option;\n }\n\n return $options;\n }\n\n /**\n * @return array<CrmFieldOption>\n */\n public function fetchOpportunityFieldOptions(Field $field): array\n {\n if ($field->isStageField()) {\n return $this->fetchOpportunityPipelineStages();\n }\n\n if ($field->isPipelineField()) {\n return $this->fetchOpportunityPipelines();\n }\n\n return $this->fetchPropertyOptions('deals', $field->getCrmProviderId());\n }\n\n /**\n * @throws BadRequest\n * @throws HubspotException\n */\n public function makeRequest(string $endpoint, $method = 'GET', $payload = [], ?string $queryString = null)\n {\n $endpoint = self::BASE_URL . $endpoint;\n\n if ($method === 'GET') {\n $response = $this->getInstance()->getClient()?->request(\n method: $method,\n endpoint: $endpoint,\n query_string: $queryString\n );\n } else {\n $response = $this->getInstance()->getClient()->request($method, $endpoint, [\n 'json' => ($payload),\n ]);\n }\n\n $max = $response->getHeaderLine('X-HubSpot-RateLimit-Max'); // \"110\"\n $remaining = $response->getHeaderLine('X-HubSpot-RateLimit-Remaining'); // \"109\"\n $interval = $response->getHeaderLine('X-HubSpot-RateLimit-Interval-Milliseconds'); // \"10000\"\n $body = json_decode((string) $response->getBody(), true);\n\n \\Illuminate\\Support\\Facades\\Log::channel('custom_channel')->info('$max ' . PHP_EOL . print_r($max, true));\n \\Illuminate\\Support\\Facades\\Log::channel('custom_channel')->info('$remaining ' . PHP_EOL . print_r($remaining, true));\n \\Illuminate\\Support\\Facades\\Log::channel('custom_channel')->info('$interval ' . PHP_EOL . print_r($interval, true));\n \\Illuminate\\Support\\Facades\\Log::channel('custom_channel')->info('$body ' . PHP_EOL . print_r($body, true));\n\n return $response;\n }\n\n /**\n * @throws BadRequest\n * @throws HubspotException\n */\n public function createMeeting(array $payload): Response\n {\n $endpoint = '/crm/v3/objects/meetings';\n\n return $this->makeRequest($endpoint, 'POST', $payload);\n }\n\n /**\n * @throws BadRequest\n * @throws HubspotException\n */\n public function updateMeeting(string $meetingId, array $payload): Response\n {\n $endpoint = '/crm/v3/objects/meetings/' . $meetingId;\n\n return $this->makeRequest($endpoint, 'PATCH', $payload);\n }\n\n /**\n * @throws \\Exception\n */\n public function createNote(\n string $body,\n string $ownerId,\n int $timestamp,\n string $objectId,\n NoteObject $noteObject\n ): ?string {\n try {\n $noteInput = new SimplePublicObjectInput([\n 'properties' => [\n 'hs_note_body' => $body,\n 'hubspot_owner_id' => $ownerId,\n 'hs_timestamp' => $timestamp,\n ],\n ]);\n\n // Create note\n $note = $this->getNewInstance()->crm()->objects()->basicApi()->create('note', $noteInput);\n\n $this->getNewInstance()->crm()->objects()->associationsApi()->create(\n 'note',\n $note->getId(),\n $this->getNoteObject($noteObject),\n $objectId,\n $this->getNoteAssociationType($noteObject),\n );\n\n return $note->getId();\n } catch (\\Exception $e) {\n $this->log->error('[Hubspot] Failed to create note', [\n 'objectId' => $objectId,\n 'noteObject' => $noteObject->getObjectType(),\n 'reason' => $e->getMessage(),\n ]);\n\n \\Sentry::captureException($e);\n }\n\n return null;\n }\n\n public function updateEngagement(string $objectId, array $engagement, array $metadata): void\n {\n $this->getInstance()->engagements()->update($objectId, $engagement, $metadata);\n }\n\n public function getEngagementData(string $engagementId): array\n {\n $engagement = $this->getInstance()->engagements()->get($engagementId);\n\n return $engagement->toArray();\n }\n\n public function createEngagement(array $engagement, array $associations, array $metadata): Response\n {\n return $this->getInstance()\n ->engagements()\n ->create($engagement, $associations, $metadata);\n }\n\n public function isUnauthorizedException(\\Exception $e): bool\n {\n // Check for specific HubSpot API exception types first\n if ($e instanceof BadRequest) {\n // BadRequest can contain 401 status codes\n return $e->getCode() === 401;\n }\n\n // Check for HTTP client exceptions with status codes\n if ($e instanceof \\GuzzleHttp\\Exception\\RequestException && $e->hasResponse()) {\n $response = $e->getResponse();\n if ($response !== null) {\n return $response->getStatusCode() === 401;\n }\n }\n\n // Check for Guzzle HTTP exceptions\n if ($e instanceof \\GuzzleHttp\\Exception\\ClientException) {\n return $e->getCode() === 401;\n }\n\n // Fallback to string matching as last resort, but be more specific\n $message = strtolower($e->getMessage());\n\n return str_contains($message, '401 unauthorized') ||\n str_contains($message, 'http 401') ||\n str_contains($message, 'status code 401') ||\n (preg_match('/\\b401\\b/', $message) && str_contains($message, 'unauthorized'));\n }\n\n /**\n * Validates and refreshes the access token if needed before API requests.\n * This ensures long-running processes don't fail due to token expiration.\n *\n * @throws SocialAccountTokenInvalidException\n */\n public function ensureValidToken(): void\n {\n if ($this->oauthAccount === null) {\n return;\n }\n\n $newToken = $this->tokenManager->ensureValidToken($this->oauthAccount);\n if ($newToken !== null) {\n $this->accessToken = $newToken;\n }\n }\n\n public function getConfig()\n {\n return $this->config;\n }\n\n // returns only active (archived=false)\n public function getOwners(): array\n {\n return $this->getNewInstance()->crm()->owners()->getAll();\n }\n\n /**\n * @param bool $archived\n *\n * @return array<Owner>|[]\n */\n public function getOwnersArchived(bool $archived = true): array\n {\n $endpoint = '/crm/v3/owners';\n $queryParams = [\n 'archived' => $archived ? 'true' : 'false',\n ];\n $queryString = http_build_query($queryParams);\n\n $owners = [];\n\n try {\n $response = $this->makeRequest(endpoint: $endpoint, queryString: $queryString);\n $responseData = $response?->toArray();\n\n foreach ($responseData['results'] as $result) {\n try {\n $owners[] = Owner::create($result);\n } catch (Throwable $e) {\n $this->log->error('[HubSpot] Failed to process owner data', [\n 'result' => $result,\n 'error' => $e->getMessage(),\n ]);\n\n continue;\n }\n }\n } catch (Throwable $e) {\n $this->log->error('HubSpot] Failed to fetch owners', [\n 'archived' => $archived,\n 'error' => $e->getMessage(),\n ]);\n\n return [];\n }\n\n return $owners;\n }\n\n public function getMeeting(string $engagementId): ObjectWithAssociations\n {\n return $this->getNewInstance()->crm()->objects()->basicApi()\n ->getById('meeting', $engagementId, null, 'contact,company,deal');\n }\n\n public function deleteEngagement(string $engagementId): void\n {\n $this->getInstance()->engagements()->delete((int) $engagementId);\n }\n\n public function getAssociationsData(array $ids, string $fromObject, string $toObject): array\n {\n $associationData = [];\n $idChunks = array_chunk($ids, self::ASSOCIATIONS_BATCH_SIZE_LIMIT);\n\n foreach ($idChunks as $idChunk) {\n try {\n $batchInput = new \\HubSpot\\Client\\Crm\\Associations\\Model\\BatchInputPublicObjectId();\n $batchInput->setInputs(array_map(function ($id) {\n $publicObjectId = new \\HubSpot\\Client\\Crm\\Associations\\Model\\PublicObjectId();\n $publicObjectId->setId($id);\n\n return $publicObjectId;\n }, $idChunk));\n\n $associatedObjectsData = $this\n ->getNewInstance()\n ->crm()\n ->associations()\n ->batchApi()\n ->read($fromObject, $toObject, $batchInput);\n\n if ($associatedObjectsData instanceof \\HubSpot\\Client\\Crm\\Associations\\Model\\BatchResponsePublicAssociationMulti) {\n foreach ($associatedObjectsData->getResults() as $association) {\n $from = $association->getFrom()->getId();\n $toAssociations = $association->getTo();\n\n if (! empty($toAssociations)) {\n $associationData[$from] = array_map(function ($item) {\n return $item->getId();\n }, $toAssociations);\n }\n }\n }\n } catch (\\Exception $e) {\n $this->log->error('[Hubspot] Failed to fetch associations', [\n 'from_object' => $fromObject,\n 'to_object' => $toObject,\n 'reason' => $e->getMessage(),\n ]);\n }\n }\n\n return $associationData;\n }\n\n /**\n * @throws \\Exception\n */\n private function getNoteAssociationType(NoteObject $noteObject): string\n {\n return match($noteObject) {\n NoteObject::Opportunity => 'note_to_deal',\n NoteObject::Lead, NoteObject::Contact => 'note_to_contact', // or 'note_to_lead' if your portal supports it\n NoteObject::Account => 'note_to_company',\n NoteObject::Call, NoteObject::Event => throw new \\Exception('Not supported'),\n };\n }\n\n /**\n * @throws \\Exception\n */\n private function getNoteObject(NoteObject $noteObject): string\n {\n return match($noteObject) {\n NoteObject::Opportunity => 'deal',\n NoteObject::Lead, NoteObject::Contact => 'contact',\n NoteObject::Account => 'company',\n NoteObject::Call, NoteObject::Event => throw new \\Exception('Not supported'),\n };\n }\n\n public function addAssociations(string $objectType, string $associationType, array $payload): Response\n {\n $endpoint = \"/crm/v4/associations/$objectType/$associationType/batch/create\";\n\n return $this->makeRequest($endpoint, 'POST', $payload);\n }\n\n public function removeAssociations(string $objectType, string $associationType, array $payload): Response\n {\n $endpoint = \"/crm/v4/associations/$objectType/$associationType/batch/archive\";\n\n return $this->makeRequest($endpoint, 'POST', $payload);\n }\n}","depth":4,"on_screen":true,"value":"<?php\n\ndeclare(strict_types=1);\n\nnamespace Jiminny\\Services\\Crm\\Hubspot;\n\nuse HubSpot\\Client\\Crm\\Deals\\ApiException as DealApiException;\nuse HubSpot\\Client\\Crm\\Contacts\\ApiException as ContactApiException;\nuse HubSpot\\Client\\Crm\\Companies\\ApiException as CompanyApiException;\nuse HubSpot\\Client\\Crm\\Contacts\\Model\\SimplePublicObjectWithAssociations as ContactsWithAssociations;\nuse HubSpot\\Client\\Crm\\Companies\\Model\\SimplePublicObjectWithAssociations as CompaniesWithAssociations;\nuse HubSpot\\Client\\Crm\\Deals\\Model\\SimplePublicObjectWithAssociations as DealWithAssociations;\nuse HubSpot\\Client\\Crm\\Objects\\Model\\SimplePublicObjectInput;\nuse HubSpot\\Client\\Crm\\Objects\\Model\\SimplePublicObjectWithAssociations as ObjectWithAssociations;\nuse HubSpot\\Client\\Crm\\Pipelines\\Model\\Error;\nuse HubSpot\\Client\\Crm\\Pipelines\\Model\\PipelineStage;\nuse HubSpot\\Client\\Crm\\Properties\\Model\\Property;\nuse HubSpot\\Discovery\\Discovery;\nuse Jiminny\\Component\\Utility\\Service\\ProviderRateLimiter;\nuse Jiminny\\Exceptions\\CrmException;\nuse Jiminny\\Exceptions\\RateLimitException;\nuse Jiminny\\Exceptions\\SocialAccountTokenInvalidException;\nuse Jiminny\\Jobs\\Crm\\NoteObject;\nuse Jiminny\\Models\\Crm\\Field;\nuse Jiminny\\Services\\Crm\\BaseClient;\nuse Jiminny\\Services\\Crm\\Hubspot\\DTO\\Response\\Owner;\nuse Jiminny\\Services\\SocialAccountService;\nuse SevenShores\\Hubspot\\Exceptions\\BadRequest;\nuse SevenShores\\Hubspot\\Exceptions\\HubspotException;\nuse SevenShores\\Hubspot\\Factory;\nuse SevenShores\\Hubspot\\Http\\Response;\nuse Jiminny\\Services\\Crm\\Hubspot\\Pagination\\HubspotPaginationService;\nuse Throwable;\n\n/**\n * @phpstan-type CrmFieldOption array{id:string, label:string, value?:string}\n */\nclass Client extends BaseClient implements HubspotClientInterface\n{\n public const string MIN_API_VERSION = '2';\n\n public const string BASE_URL = 'https://api.hubapi.com';\n\n public const int ASSOCIATIONS_BATCH_SIZE_LIMIT = 1000;\n\n private HubspotPaginationService $paginationService;\n private HubspotTokenManager $tokenManager;\n private ProviderRateLimiter $rateLimiter;\n\n public function __construct(\n SocialAccountService $socialAccountService,\n HubspotPaginationService $paginationService,\n HubspotTokenManager $tokenManager,\n ProviderRateLimiter $rateLimiter,\n ) {\n parent::__construct($socialAccountService);\n $this->paginationService = $paginationService;\n $this->tokenManager = $tokenManager;\n $this->rateLimiter = $rateLimiter;\n\n $this->setBaseUrl(self::BASE_URL);\n $this->setVersion(self::MIN_API_VERSION);\n }\n\n /**\n * Single entry point for every HubSpot API call. Enforces the per-portal\n * rate limit configured in the rate_limits table (morphed to the current\n * Configuration) and reacts to a real 429 from HubSpot by translating it\n * into a RateLimitException carrying Retry-After.\n *\n * Wrap any outbound HubSpot call (SDK or raw HTTP) like:\n *\n * $this->executeRequest(fn () => $this->getNewInstance()->crm()->...);\n *\n * @template T\n * @param callable(): T $apiCall\n * @return T\n *\n * @throws RateLimitException\n */\n private function executeRequest(callable $apiCall)\n {\n if (! $this->rateLimiter->canMakeRequest($this->config)) {\n $retryAfter = $this->rateLimiter->requestAvailableIn($this->config);\n\n $this->log->warning('[Hubspot] Rate limit exceeded, deferring request', [\n 'team_id' => $this->config->team_id,\n 'config_id' => $this->config->getId(),\n 'retry_after' => $retryAfter,\n ]);\n\n throw new RateLimitException(\n 'Hubspot rate limit reached for configuration ' . $this->config->getId(),\n $retryAfter,\n );\n }\n\n $this->rateLimiter->incrementRequestCount($this->config);\n\n try {\n return $apiCall();\n } catch (Throwable $e) {\n if ($this->isHubspotRateLimit($e)) {\n $retryAfter = $this->parseRetryAfter($e);\n\n $this->log->warning('[Hubspot] Received 429 from API', [\n 'team_id' => $this->config->team_id,\n 'config_id' => $this->config->getId(),\n 'retry_after' => $retryAfter,\n 'reason' => $e->getMessage(),\n ]);\n\n throw new RateLimitException('Hubspot returned 429', $retryAfter, $e);\n }\n\n throw $e;\n }\n }\n\n private function isHubspotRateLimit(Throwable $e): bool\n {\n return method_exists($e, 'getCode') && (int) $e->getCode() === 429;\n }\n\n private function parseRetryAfter(Throwable $e): int\n {\n if (method_exists($e, 'getResponseHeaders')) {\n $headers = $e->getResponseHeaders() ?: [];\n $value = $headers['Retry-After'] ?? $headers['retry-after'] ?? null;\n if (is_array($value)) {\n $value = $value[0] ?? null;\n }\n if (is_numeric($value)) {\n return (int) $value;\n }\n }\n\n return 10;\n }\n\n public function getMinimumApiVersion(): string\n {\n return self::MIN_API_VERSION;\n }\n\n public function getInstance(): Factory\n {\n return new Factory([\n 'key' => $this->accessToken,\n 'oauth2' => true,\n 'base_url' => $this->baseUrl,\n ]);\n }\n\n public function getNewInstance(): Discovery\n {\n return \\HubSpot\\Factory::createWithAccessToken($this->accessToken);\n }\n\n /**\n * Secondly and daily limits for Hubspot API\n *\n * Product Tier: Free & Starter | Professional & Enterprise | API add-on (any tier)\n * Burst: 100/10 seconds | 150/10 seconds | 200/10 seconds\n * Daily: 250,000 | 500,000 | 1,000,000\n *\n * Official documentation states: The search endpoints are rate limited to five requests per second.\n * Since with 5 RPS were still hitting secondly rate limits we lowered it to 4\n */\n public function getPaginatedData(array $payload, string $type, int $offset = 0): array\n {\n $total = 0;\n $lastId = null;\n $rows = [];\n foreach ($this->getPaginatedDataGenerator($payload, $type, $offset, $total, $lastId) as $row) {\n $rows[] = $row;\n }\n\n return ['results' => $rows, 'total' => $total, 'last_record' => $lastId];\n }\n\n /**\n * @throws HubspotException\n * @throws SocialAccountTokenInvalidException\n * @throws BadRequest\n */\n public function getPaginatedDataGenerator(\n array $payload,\n string $type,\n int $offset = 0,\n int &$total = 0,\n ?string &$lastRecordId = null\n ): \\Generator {\n return $this->paginationService->getPaginatedDataGenerator(\n $this,\n $payload,\n $type,\n $offset,\n $total,\n $lastRecordId\n );\n }\n\n /**\n * @throws DealApiException\n * @throws CrmException\n */\n public function getOpportunityById(string $crmId, array $fields): array\n {\n try {\n $deal = $this->executeRequest(fn () => $this->getNewInstance()->crm()->deals()->basicApi()->getById(\n $crmId,\n implode(',', $fields),\n 'companies,contacts'\n ));\n } catch (DealApiException $e) {\n $this->log->info('[Hubspot] Failed to fetch opportunity', [\n 'crm_id' => $crmId,\n 'reason' => $e->getMessage(),\n ]);\n\n throw $e;\n }\n\n if (! $deal instanceof DealWithAssociations) {\n throw new CrmException('Deal not found');\n }\n\n return [\n 'id' => $deal->getId(),\n 'properties' => $deal->getProperties(),\n 'associations' => $deal->getAssociations(),\n ];\n }\n\n /**\n * Generic batch read method for HubSpot objects\n *\n * @param string $objectType The object type ('deals', 'companies', 'contacts')\n * @param array<string> $crmIds Array of HubSpot object IDs (max 100)\n * @param array<string> $fields Array of property names to fetch\n *\n * @return array<string, array> Array keyed by CRM ID with object data\n */\n private function batchReadObjects(string $objectType, array $crmIds, array $fields): array\n {\n if (empty($crmIds)) {\n return [];\n }\n\n $this->validateBatchSize($objectType, $crmIds);\n $this->ensureValidToken();\n\n try {\n $batchConfig = $this->createBatchConfiguration($objectType);\n $batchReadRequest = $this->prepareBatchRequest($batchConfig, $crmIds, $fields);\n $response = $batchConfig['api']->read($batchReadRequest);\n\n $this->validateApiResponse($response, $objectType);\n\n $results = $this->processApiResults($response);\n $this->logBatchResults($objectType, $crmIds, $results);\n\n return $results;\n } catch (\\Throwable $e) {\n $this->handleBatchError($e, $objectType, $crmIds);\n }\n }\n\n private function validateBatchSize(string $objectType, array $crmIds): void\n {\n if (count($crmIds) > 100) {\n throw new \\InvalidArgumentException(\"Batch size cannot exceed 100 {$objectType}\");\n }\n }\n\n private function createBatchConfiguration(string $objectType): array\n {\n $configurations = [\n 'deals' => [\n 'batchReadRequest' => new \\HubSpot\\Client\\Crm\\Deals\\Model\\BatchReadInputSimplePublicObjectId(),\n 'inputClass' => \\HubSpot\\Client\\Crm\\Deals\\Model\\SimplePublicObjectId::class,\n 'api' => $this->getNewInstance()->crm()->deals()->batchApi(),\n ],\n 'companies' => [\n 'batchReadRequest' => new \\HubSpot\\Client\\Crm\\Companies\\Model\\BatchReadInputSimplePublicObjectId(),\n 'inputClass' => \\HubSpot\\Client\\Crm\\Companies\\Model\\SimplePublicObjectId::class,\n 'api' => $this->getNewInstance()->crm()->companies()->batchApi(),\n ],\n 'contacts' => [\n 'batchReadRequest' => new \\HubSpot\\Client\\Crm\\Contacts\\Model\\BatchReadInputSimplePublicObjectId(),\n 'inputClass' => \\HubSpot\\Client\\Crm\\Contacts\\Model\\SimplePublicObjectId::class,\n 'api' => $this->getNewInstance()->crm()->contacts()->batchApi(),\n ],\n ];\n\n if (! isset($configurations[$objectType])) {\n throw new \\InvalidArgumentException(\"Unsupported object type: {$objectType}\");\n }\n\n return $configurations[$objectType];\n }\n\n private function prepareBatchRequest(array $batchConfig, array $crmIds, array $fields): object\n {\n $batchReadRequest = $batchConfig['batchReadRequest'];\n $inputClass = $batchConfig['inputClass'];\n\n $inputs = array_map(function ($crmId) use ($inputClass) {\n $input = new $inputClass();\n $input->setId($crmId);\n\n return $input;\n }, $crmIds);\n\n $batchReadRequest->setInputs($inputs);\n $batchReadRequest->setProperties($fields);\n\n return $batchReadRequest;\n }\n\n private function validateApiResponse($response, string $objectType): void\n {\n if (! $response) {\n throw new CrmException(\"HubSpot API returned null response for {$objectType} batch read\");\n }\n }\n\n private function processApiResults($response): array\n {\n $results = [];\n $responseResults = $response->getResults();\n\n if ($responseResults) {\n foreach ($responseResults as $object) {\n if ($object && $object->getId()) {\n $results[$object->getId()] = [\n 'id' => $object->getId(),\n 'properties' => $object->getProperties() ?: [],\n ];\n }\n }\n }\n\n return $results;\n }\n\n private function logBatchResults(string $objectType, array $crmIds, array $results): void\n {\n $this->log->info(\"[HubSpot] Batch fetched {$objectType}\", [\n 'requested_count' => count($crmIds),\n 'returned_count' => count($results),\n 'crm_ids' => $crmIds,\n ]);\n }\n\n private function handleBatchError(\\Throwable $e, string $objectType, array $crmIds): void\n {\n $errorMessage = $e->getMessage() ?: 'Unknown error';\n $errorTrace = $e->getTraceAsString() ?: 'No trace available';\n\n $this->log->error(\"[HubSpot] Failed to batch fetch {$objectType}\", [\n 'crm_ids' => $crmIds,\n 'error' => $errorMessage,\n 'trace' => $errorTrace,\n ]);\n\n throw new CrmException(\"Failed to batch fetch {$objectType}: \" . $errorMessage);\n }\n\n /**\n * Batch read multiple opportunities by their CRM IDs\n *\n * @param array<string> $crmIds Array of HubSpot deal IDs (max 100)\n * @param array<string> $fields Array of property names to fetch\n *\n * @return array<string, array> Array keyed by CRM ID with opportunity data\n */\n public function getOpportunitiesByIds(array $crmIds, array $fields): array\n {\n return $this->batchReadObjects('deals', $crmIds, $fields);\n }\n\n /**\n * Batch read multiple companies by their CRM IDs\n *\n * @param array<string> $crmIds Array of HubSpot company IDs (max 100)\n * @param array<string> $fields Array of property names to fetch\n *\n * @return array<string, array> Array keyed by CRM ID with company data\n */\n public function getCompaniesByIds(array $crmIds, array $fields): array\n {\n return $this->batchReadObjects('companies', $crmIds, $fields);\n }\n\n /**\n * Batch read multiple contacts by their CRM IDs\n *\n * @param array<string> $crmIds Array of HubSpot contact IDs (max 100)\n * @param array<string> $fields Array of property names to fetch\n *\n * @return array<string, array> Array keyed by CRM ID with contact data\n */\n public function getContactsByIds(array $crmIds, array $fields): array\n {\n return $this->batchReadObjects('contacts', $crmIds, $fields);\n }\n\n /**\n * @throws CompanyApiException\n * @throws CrmException\n */\n public function getAccountById(string $crmId, array $fields): array\n {\n try {\n $company = $this->getNewInstance()->crm()->companies()->basicApi()->getById(\n $crmId,\n implode(',', $fields),\n );\n } catch (CompanyApiException $e) {\n $this->log->info('[Hubspot] Failed to fetch account', [\n 'crm_id' => $crmId,\n 'reason' => $e->getMessage(),\n ]);\n\n throw $e;\n }\n\n if (! $company instanceof CompaniesWithAssociations) {\n throw new CrmException('Account not found');\n }\n\n return [\n 'id' => $company->getId(),\n 'properties' => $company->getProperties(),\n ];\n }\n\n /**\n * @throws ContactApiException\n * @throws CrmException\n */\n public function getContactById(string $crmId, array $fields): array\n {\n try {\n $contact = $this->getNewInstance()->crm()->contacts()->basicApi()->getById(\n $crmId,\n implode(',', $fields)\n );\n } catch (ContactApiException $e) {\n $this->log->info('[Hubspot] Failed to fetch contact', [\n 'crm_id' => $crmId,\n 'reason' => $e->getMessage(),\n ]);\n\n throw $e;\n }\n\n if (! $contact instanceof ContactsWithAssociations) {\n throw new CrmException('Contact not found');\n }\n\n return [\n 'id' => $contact->getId(),\n 'properties' => $contact->getProperties(),\n ];\n }\n\n /**\n * This is email search request that Hubspot offers as GET (more generous quota)\n */\n public function getContactByEmail(string $email, array $fields = []): array\n {\n try {\n $contact = $this->getNewInstance()->crm()->contacts()->basicApi()->getById(\n $email,\n implode(',', $fields),\n null,\n false,\n 'email'\n );\n\n return [\n 'id' => $contact->getId(),\n 'properties' => $contact->getProperties(),\n ];\n } catch (ContactApiException $e) {\n $this->log->info('[Hubspot] Failed to fetch contact', [\n 'email' => $email,\n 'reason' => $e->getMessage(),\n ]);\n\n return [];\n }\n }\n\n /**\n * @throws CrmException\n */\n public function fetchProperty(string $objectType, string $propertyId): Property\n {\n $result = $this->getNewInstance()->crm()->properties()->coreApi()->getByName($objectType, $propertyId);\n\n if (! $result instanceof Property) {\n $this->log->error('[Hubspot] Failed to fetch property', [\n 'object_type' => $objectType,\n 'property_id' => $propertyId,\n 'reason' => $result->getMessage(),\n ]);\n\n throw new CrmException('Failed to fetch property');\n }\n\n return $result;\n }\n\n /**\n * @return array<CrmFieldOption>\n */\n public function fetchPropertyOptions(string $objectType, string $propertyId): array\n {\n /** @var array<CrmFieldOption> */\n return $this->fetchProperty($objectType, $propertyId)->getOptions();\n }\n\n /**\n * @return array<array{id:string, label:string, deleted:bool}>\n */\n public function fetchCallDispositions(): array\n {\n /** @var Response $response */\n $response = $this->getInstance()->engagements()->getCallDispositions();\n\n /**\n * @var array<array{\n * id:string,\n * label:string,\n * deleted: bool\n * }>\n */\n return $response->toArray();\n }\n\n /**\n * @return array<CrmFieldOption>\n */\n public function fetchOpportunityPipelineStages(): array\n {\n $stages = [];\n $apiResponse = $this->getNewInstance()->crm()->pipelines()->pipelinesApi()->getAll('deals');\n\n if ($apiResponse instanceof Error) {\n $this->log->error('[Hubspot] Failed to fetch opportunity pipelines', [\n 'reason' => $apiResponse->getMessage(),\n ]);\n\n return [];\n }\n\n foreach ($apiResponse->getResults() as $pipeline) {\n $pipelineStages = array_map(\n static function (PipelineStage $stage) {\n return [\n 'id' => $stage->getId(),\n 'label' => $stage->getLabel(),\n ];\n },\n $pipeline->getStages()\n );\n\n $stages = array_merge($stages, $pipelineStages);\n }\n\n return $stages;\n }\n\n public function fetchOpportunityPipelines(): array\n {\n $pipelines = [];\n\n try {\n $apiResponse = $this->makeRequest('/crm/v3/pipelines/deals');\n } catch (\\Exception $e) {\n $this->log->info('[Hubspot] Failed to fetch opportunity pipelines', [\n 'reason' => $e->getMessage(),\n ]);\n\n return [];\n }\n\n $response = $apiResponse->toArray();\n\n foreach ($response['results'] as $pipeline) {\n $pipelines[] = [\n 'id' => $pipeline['id'],\n 'label' => $pipeline['label'],\n ];\n }\n\n return $pipelines;\n }\n\n /**\n * @return array<CrmFieldOption>\n */\n public function fetchMeetingOutcomeFieldOptions(Field $field): array\n {\n return $field->getCrmProviderId() === 'meetingOutcome'\n ? $this->fetchMeetingOutcomeTypes()\n : $this->fetchCallActivityTypes();\n }\n\n public function fetchMeetingOutcomeTypes(): array\n {\n return $this->extractMeetingTypeOptions(\n 'https://api.hubapi.com/crm/v3/properties/meeting/hs_meeting_outcome'\n );\n }\n\n public function fetchCallActivityTypes(): array\n {\n return $this->extractMeetingTypeOptions(\n 'https://api.hubapi.com/crm/v3/properties/call/hs_activity_type'\n );\n }\n\n private function extractMeetingTypeOptions(string $endpoint): array\n {\n /** @var Response $response */\n $response = $this->getInstance()\n ->getClient()\n ->request('GET', $endpoint);\n\n /**\n * @var array<array{\n * value: string,\n * label: string,\n * displayOrder: int\n * }> $optionData\n */\n $optionData = $response->toArray()['options'] ?? [];\n\n $options = [];\n foreach ($optionData as $item) {\n $options[] = [\n 'id' => $item['value'],\n 'value' => $item['value'],\n 'label' => $item['label'],\n 'display_order' => $item['displayOrder'],\n ];\n }\n\n return $options;\n }\n\n /**\n * @return array<CrmFieldOption>\n */\n public function fetchDispositionFieldOptions(): array\n {\n $options = [];\n\n $dispositions = $this->fetchCallDispositions();\n\n foreach ($dispositions as $disposition) {\n if ($disposition['deleted'] !== false) {\n continue;\n }\n\n $option['value'] = $disposition['id'];\n $option['id'] = $disposition['id'];\n $option['label'] = $disposition['label'];\n\n $options[] = $option;\n }\n\n return $options;\n }\n\n /**\n * @return array<CrmFieldOption>\n */\n public function fetchOpportunityFieldOptions(Field $field): array\n {\n if ($field->isStageField()) {\n return $this->fetchOpportunityPipelineStages();\n }\n\n if ($field->isPipelineField()) {\n return $this->fetchOpportunityPipelines();\n }\n\n return $this->fetchPropertyOptions('deals', $field->getCrmProviderId());\n }\n\n /**\n * @throws BadRequest\n * @throws HubspotException\n */\n public function makeRequest(string $endpoint, $method = 'GET', $payload = [], ?string $queryString = null)\n {\n $endpoint = self::BASE_URL . $endpoint;\n\n if ($method === 'GET') {\n $response = $this->getInstance()->getClient()?->request(\n method: $method,\n endpoint: $endpoint,\n query_string: $queryString\n );\n } else {\n $response = $this->getInstance()->getClient()->request($method, $endpoint, [\n 'json' => ($payload),\n ]);\n }\n\n $max = $response->getHeaderLine('X-HubSpot-RateLimit-Max'); // \"110\"\n $remaining = $response->getHeaderLine('X-HubSpot-RateLimit-Remaining'); // \"109\"\n $interval = $response->getHeaderLine('X-HubSpot-RateLimit-Interval-Milliseconds'); // \"10000\"\n $body = json_decode((string) $response->getBody(), true);\n\n \\Illuminate\\Support\\Facades\\Log::channel('custom_channel')->info('$max ' . PHP_EOL . print_r($max, true));\n \\Illuminate\\Support\\Facades\\Log::channel('custom_channel')->info('$remaining ' . PHP_EOL . print_r($remaining, true));\n \\Illuminate\\Support\\Facades\\Log::channel('custom_channel')->info('$interval ' . PHP_EOL . print_r($interval, true));\n \\Illuminate\\Support\\Facades\\Log::channel('custom_channel')->info('$body ' . PHP_EOL . print_r($body, true));\n\n return $response;\n }\n\n /**\n * @throws BadRequest\n * @throws HubspotException\n */\n public function createMeeting(array $payload): Response\n {\n $endpoint = '/crm/v3/objects/meetings';\n\n return $this->makeRequest($endpoint, 'POST', $payload);\n }\n\n /**\n * @throws BadRequest\n * @throws HubspotException\n */\n public function updateMeeting(string $meetingId, array $payload): Response\n {\n $endpoint = '/crm/v3/objects/meetings/' . $meetingId;\n\n return $this->makeRequest($endpoint, 'PATCH', $payload);\n }\n\n /**\n * @throws \\Exception\n */\n public function createNote(\n string $body,\n string $ownerId,\n int $timestamp,\n string $objectId,\n NoteObject $noteObject\n ): ?string {\n try {\n $noteInput = new SimplePublicObjectInput([\n 'properties' => [\n 'hs_note_body' => $body,\n 'hubspot_owner_id' => $ownerId,\n 'hs_timestamp' => $timestamp,\n ],\n ]);\n\n // Create note\n $note = $this->getNewInstance()->crm()->objects()->basicApi()->create('note', $noteInput);\n\n $this->getNewInstance()->crm()->objects()->associationsApi()->create(\n 'note',\n $note->getId(),\n $this->getNoteObject($noteObject),\n $objectId,\n $this->getNoteAssociationType($noteObject),\n );\n\n return $note->getId();\n } catch (\\Exception $e) {\n $this->log->error('[Hubspot] Failed to create note', [\n 'objectId' => $objectId,\n 'noteObject' => $noteObject->getObjectType(),\n 'reason' => $e->getMessage(),\n ]);\n\n \\Sentry::captureException($e);\n }\n\n return null;\n }\n\n public function updateEngagement(string $objectId, array $engagement, array $metadata): void\n {\n $this->getInstance()->engagements()->update($objectId, $engagement, $metadata);\n }\n\n public function getEngagementData(string $engagementId): array\n {\n $engagement = $this->getInstance()->engagements()->get($engagementId);\n\n return $engagement->toArray();\n }\n\n public function createEngagement(array $engagement, array $associations, array $metadata): Response\n {\n return $this->getInstance()\n ->engagements()\n ->create($engagement, $associations, $metadata);\n }\n\n public function isUnauthorizedException(\\Exception $e): bool\n {\n // Check for specific HubSpot API exception types first\n if ($e instanceof BadRequest) {\n // BadRequest can contain 401 status codes\n return $e->getCode() === 401;\n }\n\n // Check for HTTP client exceptions with status codes\n if ($e instanceof \\GuzzleHttp\\Exception\\RequestException && $e->hasResponse()) {\n $response = $e->getResponse();\n if ($response !== null) {\n return $response->getStatusCode() === 401;\n }\n }\n\n // Check for Guzzle HTTP exceptions\n if ($e instanceof \\GuzzleHttp\\Exception\\ClientException) {\n return $e->getCode() === 401;\n }\n\n // Fallback to string matching as last resort, but be more specific\n $message = strtolower($e->getMessage());\n\n return str_contains($message, '401 unauthorized') ||\n str_contains($message, 'http 401') ||\n str_contains($message, 'status code 401') ||\n (preg_match('/\\b401\\b/', $message) && str_contains($message, 'unauthorized'));\n }\n\n /**\n * Validates and refreshes the access token if needed before API requests.\n * This ensures long-running processes don't fail due to token expiration.\n *\n * @throws SocialAccountTokenInvalidException\n */\n public function ensureValidToken(): void\n {\n if ($this->oauthAccount === null) {\n return;\n }\n\n $newToken = $this->tokenManager->ensureValidToken($this->oauthAccount);\n if ($newToken !== null) {\n $this->accessToken = $newToken;\n }\n }\n\n public function getConfig()\n {\n return $this->config;\n }\n\n // returns only active (archived=false)\n public function getOwners(): array\n {\n return $this->getNewInstance()->crm()->owners()->getAll();\n }\n\n /**\n * @param bool $archived\n *\n * @return array<Owner>|[]\n */\n public function getOwnersArchived(bool $archived = true): array\n {\n $endpoint = '/crm/v3/owners';\n $queryParams = [\n 'archived' => $archived ? 'true' : 'false',\n ];\n $queryString = http_build_query($queryParams);\n\n $owners = [];\n\n try {\n $response = $this->makeRequest(endpoint: $endpoint, queryString: $queryString);\n $responseData = $response?->toArray();\n\n foreach ($responseData['results'] as $result) {\n try {\n $owners[] = Owner::create($result);\n } catch (Throwable $e) {\n $this->log->error('[HubSpot] Failed to process owner data', [\n 'result' => $result,\n 'error' => $e->getMessage(),\n ]);\n\n continue;\n }\n }\n } catch (Throwable $e) {\n $this->log->error('HubSpot] Failed to fetch owners', [\n 'archived' => $archived,\n 'error' => $e->getMessage(),\n ]);\n\n return [];\n }\n\n return $owners;\n }\n\n public function getMeeting(string $engagementId): ObjectWithAssociations\n {\n return $this->getNewInstance()->crm()->objects()->basicApi()\n ->getById('meeting', $engagementId, null, 'contact,company,deal');\n }\n\n public function deleteEngagement(string $engagementId): void\n {\n $this->getInstance()->engagements()->delete((int) $engagementId);\n }\n\n public function getAssociationsData(array $ids, string $fromObject, string $toObject): array\n {\n $associationData = [];\n $idChunks = array_chunk($ids, self::ASSOCIATIONS_BATCH_SIZE_LIMIT);\n\n foreach ($idChunks as $idChunk) {\n try {\n $batchInput = new \\HubSpot\\Client\\Crm\\Associations\\Model\\BatchInputPublicObjectId();\n $batchInput->setInputs(array_map(function ($id) {\n $publicObjectId = new \\HubSpot\\Client\\Crm\\Associations\\Model\\PublicObjectId();\n $publicObjectId->setId($id);\n\n return $publicObjectId;\n }, $idChunk));\n\n $associatedObjectsData = $this\n ->getNewInstance()\n ->crm()\n ->associations()\n ->batchApi()\n ->read($fromObject, $toObject, $batchInput);\n\n if ($associatedObjectsData instanceof \\HubSpot\\Client\\Crm\\Associations\\Model\\BatchResponsePublicAssociationMulti) {\n foreach ($associatedObjectsData->getResults() as $association) {\n $from = $association->getFrom()->getId();\n $toAssociations = $association->getTo();\n\n if (! empty($toAssociations)) {\n $associationData[$from] = array_map(function ($item) {\n return $item->getId();\n }, $toAssociations);\n }\n }\n }\n } catch (\\Exception $e) {\n $this->log->error('[Hubspot] Failed to fetch associations', [\n 'from_object' => $fromObject,\n 'to_object' => $toObject,\n 'reason' => $e->getMessage(),\n ]);\n }\n }\n\n return $associationData;\n }\n\n /**\n * @throws \\Exception\n */\n private function getNoteAssociationType(NoteObject $noteObject): string\n {\n return match($noteObject) {\n NoteObject::Opportunity => 'note_to_deal',\n NoteObject::Lead, NoteObject::Contact => 'note_to_contact', // or 'note_to_lead' if your portal supports it\n NoteObject::Account => 'note_to_company',\n NoteObject::Call, NoteObject::Event => throw new \\Exception('Not supported'),\n };\n }\n\n /**\n * @throws \\Exception\n */\n private function getNoteObject(NoteObject $noteObject): string\n {\n return match($noteObject) {\n NoteObject::Opportunity => 'deal',\n NoteObject::Lead, NoteObject::Contact => 'contact',\n NoteObject::Account => 'company',\n NoteObject::Call, NoteObject::Event => throw new \\Exception('Not supported'),\n };\n }\n\n public function addAssociations(string $objectType, string $associationType, array $payload): Response\n {\n $endpoint = \"/crm/v4/associations/$objectType/$associationType/batch/create\";\n\n return $this->makeRequest($endpoint, 'POST', $payload);\n }\n\n public function removeAssociations(string $objectType, string $associationType, array $payload): Response\n {\n $endpoint = \"/crm/v4/associations/$objectType/$associationType/batch/archive\";\n\n return $this->makeRequest($endpoint, 'POST', $payload);\n }\n}","role_description":"text entry area","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"Project","depth":3,"on_screen":false,"role_description":"text"},{"role":"AXButton","text":"Project","depth":3,"bounds":{"left":0.011968086,"top":0.047885075,"width":0.024268618,"height":0.024740623},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"New File or Directory…","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.008643617,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Expand Selected","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.008643617,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Collapse All","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.008643617,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Options","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.008643617,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Hide","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.008643617,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false}]...
|
-8123360485970677608
|
5514066055808292964
|
click
|
accessibility
|
NULL
|
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
[2026-05-07 11:36:26] local.INFO: Jiminny\Console\Commands\Command::run Memory usage before starting command {"command":"crm:sync-hubspot-objects","memoryBeforeCommandInMb":60.0,"memoryPeakBeforeCommandInMb":99.727} {"correlation_id":"f71ac7c4-e9d4-46ad-be82-47e68a8f1199","trace_id":"5c938a2d-aea9-4025-a7f7-733d8e8e9711"}
[2026-05-07 11:36:26] local.INFO: Jiminny\Console\Commands\Command::run Memory usage for command {"command":"crm:sync-hubspot-objects","memoryBeforeCommandInMb":60.0,"memoryAfterCommandInMB":62.0,"memoryPeakBeforeCommandInMb":99.727,"memoryPeakAfterCommandInMB":99.727} {"correlation_id":"f71ac7c4-e9d4-46ad-be82-47e68a8f1199","trace_id":"5c938a2d-aea9-4025-a7f7-733d8e8e9711"}
[2026-05-07 11:36:27] local.INFO: [SyncHubspotObjects] Starting sync {"team":"abae74b8-bfa8-4383-9a7f-89f4bf2bdbb4","usage":24412984,"real_usage":65011712,"pid":13732} {"correlation_id":"766570a3-89a2-4427-81c2-c70a7c780ee3","trace_id":"5c938a2d-aea9-4025-a7f7-733d8e8e9711"}
[2026-05-07 11:36:27] local.INFO: [SocialAccountService] Fetching token {"socialAccountId":1499,"provider":"hubspot"} {"correlation_id":"766570a3-89a2-4427-81c2-c70a7c780ee3","trace_id":"5c938a2d-aea9-4025-a7f7-733d8e8e9711"}
[2026-05-07 11:36:27] local.INFO: [SocialAccountService] Token needs refreshing {"socialAccountId":1499,"provider":"hubspot"} {"correlation_id":"766570a3-89a2-4427-81c2-c70a7c780ee3","trace_id":"5c938a2d-aea9-4025-a7f7-733d8e8e9711"}
[2026-05-07 11:36:27] local.INFO: [EncryptedTokenManager] Generating access token. {"mode":"legacy"} {"correlation_id":"766570a3-89a2-4427-81c2-c70a7c780ee3","trace_id":"5c938a2d-aea9-4025-a7f7-733d8e8e9711"}
[2026-05-07 11:36:27] local.INFO: [SocialAccountService] Refreshing token from provider {"socialAccountId":1499,"provider":"hubspot","refreshToken":"96f94c623a404e02ebdbf07f1b75707bb6cdbf848cbf45d418baf608c41a8d86","state":"connected"} {"correlation_id":"766570a3-89a2-4427-81c2-c70a7c780ee3","trace_id":"5c938a2d-aea9-4025-a7f7-733d8e8e9711"}
[2026-05-07 11:36:27] local.INFO: [SocialAccountObserver] Saving model {"correlation_id":"766570a3-89a2-4427-81c2-c70a7c780ee3","trace_id":"5c938a2d-aea9-4025-a7f7-733d8e8e9711"}
[2026-05-07 11:36:27] local.INFO: [SocialAccountObserver] Access token was modified, encrypting {"correlation_id":"766570a3-89a2-4427-81c2-c70a7c780ee3","trace_id":"5c938a2d-aea9-4025-a7f7-733d8e8e9711"}
[2026-05-07 11:36:28] local.INFO: [SocialAccountService] Token refreshed {"socialAccountId":1499,"provider":"hubspot","state":"connected"} {"correlation_id":"766570a3-89a2-4427-81c2-c70a7c780ee3","trace_id":"5c938a2d-aea9-4025-a7f7-733d8e8e9711"}
[2026-05-07 11:36:28] local.INFO: [CrmOwnerResolver] Integration owner matched as CRM Owner {"crm_provider":"hubspot","crm_owner":148,"team_id":2} {"correlation_id":"766570a3-89a2-4427-81c2-c70a7c780ee3","trace_id":"5c938a2d-aea9-4025-a7f7-733d8e8e9711"}
[2026-05-07 11:36:28] local.INFO: [HubSpot] Syncing opportunities using strategy: lastModified {"team":2} {"correlation_id":"766570a3-89a2-4427-81c2-c70a7c780ee3","trace_id":"5c938a2d-aea9-4025-a7f7-733d8e8e9711"}
[2026-05-07 11:36:28] local.INFO: [Hubspot] Pagination completed {"team_id":2,"endpoint":"[URL_WITH_CREDENTIALS] CrmFieldOption array{id:string, label:string, value?:string}
*/
class Client extends BaseClient implements HubspotClientInterface
{
public const string MIN_API_VERSION = '2';
public const string BASE_URL = '[URL_WITH_CREDENTIALS] T
* @param callable(): T $apiCall
* @return T
*
* @throws RateLimitException
*/
private function executeRequest(callable $apiCall)
{
if (! $this->rateLimiter->canMakeRequest($this->config)) {
$retryAfter = $this->rateLimiter->requestAvailableIn($this->config);
$this->log->warning('[Hubspot] Rate limit exceeded, deferring request', [
'team_id' => $this->config->team_id,
'config_id' => $this->config->getId(),
'retry_after' => $retryAfter,
]);
throw new RateLimitException(
'Hubspot rate limit reached for configuration ' . $this->config->getId(),
$retryAfter,
);
}
$this->rateLimiter->incrementRequestCount($this->config);
try {
return $apiCall();
} catch (Throwable $e) {
if ($this->isHubspotRateLimit($e)) {
$retryAfter = $this->parseRetryAfter($e);
$this->log->warning('[Hubspot] Received 429 from API', [
'team_id' => $this->config->team_id,
'config_id' => $this->config->getId(),
'retry_after' => $retryAfter,
'reason' => $e->getMessage(),
]);
throw new RateLimitException('Hubspot returned 429', $retryAfter, $e);
}
throw $e;
}
}
private function isHubspotRateLimit(Throwable $e): bool
{
return method_exists($e, 'getCode') && (int) $e->getCode() === 429;
}
private function parseRetryAfter(Throwable $e): int
{
if (method_exists($e, 'getResponseHeaders')) {
$headers = $e->getResponseHeaders() ?: [];
$value = $headers['Retry-After'] ?? $headers['retry-after'] ?? null;
if (is_array($value)) {
$value = $value[0] ?? null;
}
if (is_numeric($value)) {
return (int) $value;
}
}
return 10;
}
public function getMinimumApiVersion(): string
{
return self::MIN_API_VERSION;
}
public function getInstance(): Factory
{
return new Factory([
'key' => $this->accessToken,
'oauth2' => true,
'base_url' => $this->baseUrl,
]);
}
public function getNewInstance(): Discovery
{
return \HubSpot\Factory::createWithAccessToken($this->accessToken);
}
/**
* Secondly and daily limits for Hubspot API
*
* Product Tier: Free & Starter | Professional & Enterprise | API add-on (any tier)
* Burst: 100/10 seconds | 150/10 seconds | 200/10 seconds
* Daily: 250,000 | 500,000 | 1,000,000
*
* Official documentation states: The search endpoints are rate limited to five requests per second.
* Since with 5 RPS were still hitting secondly rate limits we lowered it to 4
*/
public function getPaginatedData(array $payload, string $type, int $offset = 0): array
{
$total = 0;
$lastId = null;
$rows = [];
foreach ($this->getPaginatedDataGenerator($payload, $type, $offset, $total, $lastId) as $row) {
$rows[] = $row;
}
return ['results' => $rows, 'total' => $total, 'last_record' => $lastId];
}
/**
* @throws HubspotException
* @throws SocialAccountTokenInvalidException
* @throws BadRequest
*/
public function getPaginatedDataGenerator(
array $payload,
string $type,
int $offset = 0,
int &$total = 0,
?string &$lastRecordId = null
): \Generator {
return $this->paginationService->getPaginatedDataGenerator(
$this,
$payload,
$type,
$offset,
$total,
$lastRecordId
);
}
/**
* @throws DealApiException
* @throws CrmException
*/
public function getOpportunityById(string $crmId, array $fields): array
{
try {
$deal = $this->executeRequest(fn () => $this->getNewInstance()->crm()->deals()->basicApi()->getById(
$crmId,
implode(',', $fields),
'companies,contacts'
));
} catch (DealApiException $e) {
$this->log->info('[Hubspot] Failed to fetch opportunity', [
'crm_id' => $crmId,
'reason' => $e->getMessage(),
]);
throw $e;
}
if (! $deal instanceof DealWithAssociations) {
throw new CrmException('Deal not found');
}
return [
'id' => $deal->getId(),
'properties' => $deal->getProperties(),
'associations' => $deal->getAssociations(),
];
}
/**
* Generic batch read method for HubSpot objects
*
* @param string $objectType The object type ('deals', 'companies', 'contacts')
* @param array<string> $crmIds Array of HubSpot object IDs (max 100)
* @param array<string> $fields Array of property names to fetch
*
* @return array<string, array> Array keyed by CRM ID with object data
*/
private function batchReadObjects(string $objectType, array $crmIds, array $fields): array
{
if (empty($crmIds)) {
return [];
}
$this->validateBatchSize($objectType, $crmIds);
$this->ensureValidToken();
try {
$batchConfig = $this->createBatchConfiguration($objectType);
$batchReadRequest = $this->prepareBatchRequest($batchConfig, $crmIds, $fields);
$response = $batchConfig['api']->read($batchReadRequest);
$this->validateApiResponse($response, $objectType);
$results = $this->processApiResults($response);
$this->logBatchResults($objectType, $crmIds, $results);
return $results;
} catch (\Throwable $e) {
$this->handleBatchError($e, $objectType, $crmIds);
}
}
private function validateBatchSize(string $objectType, array $crmIds): void
{
if (count($crmIds) > 100) {
throw new \InvalidArgumentException("Batch size cannot exceed 100 {$objectType}");
}
}
private function createBatchConfiguration(string $objectType): array
{
$configurations = [
'deals' => [
'batchReadRequest' => new \HubSpot\Client\Crm\Deals\Model\BatchReadInputSimplePublicObjectId(),
'inputClass' => \HubSpot\Client\Crm\Deals\Model\SimplePublicObjectId::class,
'api' => $this->getNewInstance()->crm()->deals()->batchApi(),
],
'companies' => [
'batchReadRequest' => new \HubSpot\Client\Crm\Companies\Model\BatchReadInputSimplePublicObjectId(),
'inputClass' => \HubSpot\Client\Crm\Companies\Model\SimplePublicObjectId::class,
'api' => $this->getNewInstance()->crm()->companies()->batchApi(),
],
'contacts' => [
'batchReadRequest' => new \HubSpot\Client\Crm\Contacts\Model\BatchReadInputSimplePublicObjectId(),
'inputClass' => \HubSpot\Client\Crm\Contacts\Model\SimplePublicObjectId::class,
'api' => $this->getNewInstance()->crm()->contacts()->batchApi(),
],
];
if (! isset($configurations[$objectType])) {
throw new \InvalidArgumentException("Unsupported object type: {$objectType}");
}
return $configurations[$objectType];
}
private function prepareBatchRequest(array $batchConfig, array $crmIds, array $fields): object
{
$batchReadRequest = $batchConfig['batchReadRequest'];
$inputClass = $batchConfig['inputClass'];
$inputs = array_map(function ($crmId) use ($inputClass) {
$input = new $inputClass();
$input->setId($crmId);
return $input;
}, $crmIds);
$batchReadRequest->setInputs($inputs);
$batchReadRequest->setProperties($fields);
return $batchReadRequest;
}
private function validateApiResponse($response, string $objectType): void
{
if (! $response) {
throw new CrmException("HubSpot API returned null response for {$objectType} batch read");
}
}
private function processApiResults($response): array
{
$results = [];
$responseResults = $response->getResults();
if ($responseResults) {
foreach ($responseResults as $object) {
if ($object && $object->getId()) {
$results[$object->getId()] = [
'id' => $object->getId(),
'properties' => $object->getProperties() ?: [],
];
}
}
}
return $results;
}
private function logBatchResults(string $objectType, array $crmIds, array $results): void
{
$this->log->info("[HubSpot] Batch fetched {$objectType}", [
'requested_count' => count($crmIds),
'returned_count' => count($results),
'crm_ids' => $crmIds,
]);
}
private function handleBatchError(\Throwable $e, string $objectType, array $crmIds): void
{
$errorMessage = $e->getMessage() ?: 'Unknown error';
$errorTrace = $e->getTraceAsString() ?: 'No trace available';
$this->log->error("[HubSpot] Failed to batch fetch {$objectType}", [
'crm_ids' => $crmIds,
'error' => $errorMessage,
'trace' => $errorTrace,
]);
throw new CrmException("Failed to batch fetch {$objectType}: " . $errorMessage);
}
/**
* Batch read multiple opportunities by their CRM IDs
*
* @param array<string> $crmIds Array of HubSpot deal IDs (max 100)
* @param array<string> $fields Array of property names to fetch
*
* @return array<string, array> Array keyed by CRM ID with opportunity data
*/
public function getOpportunitiesByIds(array $crmIds, array $fields): array
{
return $this->batchReadObjects('deals', $crmIds, $fields);
}
/**
* Batch read multiple companies by their CRM IDs
*
* @param array<string> $crmIds Array of HubSpot company IDs (max 100)
* @param array<string> $fields Array of property names to fetch
*
* @return array<string, array> Array keyed by CRM ID with company data
*/
public function getCompaniesByIds(array $crmIds, array $fields): array
{
return $this->batchReadObjects('companies', $crmIds, $fields);
}
/**
* Batch read multiple contacts by their CRM IDs
*
* @param array<string> $crmIds Array of HubSpot contact IDs (max 100)
* @param array<string> $fields Array of property names to fetch
*
* @return array<string, array> Array keyed by CRM ID with contact data
*/
public function getContactsByIds(array $crmIds, array $fields): array
{
return $this->batchReadObjects('contacts', $crmIds, $fields);
}
/**
* @throws CompanyApiException
* @throws CrmException
*/
public function getAccountById(string $crmId, array $fields): array
{
try {
$company = $this->getNewInstance()->crm()->companies()->basicApi()->getById(
$crmId,
implode(',', $fields),
);
} catch (CompanyApiException $e) {
$this->log->info('[Hubspot] Failed to fetch account', [
'crm_id' => $crmId,
'reason' => $e->getMessage(),
]);
throw $e;
}
if (! $company instanceof CompaniesWithAssociations) {
throw new CrmException('Account not found');
}
return [
'id' => $company->getId(),
'properties' => $company->getProperties(),
];
}
/**
* @throws ContactApiException
* @throws CrmException
*/
public function getContactById(string $crmId, array $fields): array
{
try {
$contact = $this->getNewInstance()->crm()->contacts()->basicApi()->getById(
$crmId,
implode(',', $fields)
);
} catch (ContactApiException $e) {
$this->log->info('[Hubspot] Failed to fetch contact', [
'crm_id' => $crmId,
'reason' => $e->getMessage(),
]);
throw $e;
}
if (! $contact instanceof ContactsWithAssociations) {
throw new CrmException('Contact not found');
}
return [
'id' => $contact->getId(),
'properties' => $contact->getProperties(),
];
}
/**
* This is email search request that Hubspot offers as GET (more generous quota)
*/
public function getContactByEmail(string $email, array $fields = []): array
{
try {
$contact = $this->getNewInstance()->crm()->contacts()->basicApi()->getById(
$email,
implode(',', $fields),
null,
false,
'email'
);
return [
'id' => $contact->getId(),
'properties' => $contact->getProperties(),
];
} catch (ContactApiException $e) {
$this->log->info('[Hubspot] Failed to fetch contact', [
'email' => $email,
'reason' => $e->getMessage(),
]);
return [];
}
}
/**
* @throws CrmException
*/
public function fetchProperty(string $objectType, string $propertyId): Property
{
$result = $this->getNewInstance()->crm()->properties()->coreApi()->getByName($objectType, $propertyId);
if (! $result instanceof Property) {
$this->log->error('[Hubspot] Failed to fetch property', [
'object_type' => $objectType,
'property_id' => $propertyId,
'reason' => $result->getMessage(),
]);
throw new CrmException('Failed to fetch property');
}
return $result;
}
/**
* @return array<CrmFieldOption>
*/
public function fetchPropertyOptions(string $objectType, string $propertyId): array
{
/** @var array<CrmFieldOption> */
return $this->fetchProperty($objectType, $propertyId)->getOptions();
}
/**
* @return array<array{id:string, label:string, deleted:bool}>
*/
public function fetchCallDispositions(): array
{
/** @var Response $response */
$response = $this->getInstance()->engagements()->getCallDispositions();
/**
* @var array<array{
* id:string,
* label:string,
* deleted: bool
* }>
*/
return $response->toArray();
}
/**
* @return array<CrmFieldOption>
*/
public function fetchOpportunityPipelineStages(): array
{
$stages = [];
$apiResponse = $this->getNewInstance()->crm()->pipelines()->pipelinesApi()->getAll('deals');
if ($apiResponse instanceof Error) {
$this->log->error('[Hubspot] Failed to fetch opportunity pipelines', [
'reason' => $apiResponse->getMessage(),
]);
return [];
}
foreach ($apiResponse->getResults() as $pipeline) {
$pipelineStages = array_map(
static function (PipelineStage $stage) {
return [
'id' => $stage->getId(),
'label' => $stage->getLabel(),
];
},
$pipeline->getStages()
);
$stages = array_merge($stages, $pipelineStages);
}
return $stages;
}
public function fetchOpportunityPipelines(): array
{
$pipelines = [];
try {
$apiResponse = $this->makeRequest('/crm/v3/pipelines/deals');
} catch (\Exception $e) {
$this->log->info('[Hubspot] Failed to fetch opportunity pipelines', [
'reason' => $e->getMessage(),
]);
return [];
}
$response = $apiResponse->toArray();
foreach ($response['results'] as $pipeline) {
$pipelines[] = [
'id' => $pipeline['id'],
'label' => $pipeline['label'],
];
}
return $pipelines;
}
/**
* @return array<CrmFieldOption>
*/
public function fetchMeetingOutcomeFieldOptions(Field $field): array
{
return $field->getCrmProviderId() === 'meetingOutcome'
? $this->fetchMeetingOutcomeTypes()
: $this->fetchCallActivityTypes();
}
public function fetchMeetingOutcomeTypes(): array
{
return $this->extractMeetingTypeOptions(
'[URL_WITH_CREDENTIALS] Response $response */
$response = $this->getInstance()
->getClient()
->request('GET', $endpoint);
/**
* @var array<array{
* value: string,
* label: string,
* displayOrder: int
* }> $optionData
*/
$optionData = $response->toArray()['options'] ?? [];
$options = [];
foreach ($optionData as $item) {
$options[] = [
'id' => $item['value'],
'value' => $item['value'],
'label' => $item['label'],
'display_order' => $item['displayOrder'],
];
}
return $options;
}
/**
* @return array<CrmFieldOption>
*/
public function fetchDispositionFieldOptions(): array
{
$options = [];
$dispositions = $this->fetchCallDispositions();
foreach ($dispositions as $disposition) {
if ($disposition['deleted'] !== false) {
continue;
}
$option['value'] = $disposition['id'];
$option['id'] = $disposition['id'];
$option['label'] = $disposition['label'];
$options[] = $option;
}
return $options;
}
/**
* @return array<CrmFieldOption>
*/
public function fetchOpportunityFieldOptions(Field $field): array
{
if ($field->isStageField()) {
return $this->fetchOpportunityPipelineStages();
}
if ($field->isPipelineField()) {
return $this->fetchOpportunityPipelines();
}
return $this->fetchPropertyOptions('deals', $field->getCrmProviderId());
}
/**
* @throws BadRequest
* @throws HubspotException
*/
public function makeRequest(string $endpoint, $method = 'GET', $payload = [], ?string $queryString = null)
{
$endpoint = self::BASE_URL . $endpoint;
if ($method === 'GET') {
$response = $this->getInstance()->getClient()?->request(
method: $method,
endpoint: $endpoint,
query_string: $queryString
);
} else {
$response = $this->getInstance()->getClient()->request($method, $endpoint, [
'json' => ($payload),
]);
}
$max = $response->getHeaderLine('X-HubSpot-RateLimit-Max'); // "110"
$remaining = $response->getHeaderLine('X-HubSpot-RateLimit-Remaining'); // "109"
$interval = $response->getHeaderLine('X-HubSpot-RateLimit-Interval-Milliseconds'); // "10000"
$body = json_decode((string) $response->getBody(), true);
\Illuminate\Support\Facades\Log::channel('custom_channel')->info('$max ' . PHP_EOL . print_r($max, true));
\Illuminate\Support\Facades\Log::channel('custom_channel')->info('$remaining ' . PHP_EOL . print_r($remaining, true));
\Illuminate\Support\Facades\Log::channel('custom_channel')->info('$interval ' . PHP_EOL . print_r($interval, true));
\Illuminate\Support\Facades\Log::channel('custom_channel')->info('$body ' . PHP_EOL . print_r($body, true));
return $response;
}
/**
* @throws BadRequest
* @throws HubspotException
*/
public function createMeeting(array $payload): Response
{
$endpoint = '/crm/v3/objects/meetings';
return $this->makeRequest($endpoint, 'POST', $payload);
}
/**
* @throws BadRequest
* @throws HubspotException
*/
public function updateMeeting(string $meetingId, array $payload): Response
{
$endpoint = '/crm/v3/objects/meetings/' . $meetingId;
return $this->makeRequest($endpoint, 'PATCH', $payload);
}
/**
* @throws \Exception
*/
public function createNote(
string $body,
string $ownerId,
int $timestamp,
string $objectId,
NoteObject $noteObject
): ?string {
try {
$noteInput = new SimplePublicObjectInput([
'properties' => [
'hs_note_body' => $body,
'hubspot_owner_id' => $ownerId,
'hs_timestamp' => $timestamp,
],
]);
// Create note
$note = $this->getNewInstance()->crm()->objects()->basicApi()->create('note', $noteInput);
$this->getNewInstance()->crm()->objects()->associationsApi()->create(
'note',
$note->getId(),
$this->getNoteObject($noteObject),
$objectId,
$this->getNoteAssociationType($noteObject),
);
return $note->getId();
} catch (\Exception $e) {
$this->log->error('[Hubspot] Failed to create note', [
'objectId' => $objectId,
'noteObject' => $noteObject->getObjectType(),
'reason' => $e->getMessage(),
]);
\Sentry::captureException($e);
}
return null;
}
public function updateEngagement(string $objectId, array $engagement, array $metadata): void
{
$this->getInstance()->engagements()->update($objectId, $engagement, $metadata);
}
public function getEngagementData(string $engagementId): array
{
$engagement = $this->getInstance()->engagements()->get($engagementId);
return $engagement->toArray();
}
public function createEngagement(array $engagement, array $associations, array $metadata): Response
{
return $this->getInstance()
->engagements()
->create($engagement, $associations, $metadata);
}
public function isUnauthorizedException(\Exception $e): bool
{
// Check for specific HubSpot API exception types first
if ($e instanceof BadRequest) {
// BadRequest can contain 401 status codes
return $e->getCode() === 401;
}
// Check for HTTP client exceptions with status codes
if ($e instanceof \GuzzleHttp\Exception\RequestException && $e->hasResponse()) {
$response = $e->getResponse();
if ($response !== null) {
return $response->getStatusCode() === 401;
}
}
// Check for Guzzle HTTP exceptions
if ($e instanceof \GuzzleHttp\Exception\ClientException) {
return $e->getCode() === 401;
}
// Fallback to string matching as last resort, but be more specific
$message = strtolower($e->getMessage());
return str_contains($message, '401 unauthorized') ||
str_contains($message, 'http 401') ||
str_contains($message, 'status code 401') ||
(preg_match('/\b401\b/', $message) && str_contains($message, 'unauthorized'));
}
/**
* Validates and refreshes the access token if needed before API requests.
* This ensures long-running processes don't fail due to token expiration.
*
* @throws SocialAccountTokenInvalidException
*/
public function ensureValidToken(): void
{
if ($this->oauthAccount === null) {
return;
}
$newToken = $this->tokenManager->ensureValidToken($this->oauthAccount);
if ($newToken !== null) {
$this->accessToken = $newToken;
}
}
public function getConfig()
{
return $this->config;
}
// returns only active (archived=false)
public function getOwners(): array
{
return $this->getNewInstance()->crm()->owners()->getAll();
}
/**
* @param bool $archived
*
* @return array<Owner>|[]
*/
public function getOwnersArchived(bool $archived = true): array
{
$endpoint = '/crm/v3/owners';
$queryParams = [
'archived' => $archived ? 'true' : 'false',
];
$queryString = http_build_query($queryParams);
$owners = [];
try {
$response = $this->makeRequest(endpoint: $endpoint, queryString: $queryString);
$responseData = $response?->toArray();
foreach ($responseData['results'] as $result) {
try {
$owners[] = Owner::create($result);
} catch (Throwable $e) {
$this->log->error('[HubSpot] Failed to process owner data', [
'result' => $result,
'error' => $e->getMessage(),
]);
continue;
}
}
} catch (Throwable $e) {
$this->log->error('HubSpot] Failed to fetch owners', [
'archived' => $archived,
'error' => $e->getMessage(),
]);
return [];
}
return $owners;
}
public function getMeeting(string $engagementId): ObjectWithAssociations
{
return $this->getNewInstance()->crm()->objects()->basicApi()
->getById('meeting', $engagementId, null, 'contact,company,deal');
}
public function deleteEngagement(string $engagementId): void
{
$this->getInstance()->engagements()->delete((int) $engagementId);
}
public function getAssociationsData(array $ids, string $fromObject, string $toObject): array
{
$associationData = [];
$idChunks = array_chunk($ids, self::ASSOCIATIONS_BATCH_SIZE_LIMIT);
foreach ($idChunks as $idChunk) {
try {
$batchInput = new \HubSpot\Client\Crm\Associations\Model\BatchInputPublicObjectId();
$batchInput->setInputs(array_map(function ($id) {
$publicObjectId = new \HubSpot\Client\Crm\Associations\Model\PublicObjectId();
$publicObjectId->setId($id);
return $publicObjectId;
}, $idChunk));
$associatedObjectsData = $this
->getNewInstance()
->crm()
->associations()
->batchApi()
->read($fromObject, $toObject, $batchInput);
if ($associatedObjectsData instanceof \HubSpot\Client\Crm\Associations\Model\BatchResponsePublicAssociationMulti) {
foreach ($associatedObjectsData->getResults() as $association) {
$from = $association->getFrom()->getId();
$toAssociations = $association->getTo();
if (! empty($toAssociations)) {
$associationData[$from] = array_map(function ($item) {
return $item->getId();
}, $toAssociations);
}
}
}
} catch (\Exception $e) {
$this->log->error('[Hubspot] Failed to fetch associations', [
'from_object' => $fromObject,
'to_object' => $toObject,
'reason' => $e->getMessage(),
]);
}
}
return $associationData;
}
/**
* @throws \Exception
*/
private function getNoteAssociationType(NoteObject $noteObject): string
{
return match($noteObject) {
NoteObject::Opportunity => 'note_to_deal',
NoteObject::Lead, NoteObject::Contact => 'note_to_contact', // or 'note_to_lead' if your portal supports it
NoteObject::Account => 'note_to_company',
NoteObject::Call, NoteObject::Event => throw new \Exception('Not supported'),
};
}
/**
* @throws \Exception
*/
private function getNoteObject(NoteObject $noteObject): string
{
return match($noteObject) {
NoteObject::Opportunity => 'deal',
NoteObject::Lead, NoteObject::Contact => 'contact',
NoteObject::Account => 'company',
NoteObject::Call, NoteObject::Event => throw new \Exception('Not supported'),
};
}
public function addAssociations(string $objectType, string $associationType, array $payload): Response
{
$endpoint = "/crm/v4/associations/$objectType/$associationType/batch/create";
return $this->makeRequest($endpoint, 'POST', $payload);
}
public function removeAssociations(string $objectType, string $associationType, array $payload): Response
{
$endpoint = "/crm/v4/associations/$objectType/$associationType/batch/archive";
return $this->makeRequest($endpoint, 'POST', $payload);
}
}
Project
Project
New File or Directory…
Expand Selected
Collapse All
Options
Hide...
|
NULL
|
NULL
|
NULL
|
NULL
|
|
2711
|
112
|
28
|
2026-05-07T11:36:40.591165+00:00
|
/Users/lukas/.screenpipe/data/data/2026-05-07/1778 /Users/lukas/.screenpipe/data/data/2026-05-07/1778153800591_m2.jpg...
|
PhpStorm
|
faVsco.js – laravel.log
|
True
|
NULL
|
monitor_2
|
NULL
|
NULL
|
NULL
|
NULL
|
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
2
60
Previous Highlighted Error
Next Highlighted Error
<?php
declare(strict_types=1);
namespace Jiminny\Services\Crm\Hubspot;
use HubSpot\Client\Crm\Deals\ApiException as DealApiException;
use HubSpot\Client\Crm\Contacts\ApiException as ContactApiException;
use HubSpot\Client\Crm\Companies\ApiException as CompanyApiException;
use HubSpot\Client\Crm\Contacts\Model\SimplePublicObjectWithAssociations as ContactsWithAssociations;
use HubSpot\Client\Crm\Companies\Model\SimplePublicObjectWithAssociations as CompaniesWithAssociations;
use HubSpot\Client\Crm\Deals\Model\SimplePublicObjectWithAssociations as DealWithAssociations;
use HubSpot\Client\Crm\Objects\Model\SimplePublicObjectInput;
use HubSpot\Client\Crm\Objects\Model\SimplePublicObjectWithAssociations as ObjectWithAssociations;
use HubSpot\Client\Crm\Pipelines\Model\Error;
use HubSpot\Client\Crm\Pipelines\Model\PipelineStage;
use HubSpot\Client\Crm\Properties\Model\Property;
use HubSpot\Discovery\Discovery;
use Jiminny\Component\Utility\Service\ProviderRateLimiter;
use Jiminny\Exceptions\CrmException;
use Jiminny\Exceptions\RateLimitException;
use Jiminny\Exceptions\SocialAccountTokenInvalidException;
use Jiminny\Jobs\Crm\NoteObject;
use Jiminny\Models\Crm\Field;
use Jiminny\Services\Crm\BaseClient;
use Jiminny\Services\Crm\Hubspot\DTO\Response\Owner;
use Jiminny\Services\SocialAccountService;
use SevenShores\Hubspot\Exceptions\BadRequest;
use SevenShores\Hubspot\Exceptions\HubspotException;
use SevenShores\Hubspot\Factory;
use SevenShores\Hubspot\Http\Response;
use Jiminny\Services\Crm\Hubspot\Pagination\HubspotPaginationService;
use Throwable;
/**
* @phpstan-type CrmFieldOption array{id:string, label:string, value?:string}
*/
class Client extends BaseClient implements HubspotClientInterface
{
public const string MIN_API_VERSION = '2';
public const string BASE_URL = '[URL_WITH_CREDENTIALS] T
* @param callable(): T $apiCall
* @return T
*
* @throws RateLimitException
*/
private function executeRequest(callable $apiCall)
{
if (! $this->rateLimiter->canMakeRequest($this->config)) {
$retryAfter = $this->rateLimiter->requestAvailableIn($this->config);
$this->log->warning('[Hubspot] Rate limit exceeded, deferring request', [
'team_id' => $this->config->team_id,
'config_id' => $this->config->getId(),
'retry_after' => $retryAfter,
]);
throw new RateLimitException(
'Hubspot rate limit reached for configuration ' . $this->config->getId(),
$retryAfter,
);
}
$this->rateLimiter->incrementRequestCount($this->config);
try {
return $apiCall();
} catch (Throwable $e) {
if ($this->isHubspotRateLimit($e)) {
$retryAfter = $this->parseRetryAfter($e);
$this->log->warning('[Hubspot] Received 429 from API', [
'team_id' => $this->config->team_id,
'config_id' => $this->config->getId(),
'retry_after' => $retryAfter,
'reason' => $e->getMessage(),
]);
throw new RateLimitException('Hubspot returned 429', $retryAfter, $e);
}
throw $e;
}
}
private function isHubspotRateLimit(Throwable $e): bool
{
return method_exists($e, 'getCode') && (int) $e->getCode() === 429;
}
private function parseRetryAfter(Throwable $e): int
{
if (method_exists($e, 'getResponseHeaders')) {
$headers = $e->getResponseHeaders() ?: [];
$value = $headers['Retry-After'] ?? $headers['retry-after'] ?? null;
if (is_array($value)) {
$value = $value[0] ?? null;
}
if (is_numeric($value)) {
return (int) $value;
}
}
return 10;
}
public function getMinimumApiVersion(): string
{
return self::MIN_API_VERSION;
}
public function getInstance(): Factory
{
return new Factory([
'key' => $this->accessToken,
'oauth2' => true,
'base_url' => $this->baseUrl,
]);
}
public function getNewInstance(): Discovery
{
return \HubSpot\Factory::createWithAccessToken($this->accessToken);
}
/**
* Secondly and daily limits for Hubspot API
*
* Product Tier: Free & Starter | Professional & Enterprise | API add-on (any tier)
* Burst: 100/10 seconds | 150/10 seconds | 200/10 seconds
* Daily: 250,000 | 500,000 | 1,000,000
*
* Official documentation states: The search endpoints are rate limited to five requests per second.
* Since with 5 RPS were still hitting secondly rate limits we lowered it to 4
*/
public function getPaginatedData(array $payload, string $type, int $offset = 0): array
{
$total = 0;
$lastId = null;
$rows = [];
foreach ($this->getPaginatedDataGenerator($payload, $type, $offset, $total, $lastId) as $row) {
$rows[] = $row;
}
return ['results' => $rows, 'total' => $total, 'last_record' => $lastId];
}
/**
* @throws HubspotException
* @throws SocialAccountTokenInvalidException
* @throws BadRequest
*/
public function getPaginatedDataGenerator(
array $payload,
string $type,
int $offset = 0,
int &$total = 0,
?string &$lastRecordId = null
): \Generator {
return $this->paginationService->getPaginatedDataGenerator(
$this,
$payload,
$type,
$offset,
$total,
$lastRecordId
);
}
/**
* @throws DealApiException
* @throws CrmException
*/
public function getOpportunityById(string $crmId, array $fields): array
{
try {
$deal = $this->executeRequest(fn () => $this->getNewInstance()->crm()->deals()->basicApi()->getById(
$crmId,
implode(',', $fields),
'companies,contacts'
));
} catch (DealApiException $e) {
$this->log->info('[Hubspot] Failed to fetch opportunity', [
'crm_id' => $crmId,
'reason' => $e->getMessage(),
]);
throw $e;
}
if (! $deal instanceof DealWithAssociations) {
throw new CrmException('Deal not found');
}
return [
'id' => $deal->getId(),
'properties' => $deal->getProperties(),
'associations' => $deal->getAssociations(),
];
}
/**
* Generic batch read method for HubSpot objects
*
* @param string $objectType The object type ('deals', 'companies', 'contacts')
* @param array<string> $crmIds Array of HubSpot object IDs (max 100)
* @param array<string> $fields Array of property names to fetch
*
* @return array<string, array> Array keyed by CRM ID with object data
*/
private function batchReadObjects(string $objectType, array $crmIds, array $fields): array
{
if (empty($crmIds)) {
return [];
}
$this->validateBatchSize($objectType, $crmIds);
$this->ensureValidToken();
try {
$batchConfig = $this->createBatchConfiguration($objectType);
$batchReadRequest = $this->prepareBatchRequest($batchConfig, $crmIds, $fields);
$response = $batchConfig['api']->read($batchReadRequest);
$this->validateApiResponse($response, $objectType);
$results = $this->processApiResults($response);
$this->logBatchResults($objectType, $crmIds, $results);
return $results;
} catch (\Throwable $e) {
$this->handleBatchError($e, $objectType, $crmIds);
}
}
private function validateBatchSize(string $objectType, array $crmIds): void
{
if (count($crmIds) > 100) {
throw new \InvalidArgumentException("Batch size cannot exceed 100 {$objectType}");
}
}
private function createBatchConfiguration(string $objectType): array
{
$configurations = [
'deals' => [
'batchReadRequest' => new \HubSpot\Client\Crm\Deals\Model\BatchReadInputSimplePublicObjectId(),
'inputClass' => \HubSpot\Client\Crm\Deals\Model\SimplePublicObjectId::class,
'api' => $this->getNewInstance()->crm()->deals()->batchApi(),
],
'companies' => [
'batchReadRequest' => new \HubSpot\Client\Crm\Companies\Model\BatchReadInputSimplePublicObjectId(),
'inputClass' => \HubSpot\Client\Crm\Companies\Model\SimplePublicObjectId::class,
'api' => $this->getNewInstance()->crm()->companies()->batchApi(),
],
'contacts' => [
'batchReadRequest' => new \HubSpot\Client\Crm\Contacts\Model\BatchReadInputSimplePublicObjectId(),
'inputClass' => \HubSpot\Client\Crm\Contacts\Model\SimplePublicObjectId::class,
'api' => $this->getNewInstance()->crm()->contacts()->batchApi(),
],
];
if (! isset($configurations[$objectType])) {
throw new \InvalidArgumentException("Unsupported object type: {$objectType}");
}
return $configurations[$objectType];
}
private function prepareBatchRequest(array $batchConfig, array $crmIds, array $fields): object
{
$batchReadRequest = $batchConfig['batchReadRequest'];
$inputClass = $batchConfig['inputClass'];
$inputs = array_map(function ($crmId) use ($inputClass) {
$input = new $inputClass();
$input->setId($crmId);
return $input;
}, $crmIds);
$batchReadRequest->setInputs($inputs);
$batchReadRequest->setProperties($fields);
return $batchReadRequest;
}
private function validateApiResponse($response, string $objectType): void
{
if (! $response) {
throw new CrmException("HubSpot API returned null response for {$objectType} batch read");
}
}
private function processApiResults($response): array
{
$results = [];
$responseResults = $response->getResults();
if ($responseResults) {
foreach ($responseResults as $object) {
if ($object && $object->getId()) {
$results[$object->getId()] = [
'id' => $object->getId(),
'properties' => $object->getProperties() ?: [],
];
}
}
}
return $results;
}
private function logBatchResults(string $objectType, array $crmIds, array $results): void
{
$this->log->info("[HubSpot] Batch fetched {$objectType}", [
'requested_count' => count($crmIds),
'returned_count' => count($results),
'crm_ids' => $crmIds,
]);
}
private function handleBatchError(\Throwable $e, string $objectType, array $crmIds): void
{
$errorMessage = $e->getMessage() ?: 'Unknown error';
$errorTrace = $e->getTraceAsString() ?: 'No trace available';
$this->log->error("[HubSpot] Failed to batch fetch {$objectType}", [
'crm_ids' => $crmIds,
'error' => $errorMessage,
'trace' => $errorTrace,
]);
throw new CrmException("Failed to batch fetch {$objectType}: " . $errorMessage);
}
/**
* Batch read multiple opportunities by their CRM IDs
*
* @param array<string> $crmIds Array of HubSpot deal IDs (max 100)
* @param array<string> $fields Array of property names to fetch
*
* @return array<string, array> Array keyed by CRM ID with opportunity data
*/
public function getOpportunitiesByIds(array $crmIds, array $fields): array
{
return $this->batchReadObjects('deals', $crmIds, $fields);
}
/**
* Batch read multiple companies by their CRM IDs
*
* @param array<string> $crmIds Array of HubSpot company IDs (max 100)
* @param array<string> $fields Array of property names to fetch
*
* @return array<string, array> Array keyed by CRM ID with company data
*/
public function getCompaniesByIds(array $crmIds, array $fields): array
{
return $this->batchReadObjects('companies', $crmIds, $fields);
}
/**
* Batch read multiple contacts by their CRM IDs
*
* @param array<string> $crmIds Array of HubSpot contact IDs (max 100)
* @param array<string> $fields Array of property names to fetch
*
* @return array<string, array> Array keyed by CRM ID with contact data
*/
public function getContactsByIds(array $crmIds, array $fields): array
{
return $this->batchReadObjects('contacts', $crmIds, $fields);
}
/**
* @throws CompanyApiException
* @throws CrmException
*/
public function getAccountById(string $crmId, array $fields): array
{
try {
$company = $this->getNewInstance()->crm()->companies()->basicApi()->getById(
$crmId,
implode(',', $fields),
);
} catch (CompanyApiException $e) {
$this->log->info('[Hubspot] Failed to fetch account', [
'crm_id' => $crmId,
'reason' => $e->getMessage(),
]);
throw $e;
}
if (! $company instanceof CompaniesWithAssociations) {
throw new CrmException('Account not found');
}
return [
'id' => $company->getId(),
'properties' => $company->getProperties(),
];
}
/**
* @throws ContactApiException
* @throws CrmException
*/
public function getContactById(string $crmId, array $fields): array
{
try {
$contact = $this->getNewInstance()->crm()->contacts()->basicApi()->getById(
$crmId,
implode(',', $fields)
);
} catch (ContactApiException $e) {
$this->log->info('[Hubspot] Failed to fetch contact', [
'crm_id' => $crmId,
'reason' => $e->getMessage(),
]);
throw $e;
}
if (! $contact instanceof ContactsWithAssociations) {
throw new CrmException('Contact not found');
}
return [
'id' => $contact->getId(),
'properties' => $contact->getProperties(),
];
}
/**
* This is email search request that Hubspot offers as GET (more generous quota)
*/
public function getContactByEmail(string $email, array $fields = []): array
{
try {
$contact = $this->getNewInstance()->crm()->contacts()->basicApi()->getById(
$email,
implode(',', $fields),
null,
false,
'email'
);
return [
'id' => $contact->getId(),
'properties' => $contact->getProperties(),
];
} catch (ContactApiException $e) {
$this->log->info('[Hubspot] Failed to fetch contact', [
'email' => $email,
'reason' => $e->getMessage(),
]);
return [];
}
}
/**
* @throws CrmException
*/
public function fetchProperty(string $objectType, string $propertyId): Property
{
$result = $this->getNewInstance()->crm()->properties()->coreApi()->getByName($objectType, $propertyId);
if (! $result instanceof Property) {
$this->log->error('[Hubspot] Failed to fetch property', [
'object_type' => $objectType,
'property_id' => $propertyId,
'reason' => $result->getMessage(),
]);
throw new CrmException('Failed to fetch property');
}
return $result;
}
/**
* @return array<CrmFieldOption>
*/
public function fetchPropertyOptions(string $objectType, string $propertyId): array
{
/** @var array<CrmFieldOption> */
return $this->fetchProperty($objectType, $propertyId)->getOptions();
}
/**
* @return array<array{id:string, label:string, deleted:bool}>
*/
public function fetchCallDispositions(): array
{
/** @var Response $response */
$response = $this->getInstance()->engagements()->getCallDispositions();
/**
* @var array<array{
* id:string,
* label:string,
* deleted: bool
* }>
*/
return $response->toArray();
}
/**
* @return array<CrmFieldOption>
*/
public function fetchOpportunityPipelineStages(): array
{
$stages = [];
$apiResponse = $this->getNewInstance()->crm()->pipelines()->pipelinesApi()->getAll('deals');
if ($apiResponse instanceof Error) {
$this->log->error('[Hubspot] Failed to fetch opportunity pipelines', [
'reason' => $apiResponse->getMessage(),
]);
return [];
}
foreach ($apiResponse->getResults() as $pipeline) {
$pipelineStages = array_map(
static function (PipelineStage $stage) {
return [
'id' => $stage->getId(),
'label' => $stage->getLabel(),
];
},
$pipeline->getStages()
);
$stages = array_merge($stages, $pipelineStages);
}
return $stages;
}
public function fetchOpportunityPipelines(): array
{
$pipelines = [];
try {
$apiResponse = $this->makeRequest('/crm/v3/pipelines/deals');
} catch (\Exception $e) {
$this->log->info('[Hubspot] Failed to fetch opportunity pipelines', [
'reason' => $e->getMessage(),
]);
return [];
}
$response = $apiResponse->toArray();
foreach ($response['results'] as $pipeline) {
$pipelines[] = [
'id' => $pipeline['id'],
'label' => $pipeline['label'],
];
}
return $pipelines;
}
/**
* @return array<CrmFieldOption>
*/
public function fetchMeetingOutcomeFieldOptions(Field $field): array
{
return $field->getCrmProviderId() === 'meetingOutcome'
? $this->fetchMeetingOutcomeTypes()
: $this->fetchCallActivityTypes();
}
public function fetchMeetingOutcomeTypes(): array
{
return $this->extractMeetingTypeOptions(
'[URL_WITH_CREDENTIALS] Response $response */
$response = $this->getInstance()
->getClient()
->request('GET', $endpoint);
/**
* @var array<array{
* value: string,
* label: string,
* displayOrder: int
* }> $optionData
*/
$optionData = $response->toArray()['options'] ?? [];
$options = [];
foreach ($optionData as $item) {
$options[] = [
'id' => $item['value'],
'value' => $item['value'],
'label' => $item['label'],
'display_order' => $item['displayOrder'],
];
}
return $options;
}
/**
* @return array<CrmFieldOption>
*/
public function fetchDispositionFieldOptions(): array
{
$options = [];
$dispositions = $this->fetchCallDispositions();
foreach ($dispositions as $disposition) {
if ($disposition['deleted'] !== false) {
continue;
}
$option['value'] = $disposition['id'];
$option['id'] = $disposition['id'];
$option['label'] = $disposition['label'];
$options[] = $option;
}
return $options;
}
/**
* @return array<CrmFieldOption>
*/
public function fetchOpportunityFieldOptions(Field $field): array
{
if ($field->isStageField()) {
return $this->fetchOpportunityPipelineStages();
}
if ($field->isPipelineField()) {
return $this->fetchOpportunityPipelines();
}
return $this->fetchPropertyOptions('deals', $field->getCrmProviderId());
}
/**
* @throws BadRequest
* @throws HubspotException
*/
public function makeRequest(string $endpoint, $method = 'GET', $payload = [], ?string $queryString = null)
{
$endpoint = self::BASE_URL . $endpoint;
if ($method === 'GET') {
$response = $this->getInstance()->getClient()?->request(
method: $method,
endpoint: $endpoint,
query_string: $queryString
);
} else {
$response = $this->getInstance()->getClient()->request($method, $endpoint, [
'json' => ($payload),
]);
}
$max = $response->getHeaderLine('X-HubSpot-RateLimit-Max'); // "110"
$remaining = $response->getHeaderLine('X-HubSpot-RateLimit-Remaining'); // "109"
$interval = $response->getHeaderLine('X-HubSpot-RateLimit-Interval-Milliseconds'); // "10000"
$body = json_decode((string) $response->getBody(), true);
\Illuminate\Support\Facades\Log::channel('custom_channel')->info('$max ' . PHP_EOL . print_r($max, true));
\Illuminate\Support\Facades\Log::channel('custom_channel')->info('$remaining ' . PHP_EOL . print_r($remaining, true));
\Illuminate\Support\Facades\Log::channel('custom_channel')->info('$interval ' . PHP_EOL . print_r($interval, true));
\Illuminate\Support\Facades\Log::channel('custom_channel')->info('$body ' . PHP_EOL . print_r($body, true));
return $response;
}
/**
* @throws BadRequest
* @throws HubspotException
*/
public function createMeeting(array $payload): Response
{
$endpoint = '/crm/v3/objects/meetings';
return $this->makeRequest($endpoint, 'POST', $payload);
}
/**
* @throws BadRequest
* @throws HubspotException
*/
public function updateMeeting(string $meetingId, array $payload): Response
{
$endpoint = '/crm/v3/objects/meetings/' . $meetingId;
return $this->makeRequest($endpoint, 'PATCH', $payload);
}
/**
* @throws \Exception
*/
public function createNote(
string $body,
string $ownerId,
int $timestamp,
string $objectId,
NoteObject $noteObject
): ?string {
try {
$noteInput = new SimplePublicObjectInput([
'properties' => [
'hs_note_body' => $body,
'hubspot_owner_id' => $ownerId,
'hs_timestamp' => $timestamp,
],
]);
// Create note
$note = $this->getNewInstance()->crm()->objects()->basicApi()->create('note', $noteInput);
$this->getNewInstance()->crm()->objects()->associationsApi()->create(
'note',
$note->getId(),
$this->getNoteObject($noteObject),
$objectId,
$this->getNoteAssociationType($noteObject),
);
return $note->getId();
} catch (\Exception $e) {
$this->log->error('[Hubspot] Failed to create note', [
'objectId' => $objectId,
'noteObject' => $noteObject->getObjectType(),
'reason' => $e->getMessage(),
]);
\Sentry::captureException($e);
}
return null;
}
public function updateEngagement(string $objectId, array $engagement, array $metadata): void
{
$this->getInstance()->engagements()->update($objectId, $engagement, $metadata);
}
public function getEngagementData(string $engagementId): array
{
$engagement = $this->getInstance()->engagements()->get($engagementId);
return $engagement->toArray();
}
public function createEngagement(array $engagement, array $associations, array $metadata): Response
{
return $this->getInstance()
->engagements()
->create($engagement, $associations, $metadata);
}
public function isUnauthorizedException(\Exception $e): bool
{
// Check for specific HubSpot API exception types first
if ($e instanceof BadRequest) {
// BadRequest can contain 401 status codes
return $e->getCode() === 401;
}
// Check for HTTP client exceptions with status codes
if ($e instanceof \GuzzleHttp\Exception\RequestException && $e->hasResponse()) {
$response = $e->getResponse();
if ($response !== null) {
return $response->getStatusCode() === 401;
}
}
// Check for Guzzle HTTP exceptions
if ($e instanceof \GuzzleHttp\Exception\ClientException) {
return $e->getCode() === 401;
}
// Fallback to string matching as last resort, but be more specific
$message = strtolower($e->getMessage());
return str_contains($message, '401 unauthorized') ||
str_contains($message, 'http 401') ||
str_contains($message, 'status code 401') ||
(preg_match('/\b401\b/', $message) && str_contains($message, 'unauthorized'));
}
/**
* Validates and refreshes the access token if needed before API requests.
* This ensures long-running processes don't fail due to token expiration.
*
* @throws SocialAccountTokenInvalidException
*/
public function ensureValidToken(): void
{
if ($this->oauthAccount === null) {
return;
}
$newToken = $this->tokenManager->ensureValidToken($this->oauthAccount);
if ($newToken !== null) {
$this->accessToken = $newToken;
}
}
public function getConfig()
{
return $this->config;
}
// returns only active (archived=false)
public function getOwners(): array
{
return $this->getNewInstance()->crm()->owners()->getAll();
}
/**
* @param bool $archived
*
* @return array<Owner>|[]
*/
public function getOwnersArchived(bool $archived = true): array
{
$endpoint = '/crm/v3/owners';
$queryParams = [
'archived' => $archived ? 'true' : 'false',
];
$queryString = http_build_query($queryParams);
$owners = [];
try {
$response = $this->makeRequest(endpoint: $endpoint, queryString: $queryString);
$responseData = $response?->toArray();
foreach ($responseData['results'] as $result) {
try {
$owners[] = Owner::create($result);
} catch (Throwable $e) {
$this->log->error('[HubSpot] Failed to process owner data', [
'result' => $result,
'error' => $e->getMessage(),
]);
continue;
}
}
} catch (Throwable $e) {
$this->log->error('HubSpot] Failed to fetch owners', [
'archived' => $archived,
'error' => $e->getMessage(),
]);
return [];
}
return $owners;
}
public function getMeeting(string $engagementId): ObjectWithAssociations
{
return $this->getNewInstance()->crm()->objects()->basicApi()
->getById('meeting', $engagementId, null, 'contact,company,deal');
}
public function deleteEngagement(string $engagementId): void
{
$this->getInstance()->engagements()->delete((int) $engagementId);
}
public function getAssociationsData(array $ids, string $fromObject, string $toObject): array
{
$associationData = [];
$idChunks = array_chunk($ids, self::ASSOCIATIONS_BATCH_SIZE_LIMIT);
foreach ($idChunks as $idChunk) {
try {
$batchInput = new \HubSpot\Client\Crm\Associations\Model\BatchInputPublicObjectId();
$batchInput->setInputs(array_map(function ($id) {
$publicObjectId = new \HubSpot\Client\Crm\Associations\Model\PublicObjectId();
$publicObjectId->setId($id);
return $publicObjectId;
}, $idChunk));
$associatedObjectsData = $this
->getNewInstance()
->crm()
->associations()
->batchApi()
->read($fromObject, $toObject, $batchInput);
if ($associatedObjectsData instanceof \HubSpot\Client\Crm\Associations\Model\BatchResponsePublicAssociationMulti) {
foreach ($associatedObjectsData->getResults() as $association) {
$from = $association->getFrom()->getId();
$toAssociations = $association->getTo();
if (! empty($toAssociations)) {
$associationData[$from] = array_map(function ($item) {
return $item->getId();
}, $toAssociations);
}
}
}
} catch (\Exception $e) {
$this->log->error('[Hubspot] Failed to fetch associations', [
'from_object' => $fromObject,
'to_object' => $toObject,
'reason' => $e->getMessage(),
]);
}
}
return $associationData;
}
/**
* @throws \Exception
*/
private function getNoteAssociationType(NoteObject $noteObject): string
{
return match($noteObject) {
NoteObject::Opportunity => 'note_to_deal',
NoteObject::Lead, NoteObject::Contact => 'note_to_contact', // or 'note_to_lead' if your portal supports it
NoteObject::Account => 'note_to_company',
NoteObject::Call, NoteObject::Event => throw new \Exception('Not supported'),
};
}
/**
* @throws \Exception
*/
private function getNoteObject(NoteObject $noteObject): string
{
return match($noteObject) {
NoteObject::Opportunity => 'deal',
NoteObject::Lead, NoteObject::Contact => 'contact',
NoteObject::Account => 'company',
NoteObject::Call, NoteObject::Event => throw new \Exception('Not supported'),
};
}
public function addAssociations(string $objectType, string $associationType, array $payload): Response
{
$endpoint = "/crm/v4/associations/$objectType/$associationType/batch/create";
return $this->makeRequest($endpoint, 'POST', $payload);
}
public function removeAssociations(string $objectType, string $associationType, array $payload): Response
{
$endpoint = "/crm/v4/associations/$objectType/$associationType/batch/archive";
return $this->makeRequest($endpoint, 'POST', $payload);
}
}
Project
Project
New File or Directory…
Expand Selected
Collapse All
Options
Hide...
|
[{"role":"AXButton","text" [{"role":"AXButton","text":"Project: faVsco.js, menu","depth":5,"bounds":{"left":0.025930852,"top":0.019952115,"width":0.03856383,"height":0.025538707},"on_screen":true,"help_text":"~/jiminny/app","role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"master, menu","depth":5,"bounds":{"left":0.064494684,"top":0.019952115,"width":0.034242023,"height":0.025538707},"on_screen":true,"help_text":"Git Branch: master","role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Start Listening for PHP Debug Connections","depth":5,"bounds":{"left":0.8081782,"top":0.019952115,"width":0.011303191,"height":0.025538707},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"AskJiminnyReportActivityServiceTest","depth":6,"bounds":{"left":0.8234708,"top":0.019952115,"width":0.09208777,"height":0.025538707},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Run 'AskJiminnyReportActivityServiceTest'","depth":6,"bounds":{"left":0.9155585,"top":0.019952115,"width":0.011303191,"height":0.025538707},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Debug 'AskJiminnyReportActivityServiceTest'","depth":6,"bounds":{"left":0.9268617,"top":0.019952115,"width":0.011303191,"height":0.025538707},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"More Actions","depth":6,"bounds":{"left":0.9381649,"top":0.019952115,"width":0.011303191,"height":0.025538707},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"JetBrains AI","depth":5,"bounds":{"left":0.96609044,"top":0.019952115,"width":0.011303191,"height":0.025538707},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Search Everywhere","depth":5,"bounds":{"left":0.9773936,"top":0.019952115,"width":0.011303191,"height":0.025538707},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"IDE and Project Settings","depth":5,"bounds":{"left":0.9886968,"top":0.019952115,"width":0.011303186,"height":0.025538707},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXTextArea","text":"","depth":4,"bounds":{"left":0.52859044,"top":0.0726257,"width":0.45977393,"height":0.9066241},"on_screen":true,"value":"","role_description":"text entry area","is_enabled":true,"is_focused":true,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Sync Changes","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.008643617,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Hide This Notification","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.008643617,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Code changed:","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.042220745,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Hide","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.008643617,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"2","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.007978723,"height":0.0},"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"60","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.010305851,"height":0.0},"on_screen":false,"role_description":"text"},{"role":"AXButton","text":"Previous Highlighted Error","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.00731383,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Next Highlighted Error","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.006981383,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXTextArea","text":"<?php\n\ndeclare(strict_types=1);\n\nnamespace Jiminny\\Services\\Crm\\Hubspot;\n\nuse HubSpot\\Client\\Crm\\Deals\\ApiException as DealApiException;\nuse HubSpot\\Client\\Crm\\Contacts\\ApiException as ContactApiException;\nuse HubSpot\\Client\\Crm\\Companies\\ApiException as CompanyApiException;\nuse HubSpot\\Client\\Crm\\Contacts\\Model\\SimplePublicObjectWithAssociations as ContactsWithAssociations;\nuse HubSpot\\Client\\Crm\\Companies\\Model\\SimplePublicObjectWithAssociations as CompaniesWithAssociations;\nuse HubSpot\\Client\\Crm\\Deals\\Model\\SimplePublicObjectWithAssociations as DealWithAssociations;\nuse HubSpot\\Client\\Crm\\Objects\\Model\\SimplePublicObjectInput;\nuse HubSpot\\Client\\Crm\\Objects\\Model\\SimplePublicObjectWithAssociations as ObjectWithAssociations;\nuse HubSpot\\Client\\Crm\\Pipelines\\Model\\Error;\nuse HubSpot\\Client\\Crm\\Pipelines\\Model\\PipelineStage;\nuse HubSpot\\Client\\Crm\\Properties\\Model\\Property;\nuse HubSpot\\Discovery\\Discovery;\nuse Jiminny\\Component\\Utility\\Service\\ProviderRateLimiter;\nuse Jiminny\\Exceptions\\CrmException;\nuse Jiminny\\Exceptions\\RateLimitException;\nuse Jiminny\\Exceptions\\SocialAccountTokenInvalidException;\nuse Jiminny\\Jobs\\Crm\\NoteObject;\nuse Jiminny\\Models\\Crm\\Field;\nuse Jiminny\\Services\\Crm\\BaseClient;\nuse Jiminny\\Services\\Crm\\Hubspot\\DTO\\Response\\Owner;\nuse Jiminny\\Services\\SocialAccountService;\nuse SevenShores\\Hubspot\\Exceptions\\BadRequest;\nuse SevenShores\\Hubspot\\Exceptions\\HubspotException;\nuse SevenShores\\Hubspot\\Factory;\nuse SevenShores\\Hubspot\\Http\\Response;\nuse Jiminny\\Services\\Crm\\Hubspot\\Pagination\\HubspotPaginationService;\nuse Throwable;\n\n/**\n * @phpstan-type CrmFieldOption array{id:string, label:string, value?:string}\n */\nclass Client extends BaseClient implements HubspotClientInterface\n{\n public const string MIN_API_VERSION = '2';\n\n public const string BASE_URL = 'https://api.hubapi.com';\n\n public const int ASSOCIATIONS_BATCH_SIZE_LIMIT = 1000;\n\n private HubspotPaginationService $paginationService;\n private HubspotTokenManager $tokenManager;\n private ProviderRateLimiter $rateLimiter;\n\n public function __construct(\n SocialAccountService $socialAccountService,\n HubspotPaginationService $paginationService,\n HubspotTokenManager $tokenManager,\n ProviderRateLimiter $rateLimiter,\n ) {\n parent::__construct($socialAccountService);\n $this->paginationService = $paginationService;\n $this->tokenManager = $tokenManager;\n $this->rateLimiter = $rateLimiter;\n\n $this->setBaseUrl(self::BASE_URL);\n $this->setVersion(self::MIN_API_VERSION);\n }\n\n /**\n * Single entry point for every HubSpot API call. Enforces the per-portal\n * rate limit configured in the rate_limits table (morphed to the current\n * Configuration) and reacts to a real 429 from HubSpot by translating it\n * into a RateLimitException carrying Retry-After.\n *\n * Wrap any outbound HubSpot call (SDK or raw HTTP) like:\n *\n * $this->executeRequest(fn () => $this->getNewInstance()->crm()->...);\n *\n * @template T\n * @param callable(): T $apiCall\n * @return T\n *\n * @throws RateLimitException\n */\n private function executeRequest(callable $apiCall)\n {\n if (! $this->rateLimiter->canMakeRequest($this->config)) {\n $retryAfter = $this->rateLimiter->requestAvailableIn($this->config);\n\n $this->log->warning('[Hubspot] Rate limit exceeded, deferring request', [\n 'team_id' => $this->config->team_id,\n 'config_id' => $this->config->getId(),\n 'retry_after' => $retryAfter,\n ]);\n\n throw new RateLimitException(\n 'Hubspot rate limit reached for configuration ' . $this->config->getId(),\n $retryAfter,\n );\n }\n\n $this->rateLimiter->incrementRequestCount($this->config);\n\n try {\n return $apiCall();\n } catch (Throwable $e) {\n if ($this->isHubspotRateLimit($e)) {\n $retryAfter = $this->parseRetryAfter($e);\n\n $this->log->warning('[Hubspot] Received 429 from API', [\n 'team_id' => $this->config->team_id,\n 'config_id' => $this->config->getId(),\n 'retry_after' => $retryAfter,\n 'reason' => $e->getMessage(),\n ]);\n\n throw new RateLimitException('Hubspot returned 429', $retryAfter, $e);\n }\n\n throw $e;\n }\n }\n\n private function isHubspotRateLimit(Throwable $e): bool\n {\n return method_exists($e, 'getCode') && (int) $e->getCode() === 429;\n }\n\n private function parseRetryAfter(Throwable $e): int\n {\n if (method_exists($e, 'getResponseHeaders')) {\n $headers = $e->getResponseHeaders() ?: [];\n $value = $headers['Retry-After'] ?? $headers['retry-after'] ?? null;\n if (is_array($value)) {\n $value = $value[0] ?? null;\n }\n if (is_numeric($value)) {\n return (int) $value;\n }\n }\n\n return 10;\n }\n\n public function getMinimumApiVersion(): string\n {\n return self::MIN_API_VERSION;\n }\n\n public function getInstance(): Factory\n {\n return new Factory([\n 'key' => $this->accessToken,\n 'oauth2' => true,\n 'base_url' => $this->baseUrl,\n ]);\n }\n\n public function getNewInstance(): Discovery\n {\n return \\HubSpot\\Factory::createWithAccessToken($this->accessToken);\n }\n\n /**\n * Secondly and daily limits for Hubspot API\n *\n * Product Tier: Free & Starter | Professional & Enterprise | API add-on (any tier)\n * Burst: 100/10 seconds | 150/10 seconds | 200/10 seconds\n * Daily: 250,000 | 500,000 | 1,000,000\n *\n * Official documentation states: The search endpoints are rate limited to five requests per second.\n * Since with 5 RPS were still hitting secondly rate limits we lowered it to 4\n */\n public function getPaginatedData(array $payload, string $type, int $offset = 0): array\n {\n $total = 0;\n $lastId = null;\n $rows = [];\n foreach ($this->getPaginatedDataGenerator($payload, $type, $offset, $total, $lastId) as $row) {\n $rows[] = $row;\n }\n\n return ['results' => $rows, 'total' => $total, 'last_record' => $lastId];\n }\n\n /**\n * @throws HubspotException\n * @throws SocialAccountTokenInvalidException\n * @throws BadRequest\n */\n public function getPaginatedDataGenerator(\n array $payload,\n string $type,\n int $offset = 0,\n int &$total = 0,\n ?string &$lastRecordId = null\n ): \\Generator {\n return $this->paginationService->getPaginatedDataGenerator(\n $this,\n $payload,\n $type,\n $offset,\n $total,\n $lastRecordId\n );\n }\n\n /**\n * @throws DealApiException\n * @throws CrmException\n */\n public function getOpportunityById(string $crmId, array $fields): array\n {\n try {\n $deal = $this->executeRequest(fn () => $this->getNewInstance()->crm()->deals()->basicApi()->getById(\n $crmId,\n implode(',', $fields),\n 'companies,contacts'\n ));\n } catch (DealApiException $e) {\n $this->log->info('[Hubspot] Failed to fetch opportunity', [\n 'crm_id' => $crmId,\n 'reason' => $e->getMessage(),\n ]);\n\n throw $e;\n }\n\n if (! $deal instanceof DealWithAssociations) {\n throw new CrmException('Deal not found');\n }\n\n return [\n 'id' => $deal->getId(),\n 'properties' => $deal->getProperties(),\n 'associations' => $deal->getAssociations(),\n ];\n }\n\n /**\n * Generic batch read method for HubSpot objects\n *\n * @param string $objectType The object type ('deals', 'companies', 'contacts')\n * @param array<string> $crmIds Array of HubSpot object IDs (max 100)\n * @param array<string> $fields Array of property names to fetch\n *\n * @return array<string, array> Array keyed by CRM ID with object data\n */\n private function batchReadObjects(string $objectType, array $crmIds, array $fields): array\n {\n if (empty($crmIds)) {\n return [];\n }\n\n $this->validateBatchSize($objectType, $crmIds);\n $this->ensureValidToken();\n\n try {\n $batchConfig = $this->createBatchConfiguration($objectType);\n $batchReadRequest = $this->prepareBatchRequest($batchConfig, $crmIds, $fields);\n $response = $batchConfig['api']->read($batchReadRequest);\n\n $this->validateApiResponse($response, $objectType);\n\n $results = $this->processApiResults($response);\n $this->logBatchResults($objectType, $crmIds, $results);\n\n return $results;\n } catch (\\Throwable $e) {\n $this->handleBatchError($e, $objectType, $crmIds);\n }\n }\n\n private function validateBatchSize(string $objectType, array $crmIds): void\n {\n if (count($crmIds) > 100) {\n throw new \\InvalidArgumentException(\"Batch size cannot exceed 100 {$objectType}\");\n }\n }\n\n private function createBatchConfiguration(string $objectType): array\n {\n $configurations = [\n 'deals' => [\n 'batchReadRequest' => new \\HubSpot\\Client\\Crm\\Deals\\Model\\BatchReadInputSimplePublicObjectId(),\n 'inputClass' => \\HubSpot\\Client\\Crm\\Deals\\Model\\SimplePublicObjectId::class,\n 'api' => $this->getNewInstance()->crm()->deals()->batchApi(),\n ],\n 'companies' => [\n 'batchReadRequest' => new \\HubSpot\\Client\\Crm\\Companies\\Model\\BatchReadInputSimplePublicObjectId(),\n 'inputClass' => \\HubSpot\\Client\\Crm\\Companies\\Model\\SimplePublicObjectId::class,\n 'api' => $this->getNewInstance()->crm()->companies()->batchApi(),\n ],\n 'contacts' => [\n 'batchReadRequest' => new \\HubSpot\\Client\\Crm\\Contacts\\Model\\BatchReadInputSimplePublicObjectId(),\n 'inputClass' => \\HubSpot\\Client\\Crm\\Contacts\\Model\\SimplePublicObjectId::class,\n 'api' => $this->getNewInstance()->crm()->contacts()->batchApi(),\n ],\n ];\n\n if (! isset($configurations[$objectType])) {\n throw new \\InvalidArgumentException(\"Unsupported object type: {$objectType}\");\n }\n\n return $configurations[$objectType];\n }\n\n private function prepareBatchRequest(array $batchConfig, array $crmIds, array $fields): object\n {\n $batchReadRequest = $batchConfig['batchReadRequest'];\n $inputClass = $batchConfig['inputClass'];\n\n $inputs = array_map(function ($crmId) use ($inputClass) {\n $input = new $inputClass();\n $input->setId($crmId);\n\n return $input;\n }, $crmIds);\n\n $batchReadRequest->setInputs($inputs);\n $batchReadRequest->setProperties($fields);\n\n return $batchReadRequest;\n }\n\n private function validateApiResponse($response, string $objectType): void\n {\n if (! $response) {\n throw new CrmException(\"HubSpot API returned null response for {$objectType} batch read\");\n }\n }\n\n private function processApiResults($response): array\n {\n $results = [];\n $responseResults = $response->getResults();\n\n if ($responseResults) {\n foreach ($responseResults as $object) {\n if ($object && $object->getId()) {\n $results[$object->getId()] = [\n 'id' => $object->getId(),\n 'properties' => $object->getProperties() ?: [],\n ];\n }\n }\n }\n\n return $results;\n }\n\n private function logBatchResults(string $objectType, array $crmIds, array $results): void\n {\n $this->log->info(\"[HubSpot] Batch fetched {$objectType}\", [\n 'requested_count' => count($crmIds),\n 'returned_count' => count($results),\n 'crm_ids' => $crmIds,\n ]);\n }\n\n private function handleBatchError(\\Throwable $e, string $objectType, array $crmIds): void\n {\n $errorMessage = $e->getMessage() ?: 'Unknown error';\n $errorTrace = $e->getTraceAsString() ?: 'No trace available';\n\n $this->log->error(\"[HubSpot] Failed to batch fetch {$objectType}\", [\n 'crm_ids' => $crmIds,\n 'error' => $errorMessage,\n 'trace' => $errorTrace,\n ]);\n\n throw new CrmException(\"Failed to batch fetch {$objectType}: \" . $errorMessage);\n }\n\n /**\n * Batch read multiple opportunities by their CRM IDs\n *\n * @param array<string> $crmIds Array of HubSpot deal IDs (max 100)\n * @param array<string> $fields Array of property names to fetch\n *\n * @return array<string, array> Array keyed by CRM ID with opportunity data\n */\n public function getOpportunitiesByIds(array $crmIds, array $fields): array\n {\n return $this->batchReadObjects('deals', $crmIds, $fields);\n }\n\n /**\n * Batch read multiple companies by their CRM IDs\n *\n * @param array<string> $crmIds Array of HubSpot company IDs (max 100)\n * @param array<string> $fields Array of property names to fetch\n *\n * @return array<string, array> Array keyed by CRM ID with company data\n */\n public function getCompaniesByIds(array $crmIds, array $fields): array\n {\n return $this->batchReadObjects('companies', $crmIds, $fields);\n }\n\n /**\n * Batch read multiple contacts by their CRM IDs\n *\n * @param array<string> $crmIds Array of HubSpot contact IDs (max 100)\n * @param array<string> $fields Array of property names to fetch\n *\n * @return array<string, array> Array keyed by CRM ID with contact data\n */\n public function getContactsByIds(array $crmIds, array $fields): array\n {\n return $this->batchReadObjects('contacts', $crmIds, $fields);\n }\n\n /**\n * @throws CompanyApiException\n * @throws CrmException\n */\n public function getAccountById(string $crmId, array $fields): array\n {\n try {\n $company = $this->getNewInstance()->crm()->companies()->basicApi()->getById(\n $crmId,\n implode(',', $fields),\n );\n } catch (CompanyApiException $e) {\n $this->log->info('[Hubspot] Failed to fetch account', [\n 'crm_id' => $crmId,\n 'reason' => $e->getMessage(),\n ]);\n\n throw $e;\n }\n\n if (! $company instanceof CompaniesWithAssociations) {\n throw new CrmException('Account not found');\n }\n\n return [\n 'id' => $company->getId(),\n 'properties' => $company->getProperties(),\n ];\n }\n\n /**\n * @throws ContactApiException\n * @throws CrmException\n */\n public function getContactById(string $crmId, array $fields): array\n {\n try {\n $contact = $this->getNewInstance()->crm()->contacts()->basicApi()->getById(\n $crmId,\n implode(',', $fields)\n );\n } catch (ContactApiException $e) {\n $this->log->info('[Hubspot] Failed to fetch contact', [\n 'crm_id' => $crmId,\n 'reason' => $e->getMessage(),\n ]);\n\n throw $e;\n }\n\n if (! $contact instanceof ContactsWithAssociations) {\n throw new CrmException('Contact not found');\n }\n\n return [\n 'id' => $contact->getId(),\n 'properties' => $contact->getProperties(),\n ];\n }\n\n /**\n * This is email search request that Hubspot offers as GET (more generous quota)\n */\n public function getContactByEmail(string $email, array $fields = []): array\n {\n try {\n $contact = $this->getNewInstance()->crm()->contacts()->basicApi()->getById(\n $email,\n implode(',', $fields),\n null,\n false,\n 'email'\n );\n\n return [\n 'id' => $contact->getId(),\n 'properties' => $contact->getProperties(),\n ];\n } catch (ContactApiException $e) {\n $this->log->info('[Hubspot] Failed to fetch contact', [\n 'email' => $email,\n 'reason' => $e->getMessage(),\n ]);\n\n return [];\n }\n }\n\n /**\n * @throws CrmException\n */\n public function fetchProperty(string $objectType, string $propertyId): Property\n {\n $result = $this->getNewInstance()->crm()->properties()->coreApi()->getByName($objectType, $propertyId);\n\n if (! $result instanceof Property) {\n $this->log->error('[Hubspot] Failed to fetch property', [\n 'object_type' => $objectType,\n 'property_id' => $propertyId,\n 'reason' => $result->getMessage(),\n ]);\n\n throw new CrmException('Failed to fetch property');\n }\n\n return $result;\n }\n\n /**\n * @return array<CrmFieldOption>\n */\n public function fetchPropertyOptions(string $objectType, string $propertyId): array\n {\n /** @var array<CrmFieldOption> */\n return $this->fetchProperty($objectType, $propertyId)->getOptions();\n }\n\n /**\n * @return array<array{id:string, label:string, deleted:bool}>\n */\n public function fetchCallDispositions(): array\n {\n /** @var Response $response */\n $response = $this->getInstance()->engagements()->getCallDispositions();\n\n /**\n * @var array<array{\n * id:string,\n * label:string,\n * deleted: bool\n * }>\n */\n return $response->toArray();\n }\n\n /**\n * @return array<CrmFieldOption>\n */\n public function fetchOpportunityPipelineStages(): array\n {\n $stages = [];\n $apiResponse = $this->getNewInstance()->crm()->pipelines()->pipelinesApi()->getAll('deals');\n\n if ($apiResponse instanceof Error) {\n $this->log->error('[Hubspot] Failed to fetch opportunity pipelines', [\n 'reason' => $apiResponse->getMessage(),\n ]);\n\n return [];\n }\n\n foreach ($apiResponse->getResults() as $pipeline) {\n $pipelineStages = array_map(\n static function (PipelineStage $stage) {\n return [\n 'id' => $stage->getId(),\n 'label' => $stage->getLabel(),\n ];\n },\n $pipeline->getStages()\n );\n\n $stages = array_merge($stages, $pipelineStages);\n }\n\n return $stages;\n }\n\n public function fetchOpportunityPipelines(): array\n {\n $pipelines = [];\n\n try {\n $apiResponse = $this->makeRequest('/crm/v3/pipelines/deals');\n } catch (\\Exception $e) {\n $this->log->info('[Hubspot] Failed to fetch opportunity pipelines', [\n 'reason' => $e->getMessage(),\n ]);\n\n return [];\n }\n\n $response = $apiResponse->toArray();\n\n foreach ($response['results'] as $pipeline) {\n $pipelines[] = [\n 'id' => $pipeline['id'],\n 'label' => $pipeline['label'],\n ];\n }\n\n return $pipelines;\n }\n\n /**\n * @return array<CrmFieldOption>\n */\n public function fetchMeetingOutcomeFieldOptions(Field $field): array\n {\n return $field->getCrmProviderId() === 'meetingOutcome'\n ? $this->fetchMeetingOutcomeTypes()\n : $this->fetchCallActivityTypes();\n }\n\n public function fetchMeetingOutcomeTypes(): array\n {\n return $this->extractMeetingTypeOptions(\n 'https://api.hubapi.com/crm/v3/properties/meeting/hs_meeting_outcome'\n );\n }\n\n public function fetchCallActivityTypes(): array\n {\n return $this->extractMeetingTypeOptions(\n 'https://api.hubapi.com/crm/v3/properties/call/hs_activity_type'\n );\n }\n\n private function extractMeetingTypeOptions(string $endpoint): array\n {\n /** @var Response $response */\n $response = $this->getInstance()\n ->getClient()\n ->request('GET', $endpoint);\n\n /**\n * @var array<array{\n * value: string,\n * label: string,\n * displayOrder: int\n * }> $optionData\n */\n $optionData = $response->toArray()['options'] ?? [];\n\n $options = [];\n foreach ($optionData as $item) {\n $options[] = [\n 'id' => $item['value'],\n 'value' => $item['value'],\n 'label' => $item['label'],\n 'display_order' => $item['displayOrder'],\n ];\n }\n\n return $options;\n }\n\n /**\n * @return array<CrmFieldOption>\n */\n public function fetchDispositionFieldOptions(): array\n {\n $options = [];\n\n $dispositions = $this->fetchCallDispositions();\n\n foreach ($dispositions as $disposition) {\n if ($disposition['deleted'] !== false) {\n continue;\n }\n\n $option['value'] = $disposition['id'];\n $option['id'] = $disposition['id'];\n $option['label'] = $disposition['label'];\n\n $options[] = $option;\n }\n\n return $options;\n }\n\n /**\n * @return array<CrmFieldOption>\n */\n public function fetchOpportunityFieldOptions(Field $field): array\n {\n if ($field->isStageField()) {\n return $this->fetchOpportunityPipelineStages();\n }\n\n if ($field->isPipelineField()) {\n return $this->fetchOpportunityPipelines();\n }\n\n return $this->fetchPropertyOptions('deals', $field->getCrmProviderId());\n }\n\n /**\n * @throws BadRequest\n * @throws HubspotException\n */\n public function makeRequest(string $endpoint, $method = 'GET', $payload = [], ?string $queryString = null)\n {\n $endpoint = self::BASE_URL . $endpoint;\n\n if ($method === 'GET') {\n $response = $this->getInstance()->getClient()?->request(\n method: $method,\n endpoint: $endpoint,\n query_string: $queryString\n );\n } else {\n $response = $this->getInstance()->getClient()->request($method, $endpoint, [\n 'json' => ($payload),\n ]);\n }\n\n $max = $response->getHeaderLine('X-HubSpot-RateLimit-Max'); // \"110\"\n $remaining = $response->getHeaderLine('X-HubSpot-RateLimit-Remaining'); // \"109\"\n $interval = $response->getHeaderLine('X-HubSpot-RateLimit-Interval-Milliseconds'); // \"10000\"\n $body = json_decode((string) $response->getBody(), true);\n\n \\Illuminate\\Support\\Facades\\Log::channel('custom_channel')->info('$max ' . PHP_EOL . print_r($max, true));\n \\Illuminate\\Support\\Facades\\Log::channel('custom_channel')->info('$remaining ' . PHP_EOL . print_r($remaining, true));\n \\Illuminate\\Support\\Facades\\Log::channel('custom_channel')->info('$interval ' . PHP_EOL . print_r($interval, true));\n \\Illuminate\\Support\\Facades\\Log::channel('custom_channel')->info('$body ' . PHP_EOL . print_r($body, true));\n\n return $response;\n }\n\n /**\n * @throws BadRequest\n * @throws HubspotException\n */\n public function createMeeting(array $payload): Response\n {\n $endpoint = '/crm/v3/objects/meetings';\n\n return $this->makeRequest($endpoint, 'POST', $payload);\n }\n\n /**\n * @throws BadRequest\n * @throws HubspotException\n */\n public function updateMeeting(string $meetingId, array $payload): Response\n {\n $endpoint = '/crm/v3/objects/meetings/' . $meetingId;\n\n return $this->makeRequest($endpoint, 'PATCH', $payload);\n }\n\n /**\n * @throws \\Exception\n */\n public function createNote(\n string $body,\n string $ownerId,\n int $timestamp,\n string $objectId,\n NoteObject $noteObject\n ): ?string {\n try {\n $noteInput = new SimplePublicObjectInput([\n 'properties' => [\n 'hs_note_body' => $body,\n 'hubspot_owner_id' => $ownerId,\n 'hs_timestamp' => $timestamp,\n ],\n ]);\n\n // Create note\n $note = $this->getNewInstance()->crm()->objects()->basicApi()->create('note', $noteInput);\n\n $this->getNewInstance()->crm()->objects()->associationsApi()->create(\n 'note',\n $note->getId(),\n $this->getNoteObject($noteObject),\n $objectId,\n $this->getNoteAssociationType($noteObject),\n );\n\n return $note->getId();\n } catch (\\Exception $e) {\n $this->log->error('[Hubspot] Failed to create note', [\n 'objectId' => $objectId,\n 'noteObject' => $noteObject->getObjectType(),\n 'reason' => $e->getMessage(),\n ]);\n\n \\Sentry::captureException($e);\n }\n\n return null;\n }\n\n public function updateEngagement(string $objectId, array $engagement, array $metadata): void\n {\n $this->getInstance()->engagements()->update($objectId, $engagement, $metadata);\n }\n\n public function getEngagementData(string $engagementId): array\n {\n $engagement = $this->getInstance()->engagements()->get($engagementId);\n\n return $engagement->toArray();\n }\n\n public function createEngagement(array $engagement, array $associations, array $metadata): Response\n {\n return $this->getInstance()\n ->engagements()\n ->create($engagement, $associations, $metadata);\n }\n\n public function isUnauthorizedException(\\Exception $e): bool\n {\n // Check for specific HubSpot API exception types first\n if ($e instanceof BadRequest) {\n // BadRequest can contain 401 status codes\n return $e->getCode() === 401;\n }\n\n // Check for HTTP client exceptions with status codes\n if ($e instanceof \\GuzzleHttp\\Exception\\RequestException && $e->hasResponse()) {\n $response = $e->getResponse();\n if ($response !== null) {\n return $response->getStatusCode() === 401;\n }\n }\n\n // Check for Guzzle HTTP exceptions\n if ($e instanceof \\GuzzleHttp\\Exception\\ClientException) {\n return $e->getCode() === 401;\n }\n\n // Fallback to string matching as last resort, but be more specific\n $message = strtolower($e->getMessage());\n\n return str_contains($message, '401 unauthorized') ||\n str_contains($message, 'http 401') ||\n str_contains($message, 'status code 401') ||\n (preg_match('/\\b401\\b/', $message) && str_contains($message, 'unauthorized'));\n }\n\n /**\n * Validates and refreshes the access token if needed before API requests.\n * This ensures long-running processes don't fail due to token expiration.\n *\n * @throws SocialAccountTokenInvalidException\n */\n public function ensureValidToken(): void\n {\n if ($this->oauthAccount === null) {\n return;\n }\n\n $newToken = $this->tokenManager->ensureValidToken($this->oauthAccount);\n if ($newToken !== null) {\n $this->accessToken = $newToken;\n }\n }\n\n public function getConfig()\n {\n return $this->config;\n }\n\n // returns only active (archived=false)\n public function getOwners(): array\n {\n return $this->getNewInstance()->crm()->owners()->getAll();\n }\n\n /**\n * @param bool $archived\n *\n * @return array<Owner>|[]\n */\n public function getOwnersArchived(bool $archived = true): array\n {\n $endpoint = '/crm/v3/owners';\n $queryParams = [\n 'archived' => $archived ? 'true' : 'false',\n ];\n $queryString = http_build_query($queryParams);\n\n $owners = [];\n\n try {\n $response = $this->makeRequest(endpoint: $endpoint, queryString: $queryString);\n $responseData = $response?->toArray();\n\n foreach ($responseData['results'] as $result) {\n try {\n $owners[] = Owner::create($result);\n } catch (Throwable $e) {\n $this->log->error('[HubSpot] Failed to process owner data', [\n 'result' => $result,\n 'error' => $e->getMessage(),\n ]);\n\n continue;\n }\n }\n } catch (Throwable $e) {\n $this->log->error('HubSpot] Failed to fetch owners', [\n 'archived' => $archived,\n 'error' => $e->getMessage(),\n ]);\n\n return [];\n }\n\n return $owners;\n }\n\n public function getMeeting(string $engagementId): ObjectWithAssociations\n {\n return $this->getNewInstance()->crm()->objects()->basicApi()\n ->getById('meeting', $engagementId, null, 'contact,company,deal');\n }\n\n public function deleteEngagement(string $engagementId): void\n {\n $this->getInstance()->engagements()->delete((int) $engagementId);\n }\n\n public function getAssociationsData(array $ids, string $fromObject, string $toObject): array\n {\n $associationData = [];\n $idChunks = array_chunk($ids, self::ASSOCIATIONS_BATCH_SIZE_LIMIT);\n\n foreach ($idChunks as $idChunk) {\n try {\n $batchInput = new \\HubSpot\\Client\\Crm\\Associations\\Model\\BatchInputPublicObjectId();\n $batchInput->setInputs(array_map(function ($id) {\n $publicObjectId = new \\HubSpot\\Client\\Crm\\Associations\\Model\\PublicObjectId();\n $publicObjectId->setId($id);\n\n return $publicObjectId;\n }, $idChunk));\n\n $associatedObjectsData = $this\n ->getNewInstance()\n ->crm()\n ->associations()\n ->batchApi()\n ->read($fromObject, $toObject, $batchInput);\n\n if ($associatedObjectsData instanceof \\HubSpot\\Client\\Crm\\Associations\\Model\\BatchResponsePublicAssociationMulti) {\n foreach ($associatedObjectsData->getResults() as $association) {\n $from = $association->getFrom()->getId();\n $toAssociations = $association->getTo();\n\n if (! empty($toAssociations)) {\n $associationData[$from] = array_map(function ($item) {\n return $item->getId();\n }, $toAssociations);\n }\n }\n }\n } catch (\\Exception $e) {\n $this->log->error('[Hubspot] Failed to fetch associations', [\n 'from_object' => $fromObject,\n 'to_object' => $toObject,\n 'reason' => $e->getMessage(),\n ]);\n }\n }\n\n return $associationData;\n }\n\n /**\n * @throws \\Exception\n */\n private function getNoteAssociationType(NoteObject $noteObject): string\n {\n return match($noteObject) {\n NoteObject::Opportunity => 'note_to_deal',\n NoteObject::Lead, NoteObject::Contact => 'note_to_contact', // or 'note_to_lead' if your portal supports it\n NoteObject::Account => 'note_to_company',\n NoteObject::Call, NoteObject::Event => throw new \\Exception('Not supported'),\n };\n }\n\n /**\n * @throws \\Exception\n */\n private function getNoteObject(NoteObject $noteObject): string\n {\n return match($noteObject) {\n NoteObject::Opportunity => 'deal',\n NoteObject::Lead, NoteObject::Contact => 'contact',\n NoteObject::Account => 'company',\n NoteObject::Call, NoteObject::Event => throw new \\Exception('Not supported'),\n };\n }\n\n public function addAssociations(string $objectType, string $associationType, array $payload): Response\n {\n $endpoint = \"/crm/v4/associations/$objectType/$associationType/batch/create\";\n\n return $this->makeRequest($endpoint, 'POST', $payload);\n }\n\n public function removeAssociations(string $objectType, string $associationType, array $payload): Response\n {\n $endpoint = \"/crm/v4/associations/$objectType/$associationType/batch/archive\";\n\n return $this->makeRequest($endpoint, 'POST', $payload);\n }\n}","depth":4,"on_screen":true,"value":"<?php\n\ndeclare(strict_types=1);\n\nnamespace Jiminny\\Services\\Crm\\Hubspot;\n\nuse HubSpot\\Client\\Crm\\Deals\\ApiException as DealApiException;\nuse HubSpot\\Client\\Crm\\Contacts\\ApiException as ContactApiException;\nuse HubSpot\\Client\\Crm\\Companies\\ApiException as CompanyApiException;\nuse HubSpot\\Client\\Crm\\Contacts\\Model\\SimplePublicObjectWithAssociations as ContactsWithAssociations;\nuse HubSpot\\Client\\Crm\\Companies\\Model\\SimplePublicObjectWithAssociations as CompaniesWithAssociations;\nuse HubSpot\\Client\\Crm\\Deals\\Model\\SimplePublicObjectWithAssociations as DealWithAssociations;\nuse HubSpot\\Client\\Crm\\Objects\\Model\\SimplePublicObjectInput;\nuse HubSpot\\Client\\Crm\\Objects\\Model\\SimplePublicObjectWithAssociations as ObjectWithAssociations;\nuse HubSpot\\Client\\Crm\\Pipelines\\Model\\Error;\nuse HubSpot\\Client\\Crm\\Pipelines\\Model\\PipelineStage;\nuse HubSpot\\Client\\Crm\\Properties\\Model\\Property;\nuse HubSpot\\Discovery\\Discovery;\nuse Jiminny\\Component\\Utility\\Service\\ProviderRateLimiter;\nuse Jiminny\\Exceptions\\CrmException;\nuse Jiminny\\Exceptions\\RateLimitException;\nuse Jiminny\\Exceptions\\SocialAccountTokenInvalidException;\nuse Jiminny\\Jobs\\Crm\\NoteObject;\nuse Jiminny\\Models\\Crm\\Field;\nuse Jiminny\\Services\\Crm\\BaseClient;\nuse Jiminny\\Services\\Crm\\Hubspot\\DTO\\Response\\Owner;\nuse Jiminny\\Services\\SocialAccountService;\nuse SevenShores\\Hubspot\\Exceptions\\BadRequest;\nuse SevenShores\\Hubspot\\Exceptions\\HubspotException;\nuse SevenShores\\Hubspot\\Factory;\nuse SevenShores\\Hubspot\\Http\\Response;\nuse Jiminny\\Services\\Crm\\Hubspot\\Pagination\\HubspotPaginationService;\nuse Throwable;\n\n/**\n * @phpstan-type CrmFieldOption array{id:string, label:string, value?:string}\n */\nclass Client extends BaseClient implements HubspotClientInterface\n{\n public const string MIN_API_VERSION = '2';\n\n public const string BASE_URL = 'https://api.hubapi.com';\n\n public const int ASSOCIATIONS_BATCH_SIZE_LIMIT = 1000;\n\n private HubspotPaginationService $paginationService;\n private HubspotTokenManager $tokenManager;\n private ProviderRateLimiter $rateLimiter;\n\n public function __construct(\n SocialAccountService $socialAccountService,\n HubspotPaginationService $paginationService,\n HubspotTokenManager $tokenManager,\n ProviderRateLimiter $rateLimiter,\n ) {\n parent::__construct($socialAccountService);\n $this->paginationService = $paginationService;\n $this->tokenManager = $tokenManager;\n $this->rateLimiter = $rateLimiter;\n\n $this->setBaseUrl(self::BASE_URL);\n $this->setVersion(self::MIN_API_VERSION);\n }\n\n /**\n * Single entry point for every HubSpot API call. Enforces the per-portal\n * rate limit configured in the rate_limits table (morphed to the current\n * Configuration) and reacts to a real 429 from HubSpot by translating it\n * into a RateLimitException carrying Retry-After.\n *\n * Wrap any outbound HubSpot call (SDK or raw HTTP) like:\n *\n * $this->executeRequest(fn () => $this->getNewInstance()->crm()->...);\n *\n * @template T\n * @param callable(): T $apiCall\n * @return T\n *\n * @throws RateLimitException\n */\n private function executeRequest(callable $apiCall)\n {\n if (! $this->rateLimiter->canMakeRequest($this->config)) {\n $retryAfter = $this->rateLimiter->requestAvailableIn($this->config);\n\n $this->log->warning('[Hubspot] Rate limit exceeded, deferring request', [\n 'team_id' => $this->config->team_id,\n 'config_id' => $this->config->getId(),\n 'retry_after' => $retryAfter,\n ]);\n\n throw new RateLimitException(\n 'Hubspot rate limit reached for configuration ' . $this->config->getId(),\n $retryAfter,\n );\n }\n\n $this->rateLimiter->incrementRequestCount($this->config);\n\n try {\n return $apiCall();\n } catch (Throwable $e) {\n if ($this->isHubspotRateLimit($e)) {\n $retryAfter = $this->parseRetryAfter($e);\n\n $this->log->warning('[Hubspot] Received 429 from API', [\n 'team_id' => $this->config->team_id,\n 'config_id' => $this->config->getId(),\n 'retry_after' => $retryAfter,\n 'reason' => $e->getMessage(),\n ]);\n\n throw new RateLimitException('Hubspot returned 429', $retryAfter, $e);\n }\n\n throw $e;\n }\n }\n\n private function isHubspotRateLimit(Throwable $e): bool\n {\n return method_exists($e, 'getCode') && (int) $e->getCode() === 429;\n }\n\n private function parseRetryAfter(Throwable $e): int\n {\n if (method_exists($e, 'getResponseHeaders')) {\n $headers = $e->getResponseHeaders() ?: [];\n $value = $headers['Retry-After'] ?? $headers['retry-after'] ?? null;\n if (is_array($value)) {\n $value = $value[0] ?? null;\n }\n if (is_numeric($value)) {\n return (int) $value;\n }\n }\n\n return 10;\n }\n\n public function getMinimumApiVersion(): string\n {\n return self::MIN_API_VERSION;\n }\n\n public function getInstance(): Factory\n {\n return new Factory([\n 'key' => $this->accessToken,\n 'oauth2' => true,\n 'base_url' => $this->baseUrl,\n ]);\n }\n\n public function getNewInstance(): Discovery\n {\n return \\HubSpot\\Factory::createWithAccessToken($this->accessToken);\n }\n\n /**\n * Secondly and daily limits for Hubspot API\n *\n * Product Tier: Free & Starter | Professional & Enterprise | API add-on (any tier)\n * Burst: 100/10 seconds | 150/10 seconds | 200/10 seconds\n * Daily: 250,000 | 500,000 | 1,000,000\n *\n * Official documentation states: The search endpoints are rate limited to five requests per second.\n * Since with 5 RPS were still hitting secondly rate limits we lowered it to 4\n */\n public function getPaginatedData(array $payload, string $type, int $offset = 0): array\n {\n $total = 0;\n $lastId = null;\n $rows = [];\n foreach ($this->getPaginatedDataGenerator($payload, $type, $offset, $total, $lastId) as $row) {\n $rows[] = $row;\n }\n\n return ['results' => $rows, 'total' => $total, 'last_record' => $lastId];\n }\n\n /**\n * @throws HubspotException\n * @throws SocialAccountTokenInvalidException\n * @throws BadRequest\n */\n public function getPaginatedDataGenerator(\n array $payload,\n string $type,\n int $offset = 0,\n int &$total = 0,\n ?string &$lastRecordId = null\n ): \\Generator {\n return $this->paginationService->getPaginatedDataGenerator(\n $this,\n $payload,\n $type,\n $offset,\n $total,\n $lastRecordId\n );\n }\n\n /**\n * @throws DealApiException\n * @throws CrmException\n */\n public function getOpportunityById(string $crmId, array $fields): array\n {\n try {\n $deal = $this->executeRequest(fn () => $this->getNewInstance()->crm()->deals()->basicApi()->getById(\n $crmId,\n implode(',', $fields),\n 'companies,contacts'\n ));\n } catch (DealApiException $e) {\n $this->log->info('[Hubspot] Failed to fetch opportunity', [\n 'crm_id' => $crmId,\n 'reason' => $e->getMessage(),\n ]);\n\n throw $e;\n }\n\n if (! $deal instanceof DealWithAssociations) {\n throw new CrmException('Deal not found');\n }\n\n return [\n 'id' => $deal->getId(),\n 'properties' => $deal->getProperties(),\n 'associations' => $deal->getAssociations(),\n ];\n }\n\n /**\n * Generic batch read method for HubSpot objects\n *\n * @param string $objectType The object type ('deals', 'companies', 'contacts')\n * @param array<string> $crmIds Array of HubSpot object IDs (max 100)\n * @param array<string> $fields Array of property names to fetch\n *\n * @return array<string, array> Array keyed by CRM ID with object data\n */\n private function batchReadObjects(string $objectType, array $crmIds, array $fields): array\n {\n if (empty($crmIds)) {\n return [];\n }\n\n $this->validateBatchSize($objectType, $crmIds);\n $this->ensureValidToken();\n\n try {\n $batchConfig = $this->createBatchConfiguration($objectType);\n $batchReadRequest = $this->prepareBatchRequest($batchConfig, $crmIds, $fields);\n $response = $batchConfig['api']->read($batchReadRequest);\n\n $this->validateApiResponse($response, $objectType);\n\n $results = $this->processApiResults($response);\n $this->logBatchResults($objectType, $crmIds, $results);\n\n return $results;\n } catch (\\Throwable $e) {\n $this->handleBatchError($e, $objectType, $crmIds);\n }\n }\n\n private function validateBatchSize(string $objectType, array $crmIds): void\n {\n if (count($crmIds) > 100) {\n throw new \\InvalidArgumentException(\"Batch size cannot exceed 100 {$objectType}\");\n }\n }\n\n private function createBatchConfiguration(string $objectType): array\n {\n $configurations = [\n 'deals' => [\n 'batchReadRequest' => new \\HubSpot\\Client\\Crm\\Deals\\Model\\BatchReadInputSimplePublicObjectId(),\n 'inputClass' => \\HubSpot\\Client\\Crm\\Deals\\Model\\SimplePublicObjectId::class,\n 'api' => $this->getNewInstance()->crm()->deals()->batchApi(),\n ],\n 'companies' => [\n 'batchReadRequest' => new \\HubSpot\\Client\\Crm\\Companies\\Model\\BatchReadInputSimplePublicObjectId(),\n 'inputClass' => \\HubSpot\\Client\\Crm\\Companies\\Model\\SimplePublicObjectId::class,\n 'api' => $this->getNewInstance()->crm()->companies()->batchApi(),\n ],\n 'contacts' => [\n 'batchReadRequest' => new \\HubSpot\\Client\\Crm\\Contacts\\Model\\BatchReadInputSimplePublicObjectId(),\n 'inputClass' => \\HubSpot\\Client\\Crm\\Contacts\\Model\\SimplePublicObjectId::class,\n 'api' => $this->getNewInstance()->crm()->contacts()->batchApi(),\n ],\n ];\n\n if (! isset($configurations[$objectType])) {\n throw new \\InvalidArgumentException(\"Unsupported object type: {$objectType}\");\n }\n\n return $configurations[$objectType];\n }\n\n private function prepareBatchRequest(array $batchConfig, array $crmIds, array $fields): object\n {\n $batchReadRequest = $batchConfig['batchReadRequest'];\n $inputClass = $batchConfig['inputClass'];\n\n $inputs = array_map(function ($crmId) use ($inputClass) {\n $input = new $inputClass();\n $input->setId($crmId);\n\n return $input;\n }, $crmIds);\n\n $batchReadRequest->setInputs($inputs);\n $batchReadRequest->setProperties($fields);\n\n return $batchReadRequest;\n }\n\n private function validateApiResponse($response, string $objectType): void\n {\n if (! $response) {\n throw new CrmException(\"HubSpot API returned null response for {$objectType} batch read\");\n }\n }\n\n private function processApiResults($response): array\n {\n $results = [];\n $responseResults = $response->getResults();\n\n if ($responseResults) {\n foreach ($responseResults as $object) {\n if ($object && $object->getId()) {\n $results[$object->getId()] = [\n 'id' => $object->getId(),\n 'properties' => $object->getProperties() ?: [],\n ];\n }\n }\n }\n\n return $results;\n }\n\n private function logBatchResults(string $objectType, array $crmIds, array $results): void\n {\n $this->log->info(\"[HubSpot] Batch fetched {$objectType}\", [\n 'requested_count' => count($crmIds),\n 'returned_count' => count($results),\n 'crm_ids' => $crmIds,\n ]);\n }\n\n private function handleBatchError(\\Throwable $e, string $objectType, array $crmIds): void\n {\n $errorMessage = $e->getMessage() ?: 'Unknown error';\n $errorTrace = $e->getTraceAsString() ?: 'No trace available';\n\n $this->log->error(\"[HubSpot] Failed to batch fetch {$objectType}\", [\n 'crm_ids' => $crmIds,\n 'error' => $errorMessage,\n 'trace' => $errorTrace,\n ]);\n\n throw new CrmException(\"Failed to batch fetch {$objectType}: \" . $errorMessage);\n }\n\n /**\n * Batch read multiple opportunities by their CRM IDs\n *\n * @param array<string> $crmIds Array of HubSpot deal IDs (max 100)\n * @param array<string> $fields Array of property names to fetch\n *\n * @return array<string, array> Array keyed by CRM ID with opportunity data\n */\n public function getOpportunitiesByIds(array $crmIds, array $fields): array\n {\n return $this->batchReadObjects('deals', $crmIds, $fields);\n }\n\n /**\n * Batch read multiple companies by their CRM IDs\n *\n * @param array<string> $crmIds Array of HubSpot company IDs (max 100)\n * @param array<string> $fields Array of property names to fetch\n *\n * @return array<string, array> Array keyed by CRM ID with company data\n */\n public function getCompaniesByIds(array $crmIds, array $fields): array\n {\n return $this->batchReadObjects('companies', $crmIds, $fields);\n }\n\n /**\n * Batch read multiple contacts by their CRM IDs\n *\n * @param array<string> $crmIds Array of HubSpot contact IDs (max 100)\n * @param array<string> $fields Array of property names to fetch\n *\n * @return array<string, array> Array keyed by CRM ID with contact data\n */\n public function getContactsByIds(array $crmIds, array $fields): array\n {\n return $this->batchReadObjects('contacts', $crmIds, $fields);\n }\n\n /**\n * @throws CompanyApiException\n * @throws CrmException\n */\n public function getAccountById(string $crmId, array $fields): array\n {\n try {\n $company = $this->getNewInstance()->crm()->companies()->basicApi()->getById(\n $crmId,\n implode(',', $fields),\n );\n } catch (CompanyApiException $e) {\n $this->log->info('[Hubspot] Failed to fetch account', [\n 'crm_id' => $crmId,\n 'reason' => $e->getMessage(),\n ]);\n\n throw $e;\n }\n\n if (! $company instanceof CompaniesWithAssociations) {\n throw new CrmException('Account not found');\n }\n\n return [\n 'id' => $company->getId(),\n 'properties' => $company->getProperties(),\n ];\n }\n\n /**\n * @throws ContactApiException\n * @throws CrmException\n */\n public function getContactById(string $crmId, array $fields): array\n {\n try {\n $contact = $this->getNewInstance()->crm()->contacts()->basicApi()->getById(\n $crmId,\n implode(',', $fields)\n );\n } catch (ContactApiException $e) {\n $this->log->info('[Hubspot] Failed to fetch contact', [\n 'crm_id' => $crmId,\n 'reason' => $e->getMessage(),\n ]);\n\n throw $e;\n }\n\n if (! $contact instanceof ContactsWithAssociations) {\n throw new CrmException('Contact not found');\n }\n\n return [\n 'id' => $contact->getId(),\n 'properties' => $contact->getProperties(),\n ];\n }\n\n /**\n * This is email search request that Hubspot offers as GET (more generous quota)\n */\n public function getContactByEmail(string $email, array $fields = []): array\n {\n try {\n $contact = $this->getNewInstance()->crm()->contacts()->basicApi()->getById(\n $email,\n implode(',', $fields),\n null,\n false,\n 'email'\n );\n\n return [\n 'id' => $contact->getId(),\n 'properties' => $contact->getProperties(),\n ];\n } catch (ContactApiException $e) {\n $this->log->info('[Hubspot] Failed to fetch contact', [\n 'email' => $email,\n 'reason' => $e->getMessage(),\n ]);\n\n return [];\n }\n }\n\n /**\n * @throws CrmException\n */\n public function fetchProperty(string $objectType, string $propertyId): Property\n {\n $result = $this->getNewInstance()->crm()->properties()->coreApi()->getByName($objectType, $propertyId);\n\n if (! $result instanceof Property) {\n $this->log->error('[Hubspot] Failed to fetch property', [\n 'object_type' => $objectType,\n 'property_id' => $propertyId,\n 'reason' => $result->getMessage(),\n ]);\n\n throw new CrmException('Failed to fetch property');\n }\n\n return $result;\n }\n\n /**\n * @return array<CrmFieldOption>\n */\n public function fetchPropertyOptions(string $objectType, string $propertyId): array\n {\n /** @var array<CrmFieldOption> */\n return $this->fetchProperty($objectType, $propertyId)->getOptions();\n }\n\n /**\n * @return array<array{id:string, label:string, deleted:bool}>\n */\n public function fetchCallDispositions(): array\n {\n /** @var Response $response */\n $response = $this->getInstance()->engagements()->getCallDispositions();\n\n /**\n * @var array<array{\n * id:string,\n * label:string,\n * deleted: bool\n * }>\n */\n return $response->toArray();\n }\n\n /**\n * @return array<CrmFieldOption>\n */\n public function fetchOpportunityPipelineStages(): array\n {\n $stages = [];\n $apiResponse = $this->getNewInstance()->crm()->pipelines()->pipelinesApi()->getAll('deals');\n\n if ($apiResponse instanceof Error) {\n $this->log->error('[Hubspot] Failed to fetch opportunity pipelines', [\n 'reason' => $apiResponse->getMessage(),\n ]);\n\n return [];\n }\n\n foreach ($apiResponse->getResults() as $pipeline) {\n $pipelineStages = array_map(\n static function (PipelineStage $stage) {\n return [\n 'id' => $stage->getId(),\n 'label' => $stage->getLabel(),\n ];\n },\n $pipeline->getStages()\n );\n\n $stages = array_merge($stages, $pipelineStages);\n }\n\n return $stages;\n }\n\n public function fetchOpportunityPipelines(): array\n {\n $pipelines = [];\n\n try {\n $apiResponse = $this->makeRequest('/crm/v3/pipelines/deals');\n } catch (\\Exception $e) {\n $this->log->info('[Hubspot] Failed to fetch opportunity pipelines', [\n 'reason' => $e->getMessage(),\n ]);\n\n return [];\n }\n\n $response = $apiResponse->toArray();\n\n foreach ($response['results'] as $pipeline) {\n $pipelines[] = [\n 'id' => $pipeline['id'],\n 'label' => $pipeline['label'],\n ];\n }\n\n return $pipelines;\n }\n\n /**\n * @return array<CrmFieldOption>\n */\n public function fetchMeetingOutcomeFieldOptions(Field $field): array\n {\n return $field->getCrmProviderId() === 'meetingOutcome'\n ? $this->fetchMeetingOutcomeTypes()\n : $this->fetchCallActivityTypes();\n }\n\n public function fetchMeetingOutcomeTypes(): array\n {\n return $this->extractMeetingTypeOptions(\n 'https://api.hubapi.com/crm/v3/properties/meeting/hs_meeting_outcome'\n );\n }\n\n public function fetchCallActivityTypes(): array\n {\n return $this->extractMeetingTypeOptions(\n 'https://api.hubapi.com/crm/v3/properties/call/hs_activity_type'\n );\n }\n\n private function extractMeetingTypeOptions(string $endpoint): array\n {\n /** @var Response $response */\n $response = $this->getInstance()\n ->getClient()\n ->request('GET', $endpoint);\n\n /**\n * @var array<array{\n * value: string,\n * label: string,\n * displayOrder: int\n * }> $optionData\n */\n $optionData = $response->toArray()['options'] ?? [];\n\n $options = [];\n foreach ($optionData as $item) {\n $options[] = [\n 'id' => $item['value'],\n 'value' => $item['value'],\n 'label' => $item['label'],\n 'display_order' => $item['displayOrder'],\n ];\n }\n\n return $options;\n }\n\n /**\n * @return array<CrmFieldOption>\n */\n public function fetchDispositionFieldOptions(): array\n {\n $options = [];\n\n $dispositions = $this->fetchCallDispositions();\n\n foreach ($dispositions as $disposition) {\n if ($disposition['deleted'] !== false) {\n continue;\n }\n\n $option['value'] = $disposition['id'];\n $option['id'] = $disposition['id'];\n $option['label'] = $disposition['label'];\n\n $options[] = $option;\n }\n\n return $options;\n }\n\n /**\n * @return array<CrmFieldOption>\n */\n public function fetchOpportunityFieldOptions(Field $field): array\n {\n if ($field->isStageField()) {\n return $this->fetchOpportunityPipelineStages();\n }\n\n if ($field->isPipelineField()) {\n return $this->fetchOpportunityPipelines();\n }\n\n return $this->fetchPropertyOptions('deals', $field->getCrmProviderId());\n }\n\n /**\n * @throws BadRequest\n * @throws HubspotException\n */\n public function makeRequest(string $endpoint, $method = 'GET', $payload = [], ?string $queryString = null)\n {\n $endpoint = self::BASE_URL . $endpoint;\n\n if ($method === 'GET') {\n $response = $this->getInstance()->getClient()?->request(\n method: $method,\n endpoint: $endpoint,\n query_string: $queryString\n );\n } else {\n $response = $this->getInstance()->getClient()->request($method, $endpoint, [\n 'json' => ($payload),\n ]);\n }\n\n $max = $response->getHeaderLine('X-HubSpot-RateLimit-Max'); // \"110\"\n $remaining = $response->getHeaderLine('X-HubSpot-RateLimit-Remaining'); // \"109\"\n $interval = $response->getHeaderLine('X-HubSpot-RateLimit-Interval-Milliseconds'); // \"10000\"\n $body = json_decode((string) $response->getBody(), true);\n\n \\Illuminate\\Support\\Facades\\Log::channel('custom_channel')->info('$max ' . PHP_EOL . print_r($max, true));\n \\Illuminate\\Support\\Facades\\Log::channel('custom_channel')->info('$remaining ' . PHP_EOL . print_r($remaining, true));\n \\Illuminate\\Support\\Facades\\Log::channel('custom_channel')->info('$interval ' . PHP_EOL . print_r($interval, true));\n \\Illuminate\\Support\\Facades\\Log::channel('custom_channel')->info('$body ' . PHP_EOL . print_r($body, true));\n\n return $response;\n }\n\n /**\n * @throws BadRequest\n * @throws HubspotException\n */\n public function createMeeting(array $payload): Response\n {\n $endpoint = '/crm/v3/objects/meetings';\n\n return $this->makeRequest($endpoint, 'POST', $payload);\n }\n\n /**\n * @throws BadRequest\n * @throws HubspotException\n */\n public function updateMeeting(string $meetingId, array $payload): Response\n {\n $endpoint = '/crm/v3/objects/meetings/' . $meetingId;\n\n return $this->makeRequest($endpoint, 'PATCH', $payload);\n }\n\n /**\n * @throws \\Exception\n */\n public function createNote(\n string $body,\n string $ownerId,\n int $timestamp,\n string $objectId,\n NoteObject $noteObject\n ): ?string {\n try {\n $noteInput = new SimplePublicObjectInput([\n 'properties' => [\n 'hs_note_body' => $body,\n 'hubspot_owner_id' => $ownerId,\n 'hs_timestamp' => $timestamp,\n ],\n ]);\n\n // Create note\n $note = $this->getNewInstance()->crm()->objects()->basicApi()->create('note', $noteInput);\n\n $this->getNewInstance()->crm()->objects()->associationsApi()->create(\n 'note',\n $note->getId(),\n $this->getNoteObject($noteObject),\n $objectId,\n $this->getNoteAssociationType($noteObject),\n );\n\n return $note->getId();\n } catch (\\Exception $e) {\n $this->log->error('[Hubspot] Failed to create note', [\n 'objectId' => $objectId,\n 'noteObject' => $noteObject->getObjectType(),\n 'reason' => $e->getMessage(),\n ]);\n\n \\Sentry::captureException($e);\n }\n\n return null;\n }\n\n public function updateEngagement(string $objectId, array $engagement, array $metadata): void\n {\n $this->getInstance()->engagements()->update($objectId, $engagement, $metadata);\n }\n\n public function getEngagementData(string $engagementId): array\n {\n $engagement = $this->getInstance()->engagements()->get($engagementId);\n\n return $engagement->toArray();\n }\n\n public function createEngagement(array $engagement, array $associations, array $metadata): Response\n {\n return $this->getInstance()\n ->engagements()\n ->create($engagement, $associations, $metadata);\n }\n\n public function isUnauthorizedException(\\Exception $e): bool\n {\n // Check for specific HubSpot API exception types first\n if ($e instanceof BadRequest) {\n // BadRequest can contain 401 status codes\n return $e->getCode() === 401;\n }\n\n // Check for HTTP client exceptions with status codes\n if ($e instanceof \\GuzzleHttp\\Exception\\RequestException && $e->hasResponse()) {\n $response = $e->getResponse();\n if ($response !== null) {\n return $response->getStatusCode() === 401;\n }\n }\n\n // Check for Guzzle HTTP exceptions\n if ($e instanceof \\GuzzleHttp\\Exception\\ClientException) {\n return $e->getCode() === 401;\n }\n\n // Fallback to string matching as last resort, but be more specific\n $message = strtolower($e->getMessage());\n\n return str_contains($message, '401 unauthorized') ||\n str_contains($message, 'http 401') ||\n str_contains($message, 'status code 401') ||\n (preg_match('/\\b401\\b/', $message) && str_contains($message, 'unauthorized'));\n }\n\n /**\n * Validates and refreshes the access token if needed before API requests.\n * This ensures long-running processes don't fail due to token expiration.\n *\n * @throws SocialAccountTokenInvalidException\n */\n public function ensureValidToken(): void\n {\n if ($this->oauthAccount === null) {\n return;\n }\n\n $newToken = $this->tokenManager->ensureValidToken($this->oauthAccount);\n if ($newToken !== null) {\n $this->accessToken = $newToken;\n }\n }\n\n public function getConfig()\n {\n return $this->config;\n }\n\n // returns only active (archived=false)\n public function getOwners(): array\n {\n return $this->getNewInstance()->crm()->owners()->getAll();\n }\n\n /**\n * @param bool $archived\n *\n * @return array<Owner>|[]\n */\n public function getOwnersArchived(bool $archived = true): array\n {\n $endpoint = '/crm/v3/owners';\n $queryParams = [\n 'archived' => $archived ? 'true' : 'false',\n ];\n $queryString = http_build_query($queryParams);\n\n $owners = [];\n\n try {\n $response = $this->makeRequest(endpoint: $endpoint, queryString: $queryString);\n $responseData = $response?->toArray();\n\n foreach ($responseData['results'] as $result) {\n try {\n $owners[] = Owner::create($result);\n } catch (Throwable $e) {\n $this->log->error('[HubSpot] Failed to process owner data', [\n 'result' => $result,\n 'error' => $e->getMessage(),\n ]);\n\n continue;\n }\n }\n } catch (Throwable $e) {\n $this->log->error('HubSpot] Failed to fetch owners', [\n 'archived' => $archived,\n 'error' => $e->getMessage(),\n ]);\n\n return [];\n }\n\n return $owners;\n }\n\n public function getMeeting(string $engagementId): ObjectWithAssociations\n {\n return $this->getNewInstance()->crm()->objects()->basicApi()\n ->getById('meeting', $engagementId, null, 'contact,company,deal');\n }\n\n public function deleteEngagement(string $engagementId): void\n {\n $this->getInstance()->engagements()->delete((int) $engagementId);\n }\n\n public function getAssociationsData(array $ids, string $fromObject, string $toObject): array\n {\n $associationData = [];\n $idChunks = array_chunk($ids, self::ASSOCIATIONS_BATCH_SIZE_LIMIT);\n\n foreach ($idChunks as $idChunk) {\n try {\n $batchInput = new \\HubSpot\\Client\\Crm\\Associations\\Model\\BatchInputPublicObjectId();\n $batchInput->setInputs(array_map(function ($id) {\n $publicObjectId = new \\HubSpot\\Client\\Crm\\Associations\\Model\\PublicObjectId();\n $publicObjectId->setId($id);\n\n return $publicObjectId;\n }, $idChunk));\n\n $associatedObjectsData = $this\n ->getNewInstance()\n ->crm()\n ->associations()\n ->batchApi()\n ->read($fromObject, $toObject, $batchInput);\n\n if ($associatedObjectsData instanceof \\HubSpot\\Client\\Crm\\Associations\\Model\\BatchResponsePublicAssociationMulti) {\n foreach ($associatedObjectsData->getResults() as $association) {\n $from = $association->getFrom()->getId();\n $toAssociations = $association->getTo();\n\n if (! empty($toAssociations)) {\n $associationData[$from] = array_map(function ($item) {\n return $item->getId();\n }, $toAssociations);\n }\n }\n }\n } catch (\\Exception $e) {\n $this->log->error('[Hubspot] Failed to fetch associations', [\n 'from_object' => $fromObject,\n 'to_object' => $toObject,\n 'reason' => $e->getMessage(),\n ]);\n }\n }\n\n return $associationData;\n }\n\n /**\n * @throws \\Exception\n */\n private function getNoteAssociationType(NoteObject $noteObject): string\n {\n return match($noteObject) {\n NoteObject::Opportunity => 'note_to_deal',\n NoteObject::Lead, NoteObject::Contact => 'note_to_contact', // or 'note_to_lead' if your portal supports it\n NoteObject::Account => 'note_to_company',\n NoteObject::Call, NoteObject::Event => throw new \\Exception('Not supported'),\n };\n }\n\n /**\n * @throws \\Exception\n */\n private function getNoteObject(NoteObject $noteObject): string\n {\n return match($noteObject) {\n NoteObject::Opportunity => 'deal',\n NoteObject::Lead, NoteObject::Contact => 'contact',\n NoteObject::Account => 'company',\n NoteObject::Call, NoteObject::Event => throw new \\Exception('Not supported'),\n };\n }\n\n public function addAssociations(string $objectType, string $associationType, array $payload): Response\n {\n $endpoint = \"/crm/v4/associations/$objectType/$associationType/batch/create\";\n\n return $this->makeRequest($endpoint, 'POST', $payload);\n }\n\n public function removeAssociations(string $objectType, string $associationType, array $payload): Response\n {\n $endpoint = \"/crm/v4/associations/$objectType/$associationType/batch/archive\";\n\n return $this->makeRequest($endpoint, 'POST', $payload);\n }\n}","role_description":"text entry area","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"Project","depth":3,"on_screen":false,"role_description":"text"},{"role":"AXButton","text":"Project","depth":3,"bounds":{"left":0.011968086,"top":0.047885075,"width":0.024268618,"height":0.024740623},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"New File or Directory…","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.008643617,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Expand Selected","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.008643617,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Collapse All","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.008643617,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Options","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.008643617,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Hide","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.008643617,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false}]...
|
291277805985683123
|
6378757184196315236
|
visual_change
|
accessibility
|
NULL
|
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
2
60
Previous Highlighted Error
Next Highlighted Error
<?php
declare(strict_types=1);
namespace Jiminny\Services\Crm\Hubspot;
use HubSpot\Client\Crm\Deals\ApiException as DealApiException;
use HubSpot\Client\Crm\Contacts\ApiException as ContactApiException;
use HubSpot\Client\Crm\Companies\ApiException as CompanyApiException;
use HubSpot\Client\Crm\Contacts\Model\SimplePublicObjectWithAssociations as ContactsWithAssociations;
use HubSpot\Client\Crm\Companies\Model\SimplePublicObjectWithAssociations as CompaniesWithAssociations;
use HubSpot\Client\Crm\Deals\Model\SimplePublicObjectWithAssociations as DealWithAssociations;
use HubSpot\Client\Crm\Objects\Model\SimplePublicObjectInput;
use HubSpot\Client\Crm\Objects\Model\SimplePublicObjectWithAssociations as ObjectWithAssociations;
use HubSpot\Client\Crm\Pipelines\Model\Error;
use HubSpot\Client\Crm\Pipelines\Model\PipelineStage;
use HubSpot\Client\Crm\Properties\Model\Property;
use HubSpot\Discovery\Discovery;
use Jiminny\Component\Utility\Service\ProviderRateLimiter;
use Jiminny\Exceptions\CrmException;
use Jiminny\Exceptions\RateLimitException;
use Jiminny\Exceptions\SocialAccountTokenInvalidException;
use Jiminny\Jobs\Crm\NoteObject;
use Jiminny\Models\Crm\Field;
use Jiminny\Services\Crm\BaseClient;
use Jiminny\Services\Crm\Hubspot\DTO\Response\Owner;
use Jiminny\Services\SocialAccountService;
use SevenShores\Hubspot\Exceptions\BadRequest;
use SevenShores\Hubspot\Exceptions\HubspotException;
use SevenShores\Hubspot\Factory;
use SevenShores\Hubspot\Http\Response;
use Jiminny\Services\Crm\Hubspot\Pagination\HubspotPaginationService;
use Throwable;
/**
* @phpstan-type CrmFieldOption array{id:string, label:string, value?:string}
*/
class Client extends BaseClient implements HubspotClientInterface
{
public const string MIN_API_VERSION = '2';
public const string BASE_URL = '[URL_WITH_CREDENTIALS] T
* @param callable(): T $apiCall
* @return T
*
* @throws RateLimitException
*/
private function executeRequest(callable $apiCall)
{
if (! $this->rateLimiter->canMakeRequest($this->config)) {
$retryAfter = $this->rateLimiter->requestAvailableIn($this->config);
$this->log->warning('[Hubspot] Rate limit exceeded, deferring request', [
'team_id' => $this->config->team_id,
'config_id' => $this->config->getId(),
'retry_after' => $retryAfter,
]);
throw new RateLimitException(
'Hubspot rate limit reached for configuration ' . $this->config->getId(),
$retryAfter,
);
}
$this->rateLimiter->incrementRequestCount($this->config);
try {
return $apiCall();
} catch (Throwable $e) {
if ($this->isHubspotRateLimit($e)) {
$retryAfter = $this->parseRetryAfter($e);
$this->log->warning('[Hubspot] Received 429 from API', [
'team_id' => $this->config->team_id,
'config_id' => $this->config->getId(),
'retry_after' => $retryAfter,
'reason' => $e->getMessage(),
]);
throw new RateLimitException('Hubspot returned 429', $retryAfter, $e);
}
throw $e;
}
}
private function isHubspotRateLimit(Throwable $e): bool
{
return method_exists($e, 'getCode') && (int) $e->getCode() === 429;
}
private function parseRetryAfter(Throwable $e): int
{
if (method_exists($e, 'getResponseHeaders')) {
$headers = $e->getResponseHeaders() ?: [];
$value = $headers['Retry-After'] ?? $headers['retry-after'] ?? null;
if (is_array($value)) {
$value = $value[0] ?? null;
}
if (is_numeric($value)) {
return (int) $value;
}
}
return 10;
}
public function getMinimumApiVersion(): string
{
return self::MIN_API_VERSION;
}
public function getInstance(): Factory
{
return new Factory([
'key' => $this->accessToken,
'oauth2' => true,
'base_url' => $this->baseUrl,
]);
}
public function getNewInstance(): Discovery
{
return \HubSpot\Factory::createWithAccessToken($this->accessToken);
}
/**
* Secondly and daily limits for Hubspot API
*
* Product Tier: Free & Starter | Professional & Enterprise | API add-on (any tier)
* Burst: 100/10 seconds | 150/10 seconds | 200/10 seconds
* Daily: 250,000 | 500,000 | 1,000,000
*
* Official documentation states: The search endpoints are rate limited to five requests per second.
* Since with 5 RPS were still hitting secondly rate limits we lowered it to 4
*/
public function getPaginatedData(array $payload, string $type, int $offset = 0): array
{
$total = 0;
$lastId = null;
$rows = [];
foreach ($this->getPaginatedDataGenerator($payload, $type, $offset, $total, $lastId) as $row) {
$rows[] = $row;
}
return ['results' => $rows, 'total' => $total, 'last_record' => $lastId];
}
/**
* @throws HubspotException
* @throws SocialAccountTokenInvalidException
* @throws BadRequest
*/
public function getPaginatedDataGenerator(
array $payload,
string $type,
int $offset = 0,
int &$total = 0,
?string &$lastRecordId = null
): \Generator {
return $this->paginationService->getPaginatedDataGenerator(
$this,
$payload,
$type,
$offset,
$total,
$lastRecordId
);
}
/**
* @throws DealApiException
* @throws CrmException
*/
public function getOpportunityById(string $crmId, array $fields): array
{
try {
$deal = $this->executeRequest(fn () => $this->getNewInstance()->crm()->deals()->basicApi()->getById(
$crmId,
implode(',', $fields),
'companies,contacts'
));
} catch (DealApiException $e) {
$this->log->info('[Hubspot] Failed to fetch opportunity', [
'crm_id' => $crmId,
'reason' => $e->getMessage(),
]);
throw $e;
}
if (! $deal instanceof DealWithAssociations) {
throw new CrmException('Deal not found');
}
return [
'id' => $deal->getId(),
'properties' => $deal->getProperties(),
'associations' => $deal->getAssociations(),
];
}
/**
* Generic batch read method for HubSpot objects
*
* @param string $objectType The object type ('deals', 'companies', 'contacts')
* @param array<string> $crmIds Array of HubSpot object IDs (max 100)
* @param array<string> $fields Array of property names to fetch
*
* @return array<string, array> Array keyed by CRM ID with object data
*/
private function batchReadObjects(string $objectType, array $crmIds, array $fields): array
{
if (empty($crmIds)) {
return [];
}
$this->validateBatchSize($objectType, $crmIds);
$this->ensureValidToken();
try {
$batchConfig = $this->createBatchConfiguration($objectType);
$batchReadRequest = $this->prepareBatchRequest($batchConfig, $crmIds, $fields);
$response = $batchConfig['api']->read($batchReadRequest);
$this->validateApiResponse($response, $objectType);
$results = $this->processApiResults($response);
$this->logBatchResults($objectType, $crmIds, $results);
return $results;
} catch (\Throwable $e) {
$this->handleBatchError($e, $objectType, $crmIds);
}
}
private function validateBatchSize(string $objectType, array $crmIds): void
{
if (count($crmIds) > 100) {
throw new \InvalidArgumentException("Batch size cannot exceed 100 {$objectType}");
}
}
private function createBatchConfiguration(string $objectType): array
{
$configurations = [
'deals' => [
'batchReadRequest' => new \HubSpot\Client\Crm\Deals\Model\BatchReadInputSimplePublicObjectId(),
'inputClass' => \HubSpot\Client\Crm\Deals\Model\SimplePublicObjectId::class,
'api' => $this->getNewInstance()->crm()->deals()->batchApi(),
],
'companies' => [
'batchReadRequest' => new \HubSpot\Client\Crm\Companies\Model\BatchReadInputSimplePublicObjectId(),
'inputClass' => \HubSpot\Client\Crm\Companies\Model\SimplePublicObjectId::class,
'api' => $this->getNewInstance()->crm()->companies()->batchApi(),
],
'contacts' => [
'batchReadRequest' => new \HubSpot\Client\Crm\Contacts\Model\BatchReadInputSimplePublicObjectId(),
'inputClass' => \HubSpot\Client\Crm\Contacts\Model\SimplePublicObjectId::class,
'api' => $this->getNewInstance()->crm()->contacts()->batchApi(),
],
];
if (! isset($configurations[$objectType])) {
throw new \InvalidArgumentException("Unsupported object type: {$objectType}");
}
return $configurations[$objectType];
}
private function prepareBatchRequest(array $batchConfig, array $crmIds, array $fields): object
{
$batchReadRequest = $batchConfig['batchReadRequest'];
$inputClass = $batchConfig['inputClass'];
$inputs = array_map(function ($crmId) use ($inputClass) {
$input = new $inputClass();
$input->setId($crmId);
return $input;
}, $crmIds);
$batchReadRequest->setInputs($inputs);
$batchReadRequest->setProperties($fields);
return $batchReadRequest;
}
private function validateApiResponse($response, string $objectType): void
{
if (! $response) {
throw new CrmException("HubSpot API returned null response for {$objectType} batch read");
}
}
private function processApiResults($response): array
{
$results = [];
$responseResults = $response->getResults();
if ($responseResults) {
foreach ($responseResults as $object) {
if ($object && $object->getId()) {
$results[$object->getId()] = [
'id' => $object->getId(),
'properties' => $object->getProperties() ?: [],
];
}
}
}
return $results;
}
private function logBatchResults(string $objectType, array $crmIds, array $results): void
{
$this->log->info("[HubSpot] Batch fetched {$objectType}", [
'requested_count' => count($crmIds),
'returned_count' => count($results),
'crm_ids' => $crmIds,
]);
}
private function handleBatchError(\Throwable $e, string $objectType, array $crmIds): void
{
$errorMessage = $e->getMessage() ?: 'Unknown error';
$errorTrace = $e->getTraceAsString() ?: 'No trace available';
$this->log->error("[HubSpot] Failed to batch fetch {$objectType}", [
'crm_ids' => $crmIds,
'error' => $errorMessage,
'trace' => $errorTrace,
]);
throw new CrmException("Failed to batch fetch {$objectType}: " . $errorMessage);
}
/**
* Batch read multiple opportunities by their CRM IDs
*
* @param array<string> $crmIds Array of HubSpot deal IDs (max 100)
* @param array<string> $fields Array of property names to fetch
*
* @return array<string, array> Array keyed by CRM ID with opportunity data
*/
public function getOpportunitiesByIds(array $crmIds, array $fields): array
{
return $this->batchReadObjects('deals', $crmIds, $fields);
}
/**
* Batch read multiple companies by their CRM IDs
*
* @param array<string> $crmIds Array of HubSpot company IDs (max 100)
* @param array<string> $fields Array of property names to fetch
*
* @return array<string, array> Array keyed by CRM ID with company data
*/
public function getCompaniesByIds(array $crmIds, array $fields): array
{
return $this->batchReadObjects('companies', $crmIds, $fields);
}
/**
* Batch read multiple contacts by their CRM IDs
*
* @param array<string> $crmIds Array of HubSpot contact IDs (max 100)
* @param array<string> $fields Array of property names to fetch
*
* @return array<string, array> Array keyed by CRM ID with contact data
*/
public function getContactsByIds(array $crmIds, array $fields): array
{
return $this->batchReadObjects('contacts', $crmIds, $fields);
}
/**
* @throws CompanyApiException
* @throws CrmException
*/
public function getAccountById(string $crmId, array $fields): array
{
try {
$company = $this->getNewInstance()->crm()->companies()->basicApi()->getById(
$crmId,
implode(',', $fields),
);
} catch (CompanyApiException $e) {
$this->log->info('[Hubspot] Failed to fetch account', [
'crm_id' => $crmId,
'reason' => $e->getMessage(),
]);
throw $e;
}
if (! $company instanceof CompaniesWithAssociations) {
throw new CrmException('Account not found');
}
return [
'id' => $company->getId(),
'properties' => $company->getProperties(),
];
}
/**
* @throws ContactApiException
* @throws CrmException
*/
public function getContactById(string $crmId, array $fields): array
{
try {
$contact = $this->getNewInstance()->crm()->contacts()->basicApi()->getById(
$crmId,
implode(',', $fields)
);
} catch (ContactApiException $e) {
$this->log->info('[Hubspot] Failed to fetch contact', [
'crm_id' => $crmId,
'reason' => $e->getMessage(),
]);
throw $e;
}
if (! $contact instanceof ContactsWithAssociations) {
throw new CrmException('Contact not found');
}
return [
'id' => $contact->getId(),
'properties' => $contact->getProperties(),
];
}
/**
* This is email search request that Hubspot offers as GET (more generous quota)
*/
public function getContactByEmail(string $email, array $fields = []): array
{
try {
$contact = $this->getNewInstance()->crm()->contacts()->basicApi()->getById(
$email,
implode(',', $fields),
null,
false,
'email'
);
return [
'id' => $contact->getId(),
'properties' => $contact->getProperties(),
];
} catch (ContactApiException $e) {
$this->log->info('[Hubspot] Failed to fetch contact', [
'email' => $email,
'reason' => $e->getMessage(),
]);
return [];
}
}
/**
* @throws CrmException
*/
public function fetchProperty(string $objectType, string $propertyId): Property
{
$result = $this->getNewInstance()->crm()->properties()->coreApi()->getByName($objectType, $propertyId);
if (! $result instanceof Property) {
$this->log->error('[Hubspot] Failed to fetch property', [
'object_type' => $objectType,
'property_id' => $propertyId,
'reason' => $result->getMessage(),
]);
throw new CrmException('Failed to fetch property');
}
return $result;
}
/**
* @return array<CrmFieldOption>
*/
public function fetchPropertyOptions(string $objectType, string $propertyId): array
{
/** @var array<CrmFieldOption> */
return $this->fetchProperty($objectType, $propertyId)->getOptions();
}
/**
* @return array<array{id:string, label:string, deleted:bool}>
*/
public function fetchCallDispositions(): array
{
/** @var Response $response */
$response = $this->getInstance()->engagements()->getCallDispositions();
/**
* @var array<array{
* id:string,
* label:string,
* deleted: bool
* }>
*/
return $response->toArray();
}
/**
* @return array<CrmFieldOption>
*/
public function fetchOpportunityPipelineStages(): array
{
$stages = [];
$apiResponse = $this->getNewInstance()->crm()->pipelines()->pipelinesApi()->getAll('deals');
if ($apiResponse instanceof Error) {
$this->log->error('[Hubspot] Failed to fetch opportunity pipelines', [
'reason' => $apiResponse->getMessage(),
]);
return [];
}
foreach ($apiResponse->getResults() as $pipeline) {
$pipelineStages = array_map(
static function (PipelineStage $stage) {
return [
'id' => $stage->getId(),
'label' => $stage->getLabel(),
];
},
$pipeline->getStages()
);
$stages = array_merge($stages, $pipelineStages);
}
return $stages;
}
public function fetchOpportunityPipelines(): array
{
$pipelines = [];
try {
$apiResponse = $this->makeRequest('/crm/v3/pipelines/deals');
} catch (\Exception $e) {
$this->log->info('[Hubspot] Failed to fetch opportunity pipelines', [
'reason' => $e->getMessage(),
]);
return [];
}
$response = $apiResponse->toArray();
foreach ($response['results'] as $pipeline) {
$pipelines[] = [
'id' => $pipeline['id'],
'label' => $pipeline['label'],
];
}
return $pipelines;
}
/**
* @return array<CrmFieldOption>
*/
public function fetchMeetingOutcomeFieldOptions(Field $field): array
{
return $field->getCrmProviderId() === 'meetingOutcome'
? $this->fetchMeetingOutcomeTypes()
: $this->fetchCallActivityTypes();
}
public function fetchMeetingOutcomeTypes(): array
{
return $this->extractMeetingTypeOptions(
'[URL_WITH_CREDENTIALS] Response $response */
$response = $this->getInstance()
->getClient()
->request('GET', $endpoint);
/**
* @var array<array{
* value: string,
* label: string,
* displayOrder: int
* }> $optionData
*/
$optionData = $response->toArray()['options'] ?? [];
$options = [];
foreach ($optionData as $item) {
$options[] = [
'id' => $item['value'],
'value' => $item['value'],
'label' => $item['label'],
'display_order' => $item['displayOrder'],
];
}
return $options;
}
/**
* @return array<CrmFieldOption>
*/
public function fetchDispositionFieldOptions(): array
{
$options = [];
$dispositions = $this->fetchCallDispositions();
foreach ($dispositions as $disposition) {
if ($disposition['deleted'] !== false) {
continue;
}
$option['value'] = $disposition['id'];
$option['id'] = $disposition['id'];
$option['label'] = $disposition['label'];
$options[] = $option;
}
return $options;
}
/**
* @return array<CrmFieldOption>
*/
public function fetchOpportunityFieldOptions(Field $field): array
{
if ($field->isStageField()) {
return $this->fetchOpportunityPipelineStages();
}
if ($field->isPipelineField()) {
return $this->fetchOpportunityPipelines();
}
return $this->fetchPropertyOptions('deals', $field->getCrmProviderId());
}
/**
* @throws BadRequest
* @throws HubspotException
*/
public function makeRequest(string $endpoint, $method = 'GET', $payload = [], ?string $queryString = null)
{
$endpoint = self::BASE_URL . $endpoint;
if ($method === 'GET') {
$response = $this->getInstance()->getClient()?->request(
method: $method,
endpoint: $endpoint,
query_string: $queryString
);
} else {
$response = $this->getInstance()->getClient()->request($method, $endpoint, [
'json' => ($payload),
]);
}
$max = $response->getHeaderLine('X-HubSpot-RateLimit-Max'); // "110"
$remaining = $response->getHeaderLine('X-HubSpot-RateLimit-Remaining'); // "109"
$interval = $response->getHeaderLine('X-HubSpot-RateLimit-Interval-Milliseconds'); // "10000"
$body = json_decode((string) $response->getBody(), true);
\Illuminate\Support\Facades\Log::channel('custom_channel')->info('$max ' . PHP_EOL . print_r($max, true));
\Illuminate\Support\Facades\Log::channel('custom_channel')->info('$remaining ' . PHP_EOL . print_r($remaining, true));
\Illuminate\Support\Facades\Log::channel('custom_channel')->info('$interval ' . PHP_EOL . print_r($interval, true));
\Illuminate\Support\Facades\Log::channel('custom_channel')->info('$body ' . PHP_EOL . print_r($body, true));
return $response;
}
/**
* @throws BadRequest
* @throws HubspotException
*/
public function createMeeting(array $payload): Response
{
$endpoint = '/crm/v3/objects/meetings';
return $this->makeRequest($endpoint, 'POST', $payload);
}
/**
* @throws BadRequest
* @throws HubspotException
*/
public function updateMeeting(string $meetingId, array $payload): Response
{
$endpoint = '/crm/v3/objects/meetings/' . $meetingId;
return $this->makeRequest($endpoint, 'PATCH', $payload);
}
/**
* @throws \Exception
*/
public function createNote(
string $body,
string $ownerId,
int $timestamp,
string $objectId,
NoteObject $noteObject
): ?string {
try {
$noteInput = new SimplePublicObjectInput([
'properties' => [
'hs_note_body' => $body,
'hubspot_owner_id' => $ownerId,
'hs_timestamp' => $timestamp,
],
]);
// Create note
$note = $this->getNewInstance()->crm()->objects()->basicApi()->create('note', $noteInput);
$this->getNewInstance()->crm()->objects()->associationsApi()->create(
'note',
$note->getId(),
$this->getNoteObject($noteObject),
$objectId,
$this->getNoteAssociationType($noteObject),
);
return $note->getId();
} catch (\Exception $e) {
$this->log->error('[Hubspot] Failed to create note', [
'objectId' => $objectId,
'noteObject' => $noteObject->getObjectType(),
'reason' => $e->getMessage(),
]);
\Sentry::captureException($e);
}
return null;
}
public function updateEngagement(string $objectId, array $engagement, array $metadata): void
{
$this->getInstance()->engagements()->update($objectId, $engagement, $metadata);
}
public function getEngagementData(string $engagementId): array
{
$engagement = $this->getInstance()->engagements()->get($engagementId);
return $engagement->toArray();
}
public function createEngagement(array $engagement, array $associations, array $metadata): Response
{
return $this->getInstance()
->engagements()
->create($engagement, $associations, $metadata);
}
public function isUnauthorizedException(\Exception $e): bool
{
// Check for specific HubSpot API exception types first
if ($e instanceof BadRequest) {
// BadRequest can contain 401 status codes
return $e->getCode() === 401;
}
// Check for HTTP client exceptions with status codes
if ($e instanceof \GuzzleHttp\Exception\RequestException && $e->hasResponse()) {
$response = $e->getResponse();
if ($response !== null) {
return $response->getStatusCode() === 401;
}
}
// Check for Guzzle HTTP exceptions
if ($e instanceof \GuzzleHttp\Exception\ClientException) {
return $e->getCode() === 401;
}
// Fallback to string matching as last resort, but be more specific
$message = strtolower($e->getMessage());
return str_contains($message, '401 unauthorized') ||
str_contains($message, 'http 401') ||
str_contains($message, 'status code 401') ||
(preg_match('/\b401\b/', $message) && str_contains($message, 'unauthorized'));
}
/**
* Validates and refreshes the access token if needed before API requests.
* This ensures long-running processes don't fail due to token expiration.
*
* @throws SocialAccountTokenInvalidException
*/
public function ensureValidToken(): void
{
if ($this->oauthAccount === null) {
return;
}
$newToken = $this->tokenManager->ensureValidToken($this->oauthAccount);
if ($newToken !== null) {
$this->accessToken = $newToken;
}
}
public function getConfig()
{
return $this->config;
}
// returns only active (archived=false)
public function getOwners(): array
{
return $this->getNewInstance()->crm()->owners()->getAll();
}
/**
* @param bool $archived
*
* @return array<Owner>|[]
*/
public function getOwnersArchived(bool $archived = true): array
{
$endpoint = '/crm/v3/owners';
$queryParams = [
'archived' => $archived ? 'true' : 'false',
];
$queryString = http_build_query($queryParams);
$owners = [];
try {
$response = $this->makeRequest(endpoint: $endpoint, queryString: $queryString);
$responseData = $response?->toArray();
foreach ($responseData['results'] as $result) {
try {
$owners[] = Owner::create($result);
} catch (Throwable $e) {
$this->log->error('[HubSpot] Failed to process owner data', [
'result' => $result,
'error' => $e->getMessage(),
]);
continue;
}
}
} catch (Throwable $e) {
$this->log->error('HubSpot] Failed to fetch owners', [
'archived' => $archived,
'error' => $e->getMessage(),
]);
return [];
}
return $owners;
}
public function getMeeting(string $engagementId): ObjectWithAssociations
{
return $this->getNewInstance()->crm()->objects()->basicApi()
->getById('meeting', $engagementId, null, 'contact,company,deal');
}
public function deleteEngagement(string $engagementId): void
{
$this->getInstance()->engagements()->delete((int) $engagementId);
}
public function getAssociationsData(array $ids, string $fromObject, string $toObject): array
{
$associationData = [];
$idChunks = array_chunk($ids, self::ASSOCIATIONS_BATCH_SIZE_LIMIT);
foreach ($idChunks as $idChunk) {
try {
$batchInput = new \HubSpot\Client\Crm\Associations\Model\BatchInputPublicObjectId();
$batchInput->setInputs(array_map(function ($id) {
$publicObjectId = new \HubSpot\Client\Crm\Associations\Model\PublicObjectId();
$publicObjectId->setId($id);
return $publicObjectId;
}, $idChunk));
$associatedObjectsData = $this
->getNewInstance()
->crm()
->associations()
->batchApi()
->read($fromObject, $toObject, $batchInput);
if ($associatedObjectsData instanceof \HubSpot\Client\Crm\Associations\Model\BatchResponsePublicAssociationMulti) {
foreach ($associatedObjectsData->getResults() as $association) {
$from = $association->getFrom()->getId();
$toAssociations = $association->getTo();
if (! empty($toAssociations)) {
$associationData[$from] = array_map(function ($item) {
return $item->getId();
}, $toAssociations);
}
}
}
} catch (\Exception $e) {
$this->log->error('[Hubspot] Failed to fetch associations', [
'from_object' => $fromObject,
'to_object' => $toObject,
'reason' => $e->getMessage(),
]);
}
}
return $associationData;
}
/**
* @throws \Exception
*/
private function getNoteAssociationType(NoteObject $noteObject): string
{
return match($noteObject) {
NoteObject::Opportunity => 'note_to_deal',
NoteObject::Lead, NoteObject::Contact => 'note_to_contact', // or 'note_to_lead' if your portal supports it
NoteObject::Account => 'note_to_company',
NoteObject::Call, NoteObject::Event => throw new \Exception('Not supported'),
};
}
/**
* @throws \Exception
*/
private function getNoteObject(NoteObject $noteObject): string
{
return match($noteObject) {
NoteObject::Opportunity => 'deal',
NoteObject::Lead, NoteObject::Contact => 'contact',
NoteObject::Account => 'company',
NoteObject::Call, NoteObject::Event => throw new \Exception('Not supported'),
};
}
public function addAssociations(string $objectType, string $associationType, array $payload): Response
{
$endpoint = "/crm/v4/associations/$objectType/$associationType/batch/create";
return $this->makeRequest($endpoint, 'POST', $payload);
}
public function removeAssociations(string $objectType, string $associationType, array $payload): Response
{
$endpoint = "/crm/v4/associations/$objectType/$associationType/batch/archive";
return $this->makeRequest($endpoint, 'POST', $payload);
}
}
Project
Project
New File or Directory…
Expand Selected
Collapse All
Options
Hide...
|
2710
|
NULL
|
NULL
|
NULL
|
|
2712
|
111
|
26
|
2026-05-07T11:36:41.089374+00:00
|
/Users/lukas/.screenpipe/data/data/2026-05-07/1778 /Users/lukas/.screenpipe/data/data/2026-05-07/1778153801089_m1.jpg...
|
PhpStorm
|
faVsco.js – laravel.log
|
True
|
NULL
|
monitor_1
|
NULL
|
NULL
|
NULL
|
NULL
|
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
2
60
Previous Highlighted Error
Next Highlighted Error
<?php
declare(strict_types=1);
namespace Jiminny\Services\Crm\Hubspot;
use HubSpot\Client\Crm\Deals\ApiException as DealApiException;
use HubSpot\Client\Crm\Contacts\ApiException as ContactApiException;
use HubSpot\Client\Crm\Companies\ApiException as CompanyApiException;
use HubSpot\Client\Crm\Contacts\Model\SimplePublicObjectWithAssociations as ContactsWithAssociations;
use HubSpot\Client\Crm\Companies\Model\SimplePublicObjectWithAssociations as CompaniesWithAssociations;
use HubSpot\Client\Crm\Deals\Model\SimplePublicObjectWithAssociations as DealWithAssociations;
use HubSpot\Client\Crm\Objects\Model\SimplePublicObjectInput;
use HubSpot\Client\Crm\Objects\Model\SimplePublicObjectWithAssociations as ObjectWithAssociations;
use HubSpot\Client\Crm\Pipelines\Model\Error;
use HubSpot\Client\Crm\Pipelines\Model\PipelineStage;
use HubSpot\Client\Crm\Properties\Model\Property;
use HubSpot\Discovery\Discovery;
use Jiminny\Component\Utility\Service\ProviderRateLimiter;
use Jiminny\Exceptions\CrmException;
use Jiminny\Exceptions\RateLimitException;
use Jiminny\Exceptions\SocialAccountTokenInvalidException;
use Jiminny\Jobs\Crm\NoteObject;
use Jiminny\Models\Crm\Field;
use Jiminny\Services\Crm\BaseClient;
use Jiminny\Services\Crm\Hubspot\DTO\Response\Owner;
use Jiminny\Services\SocialAccountService;
use SevenShores\Hubspot\Exceptions\BadRequest;
use SevenShores\Hubspot\Exceptions\HubspotException;
use SevenShores\Hubspot\Factory;
use SevenShores\Hubspot\Http\Response;
use Jiminny\Services\Crm\Hubspot\Pagination\HubspotPaginationService;
use Throwable;
/**
* @phpstan-type CrmFieldOption array{id:string, label:string, value?:string}
*/
class Client extends BaseClient implements HubspotClientInterface
{
public const string MIN_API_VERSION = '2';
public const string BASE_URL = '[URL_WITH_CREDENTIALS] T
* @param callable(): T $apiCall
* @return T
*
* @throws RateLimitException
*/
private function executeRequest(callable $apiCall)
{
if (! $this->rateLimiter->canMakeRequest($this->config)) {
$retryAfter = $this->rateLimiter->requestAvailableIn($this->config);
$this->log->warning('[Hubspot] Rate limit exceeded, deferring request', [
'team_id' => $this->config->team_id,
'config_id' => $this->config->getId(),
'retry_after' => $retryAfter,
]);
throw new RateLimitException(
'Hubspot rate limit reached for configuration ' . $this->config->getId(),
$retryAfter,
);
}
$this->rateLimiter->incrementRequestCount($this->config);
try {
return $apiCall();
} catch (Throwable $e) {
if ($this->isHubspotRateLimit($e)) {
$retryAfter = $this->parseRetryAfter($e);
$this->log->warning('[Hubspot] Received 429 from API', [
'team_id' => $this->config->team_id,
'config_id' => $this->config->getId(),
'retry_after' => $retryAfter,
'reason' => $e->getMessage(),
]);
throw new RateLimitException('Hubspot returned 429', $retryAfter, $e);
}
throw $e;
}
}
private function isHubspotRateLimit(Throwable $e): bool
{
return method_exists($e, 'getCode') && (int) $e->getCode() === 429;
}
private function parseRetryAfter(Throwable $e): int
{
if (method_exists($e, 'getResponseHeaders')) {
$headers = $e->getResponseHeaders() ?: [];
$value = $headers['Retry-After'] ?? $headers['retry-after'] ?? null;
if (is_array($value)) {
$value = $value[0] ?? null;
}
if (is_numeric($value)) {
return (int) $value;
}
}
return 10;
}
public function getMinimumApiVersion(): string
{
return self::MIN_API_VERSION;
}
public function getInstance(): Factory
{
return new Factory([
'key' => $this->accessToken,
'oauth2' => true,
'base_url' => $this->baseUrl,
]);
}
public function getNewInstance(): Discovery
{
return \HubSpot\Factory::createWithAccessToken($this->accessToken);
}
/**
* Secondly and daily limits for Hubspot API
*
* Product Tier: Free & Starter | Professional & Enterprise | API add-on (any tier)
* Burst: 100/10 seconds | 150/10 seconds | 200/10 seconds
* Daily: 250,000 | 500,000 | 1,000,000
*
* Official documentation states: The search endpoints are rate limited to five requests per second.
* Since with 5 RPS were still hitting secondly rate limits we lowered it to 4
*/
public function getPaginatedData(array $payload, string $type, int $offset = 0): array
{
$total = 0;
$lastId = null;
$rows = [];
foreach ($this->getPaginatedDataGenerator($payload, $type, $offset, $total, $lastId) as $row) {
$rows[] = $row;
}
return ['results' => $rows, 'total' => $total, 'last_record' => $lastId];
}
/**
* @throws HubspotException
* @throws SocialAccountTokenInvalidException
* @throws BadRequest
*/
public function getPaginatedDataGenerator(
array $payload,
string $type,
int $offset = 0,
int &$total = 0,
?string &$lastRecordId = null
): \Generator {
return $this->paginationService->getPaginatedDataGenerator(
$this,
$payload,
$type,
$offset,
$total,
$lastRecordId
);
}
/**
* @throws DealApiException
* @throws CrmException
*/
public function getOpportunityById(string $crmId, array $fields): array
{
try {
$deal = $this->executeRequest(fn () => $this->getNewInstance()->crm()->deals()->basicApi()->getById(
$crmId,
implode(',', $fields),
'companies,contacts'
));
} catch (DealApiException $e) {
$this->log->info('[Hubspot] Failed to fetch opportunity', [
'crm_id' => $crmId,
'reason' => $e->getMessage(),
]);
throw $e;
}
if (! $deal instanceof DealWithAssociations) {
throw new CrmException('Deal not found');
}
return [
'id' => $deal->getId(),
'properties' => $deal->getProperties(),
'associations' => $deal->getAssociations(),
];
}
/**
* Generic batch read method for HubSpot objects
*
* @param string $objectType The object type ('deals', 'companies', 'contacts')
* @param array<string> $crmIds Array of HubSpot object IDs (max 100)
* @param array<string> $fields Array of property names to fetch
*
* @return array<string, array> Array keyed by CRM ID with object data
*/
private function batchReadObjects(string $objectType, array $crmIds, array $fields): array
{
if (empty($crmIds)) {
return [];
}
$this->validateBatchSize($objectType, $crmIds);
$this->ensureValidToken();
try {
$batchConfig = $this->createBatchConfiguration($objectType);
$batchReadRequest = $this->prepareBatchRequest($batchConfig, $crmIds, $fields);
$response = $batchConfig['api']->read($batchReadRequest);
$this->validateApiResponse($response, $objectType);
$results = $this->processApiResults($response);
$this->logBatchResults($objectType, $crmIds, $results);
return $results;
} catch (\Throwable $e) {
$this->handleBatchError($e, $objectType, $crmIds);
}
}
private function validateBatchSize(string $objectType, array $crmIds): void
{
if (count($crmIds) > 100) {
throw new \InvalidArgumentException("Batch size cannot exceed 100 {$objectType}");
}
}
private function createBatchConfiguration(string $objectType): array
{
$configurations = [
'deals' => [
'batchReadRequest' => new \HubSpot\Client\Crm\Deals\Model\BatchReadInputSimplePublicObjectId(),
'inputClass' => \HubSpot\Client\Crm\Deals\Model\SimplePublicObjectId::class,
'api' => $this->getNewInstance()->crm()->deals()->batchApi(),
],
'companies' => [
'batchReadRequest' => new \HubSpot\Client\Crm\Companies\Model\BatchReadInputSimplePublicObjectId(),
'inputClass' => \HubSpot\Client\Crm\Companies\Model\SimplePublicObjectId::class,
'api' => $this->getNewInstance()->crm()->companies()->batchApi(),
],
'contacts' => [
'batchReadRequest' => new \HubSpot\Client\Crm\Contacts\Model\BatchReadInputSimplePublicObjectId(),
'inputClass' => \HubSpot\Client\Crm\Contacts\Model\SimplePublicObjectId::class,
'api' => $this->getNewInstance()->crm()->contacts()->batchApi(),
],
];
if (! isset($configurations[$objectType])) {
throw new \InvalidArgumentException("Unsupported object type: {$objectType}");
}
return $configurations[$objectType];
}
private function prepareBatchRequest(array $batchConfig, array $crmIds, array $fields): object
{
$batchReadRequest = $batchConfig['batchReadRequest'];
$inputClass = $batchConfig['inputClass'];
$inputs = array_map(function ($crmId) use ($inputClass) {
$input = new $inputClass();
$input->setId($crmId);
return $input;
}, $crmIds);
$batchReadRequest->setInputs($inputs);
$batchReadRequest->setProperties($fields);
return $batchReadRequest;
}
private function validateApiResponse($response, string $objectType): void
{
if (! $response) {
throw new CrmException("HubSpot API returned null response for {$objectType} batch read");
}
}
private function processApiResults($response): array
{
$results = [];
$responseResults = $response->getResults();
if ($responseResults) {
foreach ($responseResults as $object) {
if ($object && $object->getId()) {
$results[$object->getId()] = [
'id' => $object->getId(),
'properties' => $object->getProperties() ?: [],
];
}
}
}
return $results;
}
private function logBatchResults(string $objectType, array $crmIds, array $results): void
{
$this->log->info("[HubSpot] Batch fetched {$objectType}", [
'requested_count' => count($crmIds),
'returned_count' => count($results),
'crm_ids' => $crmIds,
]);
}
private function handleBatchError(\Throwable $e, string $objectType, array $crmIds): void
{
$errorMessage = $e->getMessage() ?: 'Unknown error';
$errorTrace = $e->getTraceAsString() ?: 'No trace available';
$this->log->error("[HubSpot] Failed to batch fetch {$objectType}", [
'crm_ids' => $crmIds,
'error' => $errorMessage,
'trace' => $errorTrace,
]);
throw new CrmException("Failed to batch fetch {$objectType}: " . $errorMessage);
}
/**
* Batch read multiple opportunities by their CRM IDs
*
* @param array<string> $crmIds Array of HubSpot deal IDs (max 100)
* @param array<string> $fields Array of property names to fetch
*
* @return array<string, array> Array keyed by CRM ID with opportunity data
*/
public function getOpportunitiesByIds(array $crmIds, array $fields): array
{
return $this->batchReadObjects('deals', $crmIds, $fields);
}
/**
* Batch read multiple companies by their CRM IDs
*
* @param array<string> $crmIds Array of HubSpot company IDs (max 100)
* @param array<string> $fields Array of property names to fetch
*
* @return array<string, array> Array keyed by CRM ID with company data
*/
public function getCompaniesByIds(array $crmIds, array $fields): array
{
return $this->batchReadObjects('companies', $crmIds, $fields);
}
/**
* Batch read multiple contacts by their CRM IDs
*
* @param array<string> $crmIds Array of HubSpot contact IDs (max 100)
* @param array<string> $fields Array of property names to fetch
*
* @return array<string, array> Array keyed by CRM ID with contact data
*/
public function getContactsByIds(array $crmIds, array $fields): array
{
return $this->batchReadObjects('contacts', $crmIds, $fields);
}
/**
* @throws CompanyApiException
* @throws CrmException
*/
public function getAccountById(string $crmId, array $fields): array
{
try {
$company = $this->getNewInstance()->crm()->companies()->basicApi()->getById(
$crmId,
implode(',', $fields),
);
} catch (CompanyApiException $e) {
$this->log->info('[Hubspot] Failed to fetch account', [
'crm_id' => $crmId,
'reason' => $e->getMessage(),
]);
throw $e;
}
if (! $company instanceof CompaniesWithAssociations) {
throw new CrmException('Account not found');
}
return [
'id' => $company->getId(),
'properties' => $company->getProperties(),
];
}
/**
* @throws ContactApiException
* @throws CrmException
*/
public function getContactById(string $crmId, array $fields): array
{
try {
$contact = $this->getNewInstance()->crm()->contacts()->basicApi()->getById(
$crmId,
implode(',', $fields)
);
} catch (ContactApiException $e) {
$this->log->info('[Hubspot] Failed to fetch contact', [
'crm_id' => $crmId,
'reason' => $e->getMessage(),
]);
throw $e;
}
if (! $contact instanceof ContactsWithAssociations) {
throw new CrmException('Contact not found');
}
return [
'id' => $contact->getId(),
'properties' => $contact->getProperties(),
];
}
/**
* This is email search request that Hubspot offers as GET (more generous quota)
*/
public function getContactByEmail(string $email, array $fields = []): array
{
try {
$contact = $this->getNewInstance()->crm()->contacts()->basicApi()->getById(
$email,
implode(',', $fields),
null,
false,
'email'
);
return [
'id' => $contact->getId(),
'properties' => $contact->getProperties(),
];
} catch (ContactApiException $e) {
$this->log->info('[Hubspot] Failed to fetch contact', [
'email' => $email,
'reason' => $e->getMessage(),
]);
return [];
}
}
/**
* @throws CrmException
*/
public function fetchProperty(string $objectType, string $propertyId): Property
{
$result = $this->getNewInstance()->crm()->properties()->coreApi()->getByName($objectType, $propertyId);
if (! $result instanceof Property) {
$this->log->error('[Hubspot] Failed to fetch property', [
'object_type' => $objectType,
'property_id' => $propertyId,
'reason' => $result->getMessage(),
]);
throw new CrmException('Failed to fetch property');
}
return $result;
}
/**
* @return array<CrmFieldOption>
*/
public function fetchPropertyOptions(string $objectType, string $propertyId): array
{
/** @var array<CrmFieldOption> */
return $this->fetchProperty($objectType, $propertyId)->getOptions();
}
/**
* @return array<array{id:string, label:string, deleted:bool}>
*/
public function fetchCallDispositions(): array
{
/** @var Response $response */
$response = $this->getInstance()->engagements()->getCallDispositions();
/**
* @var array<array{
* id:string,
* label:string,
* deleted: bool
* }>
*/
return $response->toArray();
}
/**
* @return array<CrmFieldOption>
*/
public function fetchOpportunityPipelineStages(): array
{
$stages = [];
$apiResponse = $this->getNewInstance()->crm()->pipelines()->pipelinesApi()->getAll('deals');
if ($apiResponse instanceof Error) {
$this->log->error('[Hubspot] Failed to fetch opportunity pipelines', [
'reason' => $apiResponse->getMessage(),
]);
return [];
}
foreach ($apiResponse->getResults() as $pipeline) {
$pipelineStages = array_map(
static function (PipelineStage $stage) {
return [
'id' => $stage->getId(),
'label' => $stage->getLabel(),
];
},
$pipeline->getStages()
);
$stages = array_merge($stages, $pipelineStages);
}
return $stages;
}
public function fetchOpportunityPipelines(): array
{
$pipelines = [];
try {
$apiResponse = $this->makeRequest('/crm/v3/pipelines/deals');
} catch (\Exception $e) {
$this->log->info('[Hubspot] Failed to fetch opportunity pipelines', [
'reason' => $e->getMessage(),
]);
return [];
}
$response = $apiResponse->toArray();
foreach ($response['results'] as $pipeline) {
$pipelines[] = [
'id' => $pipeline['id'],
'label' => $pipeline['label'],
];
}
return $pipelines;
}
/**
* @return array<CrmFieldOption>
*/
public function fetchMeetingOutcomeFieldOptions(Field $field): array
{
return $field->getCrmProviderId() === 'meetingOutcome'
? $this->fetchMeetingOutcomeTypes()
: $this->fetchCallActivityTypes();
}
public function fetchMeetingOutcomeTypes(): array
{
return $this->extractMeetingTypeOptions(
'[URL_WITH_CREDENTIALS] Response $response */
$response = $this->getInstance()
->getClient()
->request('GET', $endpoint);
/**
* @var array<array{
* value: string,
* label: string,
* displayOrder: int
* }> $optionData
*/
$optionData = $response->toArray()['options'] ?? [];
$options = [];
foreach ($optionData as $item) {
$options[] = [
'id' => $item['value'],
'value' => $item['value'],
'label' => $item['label'],
'display_order' => $item['displayOrder'],
];
}
return $options;
}
/**
* @return array<CrmFieldOption>
*/
public function fetchDispositionFieldOptions(): array
{
$options = [];
$dispositions = $this->fetchCallDispositions();
foreach ($dispositions as $disposition) {
if ($disposition['deleted'] !== false) {
continue;
}
$option['value'] = $disposition['id'];
$option['id'] = $disposition['id'];
$option['label'] = $disposition['label'];
$options[] = $option;
}
return $options;
}
/**
* @return array<CrmFieldOption>
*/
public function fetchOpportunityFieldOptions(Field $field): array
{
if ($field->isStageField()) {
return $this->fetchOpportunityPipelineStages();
}
if ($field->isPipelineField()) {
return $this->fetchOpportunityPipelines();
}
return $this->fetchPropertyOptions('deals', $field->getCrmProviderId());
}
/**
* @throws BadRequest
* @throws HubspotException
*/
public function makeRequest(string $endpoint, $method = 'GET', $payload = [], ?string $queryString = null)
{
$endpoint = self::BASE_URL . $endpoint;
if ($method === 'GET') {
$response = $this->getInstance()->getClient()?->request(
method: $method,
endpoint: $endpoint,
query_string: $queryString
);
} else {
$response = $this->getInstance()->getClient()->request($method, $endpoint, [
'json' => ($payload),
]);
}
$max = $response->getHeaderLine('X-HubSpot-RateLimit-Max'); // "110"
$remaining = $response->getHeaderLine('X-HubSpot-RateLimit-Remaining'); // "109"
$interval = $response->getHeaderLine('X-HubSpot-RateLimit-Interval-Milliseconds'); // "10000"
$body = json_decode((string) $response->getBody(), true);
\Illuminate\Support\Facades\Log::channel('custom_channel')->info('$max ' . PHP_EOL . print_r($max, true));
\Illuminate\Support\Facades\Log::channel('custom_channel')->info('$remaining ' . PHP_EOL . print_r($remaining, true));
\Illuminate\Support\Facades\Log::channel('custom_channel')->info('$interval ' . PHP_EOL . print_r($interval, true));
\Illuminate\Support\Facades\Log::channel('custom_channel')->info('$body ' . PHP_EOL . print_r($body, true));
return $response;
}
/**
* @throws BadRequest
* @throws HubspotException
*/
public function createMeeting(array $payload): Response
{
$endpoint = '/crm/v3/objects/meetings';
return $this->makeRequest($endpoint, 'POST', $payload);
}
/**
* @throws BadRequest
* @throws HubspotException
*/
public function updateMeeting(string $meetingId, array $payload): Response
{
$endpoint = '/crm/v3/objects/meetings/' . $meetingId;
return $this->makeRequest($endpoint, 'PATCH', $payload);
}
/**
* @throws \Exception
*/
public function createNote(
string $body,
string $ownerId,
int $timestamp,
string $objectId,
NoteObject $noteObject
): ?string {
try {
$noteInput = new SimplePublicObjectInput([
'properties' => [
'hs_note_body' => $body,
'hubspot_owner_id' => $ownerId,
'hs_timestamp' => $timestamp,
],
]);
// Create note
$note = $this->getNewInstance()->crm()->objects()->basicApi()->create('note', $noteInput);
$this->getNewInstance()->crm()->objects()->associationsApi()->create(
'note',
$note->getId(),
$this->getNoteObject($noteObject),
$objectId,
$this->getNoteAssociationType($noteObject),
);
return $note->getId();
} catch (\Exception $e) {
$this->log->error('[Hubspot] Failed to create note', [
'objectId' => $objectId,
'noteObject' => $noteObject->getObjectType(),
'reason' => $e->getMessage(),
]);
\Sentry::captureException($e);
}
return null;
}
public function updateEngagement(string $objectId, array $engagement, array $metadata): void
{
$this->getInstance()->engagements()->update($objectId, $engagement, $metadata);
}
public function getEngagementData(string $engagementId): array
{
$engagement = $this->getInstance()->engagements()->get($engagementId);
return $engagement->toArray();
}
public function createEngagement(array $engagement, array $associations, array $metadata): Response
{
return $this->getInstance()
->engagements()
->create($engagement, $associations, $metadata);
}
public function isUnauthorizedException(\Exception $e): bool
{
// Check for specific HubSpot API exception types first
if ($e instanceof BadRequest) {
// BadRequest can contain 401 status codes
return $e->getCode() === 401;
}
// Check for HTTP client exceptions with status codes
if ($e instanceof \GuzzleHttp\Exception\RequestException && $e->hasResponse()) {
$response = $e->getResponse();
if ($response !== null) {
return $response->getStatusCode() === 401;
}
}
// Check for Guzzle HTTP exceptions
if ($e instanceof \GuzzleHttp\Exception\ClientException) {
return $e->getCode() === 401;
}
// Fallback to string matching as last resort, but be more specific
$message = strtolower($e->getMessage());
return str_contains($message, '401 unauthorized') ||
str_contains($message, 'http 401') ||
str_contains($message, 'status code 401') ||
(preg_match('/\b401\b/', $message) && str_contains($message, 'unauthorized'));
}
/**
* Validates and refreshes the access token if needed before API requests.
* This ensures long-running processes don't fail due to token expiration.
*
* @throws SocialAccountTokenInvalidException
*/
public function ensureValidToken(): void
{
if ($this->oauthAccount === null) {
return;
}
$newToken = $this->tokenManager->ensureValidToken($this->oauthAccount);
if ($newToken !== null) {
$this->accessToken = $newToken;
}
}
public function getConfig()
{
return $this->config;
}
// returns only active (archived=false)
public function getOwners(): array
{
return $this->getNewInstance()->crm()->owners()->getAll();
}
/**
* @param bool $archived
*
* @return array<Owner>|[]
*/
public function getOwnersArchived(bool $archived = true): array
{
$endpoint = '/crm/v3/owners';
$queryParams = [
'archived' => $archived ? 'true' : 'false',
];
$queryString = http_build_query($queryParams);
$owners = [];
try {
$response = $this->makeRequest(endpoint: $endpoint, queryString: $queryString);
$responseData = $response?->toArray();
foreach ($responseData['results'] as $result) {
try {
$owners[] = Owner::create($result);
} catch (Throwable $e) {
$this->log->error('[HubSpot] Failed to process owner data', [
'result' => $result,
'error' => $e->getMessage(),
]);
continue;
}
}
} catch (Throwable $e) {
$this->log->error('HubSpot] Failed to fetch owners', [
'archived' => $archived,
'error' => $e->getMessage(),
]);
return [];
}
return $owners;
}
public function getMeeting(string $engagementId): ObjectWithAssociations
{
return $this->getNewInstance()->crm()->objects()->basicApi()
->getById('meeting', $engagementId, null, 'contact,company,deal');
}
public function deleteEngagement(string $engagementId): void
{
$this->getInstance()->engagements()->delete((int) $engagementId);
}
public function getAssociationsData(array $ids, string $fromObject, string $toObject): array
{
$associationData = [];
$idChunks = array_chunk($ids, self::ASSOCIATIONS_BATCH_SIZE_LIMIT);
foreach ($idChunks as $idChunk) {
try {
$batchInput = new \HubSpot\Client\Crm\Associations\Model\BatchInputPublicObjectId();
$batchInput->setInputs(array_map(function ($id) {
$publicObjectId = new \HubSpot\Client\Crm\Associations\Model\PublicObjectId();
$publicObjectId->setId($id);
return $publicObjectId;
}, $idChunk));
$associatedObjectsData = $this
->getNewInstance()
->crm()
->associations()
->batchApi()
->read($fromObject, $toObject, $batchInput);
if ($associatedObjectsData instanceof \HubSpot\Client\Crm\Associations\Model\BatchResponsePublicAssociationMulti) {
foreach ($associatedObjectsData->getResults() as $association) {
$from = $association->getFrom()->getId();
$toAssociations = $association->getTo();
if (! empty($toAssociations)) {
$associationData[$from] = array_map(function ($item) {
return $item->getId();
}, $toAssociations);
}
}
}
} catch (\Exception $e) {
$this->log->error('[Hubspot] Failed to fetch associations', [
'from_object' => $fromObject,
'to_object' => $toObject,
'reason' => $e->getMessage(),
]);
}
}
return $associationData;
}
/**
* @throws \Exception
*/
private function getNoteAssociationType(NoteObject $noteObject): string
{
return match($noteObject) {
NoteObject::Opportunity => 'note_to_deal',
NoteObject::Lead, NoteObject::Contact => 'note_to_contact', // or 'note_to_lead' if your portal supports it
NoteObject::Account => 'note_to_company',
NoteObject::Call, NoteObject::Event => throw new \Exception('Not supported'),
};
}
/**
* @throws \Exception
*/
private function getNoteObject(NoteObject $noteObject): string
{
return match($noteObject) {
NoteObject::Opportunity => 'deal',
NoteObject::Lead, NoteObject::Contact => 'contact',
NoteObject::Account => 'company',
NoteObject::Call, NoteObject::Event => throw new \Exception('Not supported'),
};
}
public function addAssociations(string $objectType, string $associationType, array $payload): Response
{
$endpoint = "/crm/v4/associations/$objectType/$associationType/batch/create";
return $this->makeRequest($endpoint, 'POST', $payload);
}
public function removeAssociations(string $objectType, string $associationType, array $payload): Response
{
$endpoint = "/crm/v4/associations/$objectType/$associationType/batch/archive";
return $this->makeRequest($endpoint, 'POST', $payload);
}
}
Project
Project
New File or Directory…
Expand Selected
Collapse All
Options
Hide...
|
[{"role":"AXButton","text" [{"role":"AXButton","text":"Project: faVsco.js, menu","depth":5,"on_screen":true,"help_text":"~/jiminny/app","role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"master, menu","depth":5,"on_screen":true,"help_text":"Git Branch: master","role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Start Listening for PHP Debug Connections","depth":5,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"AskJiminnyReportActivityServiceTest","depth":6,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Run 'AskJiminnyReportActivityServiceTest'","depth":6,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Debug 'AskJiminnyReportActivityServiceTest'","depth":6,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"More Actions","depth":6,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"JetBrains AI","depth":5,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Search Everywhere","depth":5,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"IDE and Project Settings","depth":5,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXTextArea","text":"","depth":4,"on_screen":true,"value":"","role_description":"text entry area","is_enabled":true,"is_focused":true,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Sync Changes","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.018055556,"height":0.026666667},"on_screen":false,"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Hide This Notification","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.018055556,"height":0.026666667},"on_screen":false,"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Code changed:","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.088194445,"height":0.027777778},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Hide","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.018055556,"height":0.026666667},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"2","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.016666668,"height":0.02111111},"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"60","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.021527778,"height":0.02111111},"on_screen":false,"role_description":"text"},{"role":"AXButton","text":"Previous Highlighted Error","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.015277778,"height":0.025555555},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Next Highlighted Error","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.014583333,"height":0.025555555},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXTextArea","text":"<?php\n\ndeclare(strict_types=1);\n\nnamespace Jiminny\\Services\\Crm\\Hubspot;\n\nuse HubSpot\\Client\\Crm\\Deals\\ApiException as DealApiException;\nuse HubSpot\\Client\\Crm\\Contacts\\ApiException as ContactApiException;\nuse HubSpot\\Client\\Crm\\Companies\\ApiException as CompanyApiException;\nuse HubSpot\\Client\\Crm\\Contacts\\Model\\SimplePublicObjectWithAssociations as ContactsWithAssociations;\nuse HubSpot\\Client\\Crm\\Companies\\Model\\SimplePublicObjectWithAssociations as CompaniesWithAssociations;\nuse HubSpot\\Client\\Crm\\Deals\\Model\\SimplePublicObjectWithAssociations as DealWithAssociations;\nuse HubSpot\\Client\\Crm\\Objects\\Model\\SimplePublicObjectInput;\nuse HubSpot\\Client\\Crm\\Objects\\Model\\SimplePublicObjectWithAssociations as ObjectWithAssociations;\nuse HubSpot\\Client\\Crm\\Pipelines\\Model\\Error;\nuse HubSpot\\Client\\Crm\\Pipelines\\Model\\PipelineStage;\nuse HubSpot\\Client\\Crm\\Properties\\Model\\Property;\nuse HubSpot\\Discovery\\Discovery;\nuse Jiminny\\Component\\Utility\\Service\\ProviderRateLimiter;\nuse Jiminny\\Exceptions\\CrmException;\nuse Jiminny\\Exceptions\\RateLimitException;\nuse Jiminny\\Exceptions\\SocialAccountTokenInvalidException;\nuse Jiminny\\Jobs\\Crm\\NoteObject;\nuse Jiminny\\Models\\Crm\\Field;\nuse Jiminny\\Services\\Crm\\BaseClient;\nuse Jiminny\\Services\\Crm\\Hubspot\\DTO\\Response\\Owner;\nuse Jiminny\\Services\\SocialAccountService;\nuse SevenShores\\Hubspot\\Exceptions\\BadRequest;\nuse SevenShores\\Hubspot\\Exceptions\\HubspotException;\nuse SevenShores\\Hubspot\\Factory;\nuse SevenShores\\Hubspot\\Http\\Response;\nuse Jiminny\\Services\\Crm\\Hubspot\\Pagination\\HubspotPaginationService;\nuse Throwable;\n\n/**\n * @phpstan-type CrmFieldOption array{id:string, label:string, value?:string}\n */\nclass Client extends BaseClient implements HubspotClientInterface\n{\n public const string MIN_API_VERSION = '2';\n\n public const string BASE_URL = 'https://api.hubapi.com';\n\n public const int ASSOCIATIONS_BATCH_SIZE_LIMIT = 1000;\n\n private HubspotPaginationService $paginationService;\n private HubspotTokenManager $tokenManager;\n private ProviderRateLimiter $rateLimiter;\n\n public function __construct(\n SocialAccountService $socialAccountService,\n HubspotPaginationService $paginationService,\n HubspotTokenManager $tokenManager,\n ProviderRateLimiter $rateLimiter,\n ) {\n parent::__construct($socialAccountService);\n $this->paginationService = $paginationService;\n $this->tokenManager = $tokenManager;\n $this->rateLimiter = $rateLimiter;\n\n $this->setBaseUrl(self::BASE_URL);\n $this->setVersion(self::MIN_API_VERSION);\n }\n\n /**\n * Single entry point for every HubSpot API call. Enforces the per-portal\n * rate limit configured in the rate_limits table (morphed to the current\n * Configuration) and reacts to a real 429 from HubSpot by translating it\n * into a RateLimitException carrying Retry-After.\n *\n * Wrap any outbound HubSpot call (SDK or raw HTTP) like:\n *\n * $this->executeRequest(fn () => $this->getNewInstance()->crm()->...);\n *\n * @template T\n * @param callable(): T $apiCall\n * @return T\n *\n * @throws RateLimitException\n */\n private function executeRequest(callable $apiCall)\n {\n if (! $this->rateLimiter->canMakeRequest($this->config)) {\n $retryAfter = $this->rateLimiter->requestAvailableIn($this->config);\n\n $this->log->warning('[Hubspot] Rate limit exceeded, deferring request', [\n 'team_id' => $this->config->team_id,\n 'config_id' => $this->config->getId(),\n 'retry_after' => $retryAfter,\n ]);\n\n throw new RateLimitException(\n 'Hubspot rate limit reached for configuration ' . $this->config->getId(),\n $retryAfter,\n );\n }\n\n $this->rateLimiter->incrementRequestCount($this->config);\n\n try {\n return $apiCall();\n } catch (Throwable $e) {\n if ($this->isHubspotRateLimit($e)) {\n $retryAfter = $this->parseRetryAfter($e);\n\n $this->log->warning('[Hubspot] Received 429 from API', [\n 'team_id' => $this->config->team_id,\n 'config_id' => $this->config->getId(),\n 'retry_after' => $retryAfter,\n 'reason' => $e->getMessage(),\n ]);\n\n throw new RateLimitException('Hubspot returned 429', $retryAfter, $e);\n }\n\n throw $e;\n }\n }\n\n private function isHubspotRateLimit(Throwable $e): bool\n {\n return method_exists($e, 'getCode') && (int) $e->getCode() === 429;\n }\n\n private function parseRetryAfter(Throwable $e): int\n {\n if (method_exists($e, 'getResponseHeaders')) {\n $headers = $e->getResponseHeaders() ?: [];\n $value = $headers['Retry-After'] ?? $headers['retry-after'] ?? null;\n if (is_array($value)) {\n $value = $value[0] ?? null;\n }\n if (is_numeric($value)) {\n return (int) $value;\n }\n }\n\n return 10;\n }\n\n public function getMinimumApiVersion(): string\n {\n return self::MIN_API_VERSION;\n }\n\n public function getInstance(): Factory\n {\n return new Factory([\n 'key' => $this->accessToken,\n 'oauth2' => true,\n 'base_url' => $this->baseUrl,\n ]);\n }\n\n public function getNewInstance(): Discovery\n {\n return \\HubSpot\\Factory::createWithAccessToken($this->accessToken);\n }\n\n /**\n * Secondly and daily limits for Hubspot API\n *\n * Product Tier: Free & Starter | Professional & Enterprise | API add-on (any tier)\n * Burst: 100/10 seconds | 150/10 seconds | 200/10 seconds\n * Daily: 250,000 | 500,000 | 1,000,000\n *\n * Official documentation states: The search endpoints are rate limited to five requests per second.\n * Since with 5 RPS were still hitting secondly rate limits we lowered it to 4\n */\n public function getPaginatedData(array $payload, string $type, int $offset = 0): array\n {\n $total = 0;\n $lastId = null;\n $rows = [];\n foreach ($this->getPaginatedDataGenerator($payload, $type, $offset, $total, $lastId) as $row) {\n $rows[] = $row;\n }\n\n return ['results' => $rows, 'total' => $total, 'last_record' => $lastId];\n }\n\n /**\n * @throws HubspotException\n * @throws SocialAccountTokenInvalidException\n * @throws BadRequest\n */\n public function getPaginatedDataGenerator(\n array $payload,\n string $type,\n int $offset = 0,\n int &$total = 0,\n ?string &$lastRecordId = null\n ): \\Generator {\n return $this->paginationService->getPaginatedDataGenerator(\n $this,\n $payload,\n $type,\n $offset,\n $total,\n $lastRecordId\n );\n }\n\n /**\n * @throws DealApiException\n * @throws CrmException\n */\n public function getOpportunityById(string $crmId, array $fields): array\n {\n try {\n $deal = $this->executeRequest(fn () => $this->getNewInstance()->crm()->deals()->basicApi()->getById(\n $crmId,\n implode(',', $fields),\n 'companies,contacts'\n ));\n } catch (DealApiException $e) {\n $this->log->info('[Hubspot] Failed to fetch opportunity', [\n 'crm_id' => $crmId,\n 'reason' => $e->getMessage(),\n ]);\n\n throw $e;\n }\n\n if (! $deal instanceof DealWithAssociations) {\n throw new CrmException('Deal not found');\n }\n\n return [\n 'id' => $deal->getId(),\n 'properties' => $deal->getProperties(),\n 'associations' => $deal->getAssociations(),\n ];\n }\n\n /**\n * Generic batch read method for HubSpot objects\n *\n * @param string $objectType The object type ('deals', 'companies', 'contacts')\n * @param array<string> $crmIds Array of HubSpot object IDs (max 100)\n * @param array<string> $fields Array of property names to fetch\n *\n * @return array<string, array> Array keyed by CRM ID with object data\n */\n private function batchReadObjects(string $objectType, array $crmIds, array $fields): array\n {\n if (empty($crmIds)) {\n return [];\n }\n\n $this->validateBatchSize($objectType, $crmIds);\n $this->ensureValidToken();\n\n try {\n $batchConfig = $this->createBatchConfiguration($objectType);\n $batchReadRequest = $this->prepareBatchRequest($batchConfig, $crmIds, $fields);\n $response = $batchConfig['api']->read($batchReadRequest);\n\n $this->validateApiResponse($response, $objectType);\n\n $results = $this->processApiResults($response);\n $this->logBatchResults($objectType, $crmIds, $results);\n\n return $results;\n } catch (\\Throwable $e) {\n $this->handleBatchError($e, $objectType, $crmIds);\n }\n }\n\n private function validateBatchSize(string $objectType, array $crmIds): void\n {\n if (count($crmIds) > 100) {\n throw new \\InvalidArgumentException(\"Batch size cannot exceed 100 {$objectType}\");\n }\n }\n\n private function createBatchConfiguration(string $objectType): array\n {\n $configurations = [\n 'deals' => [\n 'batchReadRequest' => new \\HubSpot\\Client\\Crm\\Deals\\Model\\BatchReadInputSimplePublicObjectId(),\n 'inputClass' => \\HubSpot\\Client\\Crm\\Deals\\Model\\SimplePublicObjectId::class,\n 'api' => $this->getNewInstance()->crm()->deals()->batchApi(),\n ],\n 'companies' => [\n 'batchReadRequest' => new \\HubSpot\\Client\\Crm\\Companies\\Model\\BatchReadInputSimplePublicObjectId(),\n 'inputClass' => \\HubSpot\\Client\\Crm\\Companies\\Model\\SimplePublicObjectId::class,\n 'api' => $this->getNewInstance()->crm()->companies()->batchApi(),\n ],\n 'contacts' => [\n 'batchReadRequest' => new \\HubSpot\\Client\\Crm\\Contacts\\Model\\BatchReadInputSimplePublicObjectId(),\n 'inputClass' => \\HubSpot\\Client\\Crm\\Contacts\\Model\\SimplePublicObjectId::class,\n 'api' => $this->getNewInstance()->crm()->contacts()->batchApi(),\n ],\n ];\n\n if (! isset($configurations[$objectType])) {\n throw new \\InvalidArgumentException(\"Unsupported object type: {$objectType}\");\n }\n\n return $configurations[$objectType];\n }\n\n private function prepareBatchRequest(array $batchConfig, array $crmIds, array $fields): object\n {\n $batchReadRequest = $batchConfig['batchReadRequest'];\n $inputClass = $batchConfig['inputClass'];\n\n $inputs = array_map(function ($crmId) use ($inputClass) {\n $input = new $inputClass();\n $input->setId($crmId);\n\n return $input;\n }, $crmIds);\n\n $batchReadRequest->setInputs($inputs);\n $batchReadRequest->setProperties($fields);\n\n return $batchReadRequest;\n }\n\n private function validateApiResponse($response, string $objectType): void\n {\n if (! $response) {\n throw new CrmException(\"HubSpot API returned null response for {$objectType} batch read\");\n }\n }\n\n private function processApiResults($response): array\n {\n $results = [];\n $responseResults = $response->getResults();\n\n if ($responseResults) {\n foreach ($responseResults as $object) {\n if ($object && $object->getId()) {\n $results[$object->getId()] = [\n 'id' => $object->getId(),\n 'properties' => $object->getProperties() ?: [],\n ];\n }\n }\n }\n\n return $results;\n }\n\n private function logBatchResults(string $objectType, array $crmIds, array $results): void\n {\n $this->log->info(\"[HubSpot] Batch fetched {$objectType}\", [\n 'requested_count' => count($crmIds),\n 'returned_count' => count($results),\n 'crm_ids' => $crmIds,\n ]);\n }\n\n private function handleBatchError(\\Throwable $e, string $objectType, array $crmIds): void\n {\n $errorMessage = $e->getMessage() ?: 'Unknown error';\n $errorTrace = $e->getTraceAsString() ?: 'No trace available';\n\n $this->log->error(\"[HubSpot] Failed to batch fetch {$objectType}\", [\n 'crm_ids' => $crmIds,\n 'error' => $errorMessage,\n 'trace' => $errorTrace,\n ]);\n\n throw new CrmException(\"Failed to batch fetch {$objectType}: \" . $errorMessage);\n }\n\n /**\n * Batch read multiple opportunities by their CRM IDs\n *\n * @param array<string> $crmIds Array of HubSpot deal IDs (max 100)\n * @param array<string> $fields Array of property names to fetch\n *\n * @return array<string, array> Array keyed by CRM ID with opportunity data\n */\n public function getOpportunitiesByIds(array $crmIds, array $fields): array\n {\n return $this->batchReadObjects('deals', $crmIds, $fields);\n }\n\n /**\n * Batch read multiple companies by their CRM IDs\n *\n * @param array<string> $crmIds Array of HubSpot company IDs (max 100)\n * @param array<string> $fields Array of property names to fetch\n *\n * @return array<string, array> Array keyed by CRM ID with company data\n */\n public function getCompaniesByIds(array $crmIds, array $fields): array\n {\n return $this->batchReadObjects('companies', $crmIds, $fields);\n }\n\n /**\n * Batch read multiple contacts by their CRM IDs\n *\n * @param array<string> $crmIds Array of HubSpot contact IDs (max 100)\n * @param array<string> $fields Array of property names to fetch\n *\n * @return array<string, array> Array keyed by CRM ID with contact data\n */\n public function getContactsByIds(array $crmIds, array $fields): array\n {\n return $this->batchReadObjects('contacts', $crmIds, $fields);\n }\n\n /**\n * @throws CompanyApiException\n * @throws CrmException\n */\n public function getAccountById(string $crmId, array $fields): array\n {\n try {\n $company = $this->getNewInstance()->crm()->companies()->basicApi()->getById(\n $crmId,\n implode(',', $fields),\n );\n } catch (CompanyApiException $e) {\n $this->log->info('[Hubspot] Failed to fetch account', [\n 'crm_id' => $crmId,\n 'reason' => $e->getMessage(),\n ]);\n\n throw $e;\n }\n\n if (! $company instanceof CompaniesWithAssociations) {\n throw new CrmException('Account not found');\n }\n\n return [\n 'id' => $company->getId(),\n 'properties' => $company->getProperties(),\n ];\n }\n\n /**\n * @throws ContactApiException\n * @throws CrmException\n */\n public function getContactById(string $crmId, array $fields): array\n {\n try {\n $contact = $this->getNewInstance()->crm()->contacts()->basicApi()->getById(\n $crmId,\n implode(',', $fields)\n );\n } catch (ContactApiException $e) {\n $this->log->info('[Hubspot] Failed to fetch contact', [\n 'crm_id' => $crmId,\n 'reason' => $e->getMessage(),\n ]);\n\n throw $e;\n }\n\n if (! $contact instanceof ContactsWithAssociations) {\n throw new CrmException('Contact not found');\n }\n\n return [\n 'id' => $contact->getId(),\n 'properties' => $contact->getProperties(),\n ];\n }\n\n /**\n * This is email search request that Hubspot offers as GET (more generous quota)\n */\n public function getContactByEmail(string $email, array $fields = []): array\n {\n try {\n $contact = $this->getNewInstance()->crm()->contacts()->basicApi()->getById(\n $email,\n implode(',', $fields),\n null,\n false,\n 'email'\n );\n\n return [\n 'id' => $contact->getId(),\n 'properties' => $contact->getProperties(),\n ];\n } catch (ContactApiException $e) {\n $this->log->info('[Hubspot] Failed to fetch contact', [\n 'email' => $email,\n 'reason' => $e->getMessage(),\n ]);\n\n return [];\n }\n }\n\n /**\n * @throws CrmException\n */\n public function fetchProperty(string $objectType, string $propertyId): Property\n {\n $result = $this->getNewInstance()->crm()->properties()->coreApi()->getByName($objectType, $propertyId);\n\n if (! $result instanceof Property) {\n $this->log->error('[Hubspot] Failed to fetch property', [\n 'object_type' => $objectType,\n 'property_id' => $propertyId,\n 'reason' => $result->getMessage(),\n ]);\n\n throw new CrmException('Failed to fetch property');\n }\n\n return $result;\n }\n\n /**\n * @return array<CrmFieldOption>\n */\n public function fetchPropertyOptions(string $objectType, string $propertyId): array\n {\n /** @var array<CrmFieldOption> */\n return $this->fetchProperty($objectType, $propertyId)->getOptions();\n }\n\n /**\n * @return array<array{id:string, label:string, deleted:bool}>\n */\n public function fetchCallDispositions(): array\n {\n /** @var Response $response */\n $response = $this->getInstance()->engagements()->getCallDispositions();\n\n /**\n * @var array<array{\n * id:string,\n * label:string,\n * deleted: bool\n * }>\n */\n return $response->toArray();\n }\n\n /**\n * @return array<CrmFieldOption>\n */\n public function fetchOpportunityPipelineStages(): array\n {\n $stages = [];\n $apiResponse = $this->getNewInstance()->crm()->pipelines()->pipelinesApi()->getAll('deals');\n\n if ($apiResponse instanceof Error) {\n $this->log->error('[Hubspot] Failed to fetch opportunity pipelines', [\n 'reason' => $apiResponse->getMessage(),\n ]);\n\n return [];\n }\n\n foreach ($apiResponse->getResults() as $pipeline) {\n $pipelineStages = array_map(\n static function (PipelineStage $stage) {\n return [\n 'id' => $stage->getId(),\n 'label' => $stage->getLabel(),\n ];\n },\n $pipeline->getStages()\n );\n\n $stages = array_merge($stages, $pipelineStages);\n }\n\n return $stages;\n }\n\n public function fetchOpportunityPipelines(): array\n {\n $pipelines = [];\n\n try {\n $apiResponse = $this->makeRequest('/crm/v3/pipelines/deals');\n } catch (\\Exception $e) {\n $this->log->info('[Hubspot] Failed to fetch opportunity pipelines', [\n 'reason' => $e->getMessage(),\n ]);\n\n return [];\n }\n\n $response = $apiResponse->toArray();\n\n foreach ($response['results'] as $pipeline) {\n $pipelines[] = [\n 'id' => $pipeline['id'],\n 'label' => $pipeline['label'],\n ];\n }\n\n return $pipelines;\n }\n\n /**\n * @return array<CrmFieldOption>\n */\n public function fetchMeetingOutcomeFieldOptions(Field $field): array\n {\n return $field->getCrmProviderId() === 'meetingOutcome'\n ? $this->fetchMeetingOutcomeTypes()\n : $this->fetchCallActivityTypes();\n }\n\n public function fetchMeetingOutcomeTypes(): array\n {\n return $this->extractMeetingTypeOptions(\n 'https://api.hubapi.com/crm/v3/properties/meeting/hs_meeting_outcome'\n );\n }\n\n public function fetchCallActivityTypes(): array\n {\n return $this->extractMeetingTypeOptions(\n 'https://api.hubapi.com/crm/v3/properties/call/hs_activity_type'\n );\n }\n\n private function extractMeetingTypeOptions(string $endpoint): array\n {\n /** @var Response $response */\n $response = $this->getInstance()\n ->getClient()\n ->request('GET', $endpoint);\n\n /**\n * @var array<array{\n * value: string,\n * label: string,\n * displayOrder: int\n * }> $optionData\n */\n $optionData = $response->toArray()['options'] ?? [];\n\n $options = [];\n foreach ($optionData as $item) {\n $options[] = [\n 'id' => $item['value'],\n 'value' => $item['value'],\n 'label' => $item['label'],\n 'display_order' => $item['displayOrder'],\n ];\n }\n\n return $options;\n }\n\n /**\n * @return array<CrmFieldOption>\n */\n public function fetchDispositionFieldOptions(): array\n {\n $options = [];\n\n $dispositions = $this->fetchCallDispositions();\n\n foreach ($dispositions as $disposition) {\n if ($disposition['deleted'] !== false) {\n continue;\n }\n\n $option['value'] = $disposition['id'];\n $option['id'] = $disposition['id'];\n $option['label'] = $disposition['label'];\n\n $options[] = $option;\n }\n\n return $options;\n }\n\n /**\n * @return array<CrmFieldOption>\n */\n public function fetchOpportunityFieldOptions(Field $field): array\n {\n if ($field->isStageField()) {\n return $this->fetchOpportunityPipelineStages();\n }\n\n if ($field->isPipelineField()) {\n return $this->fetchOpportunityPipelines();\n }\n\n return $this->fetchPropertyOptions('deals', $field->getCrmProviderId());\n }\n\n /**\n * @throws BadRequest\n * @throws HubspotException\n */\n public function makeRequest(string $endpoint, $method = 'GET', $payload = [], ?string $queryString = null)\n {\n $endpoint = self::BASE_URL . $endpoint;\n\n if ($method === 'GET') {\n $response = $this->getInstance()->getClient()?->request(\n method: $method,\n endpoint: $endpoint,\n query_string: $queryString\n );\n } else {\n $response = $this->getInstance()->getClient()->request($method, $endpoint, [\n 'json' => ($payload),\n ]);\n }\n\n $max = $response->getHeaderLine('X-HubSpot-RateLimit-Max'); // \"110\"\n $remaining = $response->getHeaderLine('X-HubSpot-RateLimit-Remaining'); // \"109\"\n $interval = $response->getHeaderLine('X-HubSpot-RateLimit-Interval-Milliseconds'); // \"10000\"\n $body = json_decode((string) $response->getBody(), true);\n\n \\Illuminate\\Support\\Facades\\Log::channel('custom_channel')->info('$max ' . PHP_EOL . print_r($max, true));\n \\Illuminate\\Support\\Facades\\Log::channel('custom_channel')->info('$remaining ' . PHP_EOL . print_r($remaining, true));\n \\Illuminate\\Support\\Facades\\Log::channel('custom_channel')->info('$interval ' . PHP_EOL . print_r($interval, true));\n \\Illuminate\\Support\\Facades\\Log::channel('custom_channel')->info('$body ' . PHP_EOL . print_r($body, true));\n\n return $response;\n }\n\n /**\n * @throws BadRequest\n * @throws HubspotException\n */\n public function createMeeting(array $payload): Response\n {\n $endpoint = '/crm/v3/objects/meetings';\n\n return $this->makeRequest($endpoint, 'POST', $payload);\n }\n\n /**\n * @throws BadRequest\n * @throws HubspotException\n */\n public function updateMeeting(string $meetingId, array $payload): Response\n {\n $endpoint = '/crm/v3/objects/meetings/' . $meetingId;\n\n return $this->makeRequest($endpoint, 'PATCH', $payload);\n }\n\n /**\n * @throws \\Exception\n */\n public function createNote(\n string $body,\n string $ownerId,\n int $timestamp,\n string $objectId,\n NoteObject $noteObject\n ): ?string {\n try {\n $noteInput = new SimplePublicObjectInput([\n 'properties' => [\n 'hs_note_body' => $body,\n 'hubspot_owner_id' => $ownerId,\n 'hs_timestamp' => $timestamp,\n ],\n ]);\n\n // Create note\n $note = $this->getNewInstance()->crm()->objects()->basicApi()->create('note', $noteInput);\n\n $this->getNewInstance()->crm()->objects()->associationsApi()->create(\n 'note',\n $note->getId(),\n $this->getNoteObject($noteObject),\n $objectId,\n $this->getNoteAssociationType($noteObject),\n );\n\n return $note->getId();\n } catch (\\Exception $e) {\n $this->log->error('[Hubspot] Failed to create note', [\n 'objectId' => $objectId,\n 'noteObject' => $noteObject->getObjectType(),\n 'reason' => $e->getMessage(),\n ]);\n\n \\Sentry::captureException($e);\n }\n\n return null;\n }\n\n public function updateEngagement(string $objectId, array $engagement, array $metadata): void\n {\n $this->getInstance()->engagements()->update($objectId, $engagement, $metadata);\n }\n\n public function getEngagementData(string $engagementId): array\n {\n $engagement = $this->getInstance()->engagements()->get($engagementId);\n\n return $engagement->toArray();\n }\n\n public function createEngagement(array $engagement, array $associations, array $metadata): Response\n {\n return $this->getInstance()\n ->engagements()\n ->create($engagement, $associations, $metadata);\n }\n\n public function isUnauthorizedException(\\Exception $e): bool\n {\n // Check for specific HubSpot API exception types first\n if ($e instanceof BadRequest) {\n // BadRequest can contain 401 status codes\n return $e->getCode() === 401;\n }\n\n // Check for HTTP client exceptions with status codes\n if ($e instanceof \\GuzzleHttp\\Exception\\RequestException && $e->hasResponse()) {\n $response = $e->getResponse();\n if ($response !== null) {\n return $response->getStatusCode() === 401;\n }\n }\n\n // Check for Guzzle HTTP exceptions\n if ($e instanceof \\GuzzleHttp\\Exception\\ClientException) {\n return $e->getCode() === 401;\n }\n\n // Fallback to string matching as last resort, but be more specific\n $message = strtolower($e->getMessage());\n\n return str_contains($message, '401 unauthorized') ||\n str_contains($message, 'http 401') ||\n str_contains($message, 'status code 401') ||\n (preg_match('/\\b401\\b/', $message) && str_contains($message, 'unauthorized'));\n }\n\n /**\n * Validates and refreshes the access token if needed before API requests.\n * This ensures long-running processes don't fail due to token expiration.\n *\n * @throws SocialAccountTokenInvalidException\n */\n public function ensureValidToken(): void\n {\n if ($this->oauthAccount === null) {\n return;\n }\n\n $newToken = $this->tokenManager->ensureValidToken($this->oauthAccount);\n if ($newToken !== null) {\n $this->accessToken = $newToken;\n }\n }\n\n public function getConfig()\n {\n return $this->config;\n }\n\n // returns only active (archived=false)\n public function getOwners(): array\n {\n return $this->getNewInstance()->crm()->owners()->getAll();\n }\n\n /**\n * @param bool $archived\n *\n * @return array<Owner>|[]\n */\n public function getOwnersArchived(bool $archived = true): array\n {\n $endpoint = '/crm/v3/owners';\n $queryParams = [\n 'archived' => $archived ? 'true' : 'false',\n ];\n $queryString = http_build_query($queryParams);\n\n $owners = [];\n\n try {\n $response = $this->makeRequest(endpoint: $endpoint, queryString: $queryString);\n $responseData = $response?->toArray();\n\n foreach ($responseData['results'] as $result) {\n try {\n $owners[] = Owner::create($result);\n } catch (Throwable $e) {\n $this->log->error('[HubSpot] Failed to process owner data', [\n 'result' => $result,\n 'error' => $e->getMessage(),\n ]);\n\n continue;\n }\n }\n } catch (Throwable $e) {\n $this->log->error('HubSpot] Failed to fetch owners', [\n 'archived' => $archived,\n 'error' => $e->getMessage(),\n ]);\n\n return [];\n }\n\n return $owners;\n }\n\n public function getMeeting(string $engagementId): ObjectWithAssociations\n {\n return $this->getNewInstance()->crm()->objects()->basicApi()\n ->getById('meeting', $engagementId, null, 'contact,company,deal');\n }\n\n public function deleteEngagement(string $engagementId): void\n {\n $this->getInstance()->engagements()->delete((int) $engagementId);\n }\n\n public function getAssociationsData(array $ids, string $fromObject, string $toObject): array\n {\n $associationData = [];\n $idChunks = array_chunk($ids, self::ASSOCIATIONS_BATCH_SIZE_LIMIT);\n\n foreach ($idChunks as $idChunk) {\n try {\n $batchInput = new \\HubSpot\\Client\\Crm\\Associations\\Model\\BatchInputPublicObjectId();\n $batchInput->setInputs(array_map(function ($id) {\n $publicObjectId = new \\HubSpot\\Client\\Crm\\Associations\\Model\\PublicObjectId();\n $publicObjectId->setId($id);\n\n return $publicObjectId;\n }, $idChunk));\n\n $associatedObjectsData = $this\n ->getNewInstance()\n ->crm()\n ->associations()\n ->batchApi()\n ->read($fromObject, $toObject, $batchInput);\n\n if ($associatedObjectsData instanceof \\HubSpot\\Client\\Crm\\Associations\\Model\\BatchResponsePublicAssociationMulti) {\n foreach ($associatedObjectsData->getResults() as $association) {\n $from = $association->getFrom()->getId();\n $toAssociations = $association->getTo();\n\n if (! empty($toAssociations)) {\n $associationData[$from] = array_map(function ($item) {\n return $item->getId();\n }, $toAssociations);\n }\n }\n }\n } catch (\\Exception $e) {\n $this->log->error('[Hubspot] Failed to fetch associations', [\n 'from_object' => $fromObject,\n 'to_object' => $toObject,\n 'reason' => $e->getMessage(),\n ]);\n }\n }\n\n return $associationData;\n }\n\n /**\n * @throws \\Exception\n */\n private function getNoteAssociationType(NoteObject $noteObject): string\n {\n return match($noteObject) {\n NoteObject::Opportunity => 'note_to_deal',\n NoteObject::Lead, NoteObject::Contact => 'note_to_contact', // or 'note_to_lead' if your portal supports it\n NoteObject::Account => 'note_to_company',\n NoteObject::Call, NoteObject::Event => throw new \\Exception('Not supported'),\n };\n }\n\n /**\n * @throws \\Exception\n */\n private function getNoteObject(NoteObject $noteObject): string\n {\n return match($noteObject) {\n NoteObject::Opportunity => 'deal',\n NoteObject::Lead, NoteObject::Contact => 'contact',\n NoteObject::Account => 'company',\n NoteObject::Call, NoteObject::Event => throw new \\Exception('Not supported'),\n };\n }\n\n public function addAssociations(string $objectType, string $associationType, array $payload): Response\n {\n $endpoint = \"/crm/v4/associations/$objectType/$associationType/batch/create\";\n\n return $this->makeRequest($endpoint, 'POST', $payload);\n }\n\n public function removeAssociations(string $objectType, string $associationType, array $payload): Response\n {\n $endpoint = \"/crm/v4/associations/$objectType/$associationType/batch/archive\";\n\n return $this->makeRequest($endpoint, 'POST', $payload);\n }\n}","depth":4,"on_screen":true,"value":"<?php\n\ndeclare(strict_types=1);\n\nnamespace Jiminny\\Services\\Crm\\Hubspot;\n\nuse HubSpot\\Client\\Crm\\Deals\\ApiException as DealApiException;\nuse HubSpot\\Client\\Crm\\Contacts\\ApiException as ContactApiException;\nuse HubSpot\\Client\\Crm\\Companies\\ApiException as CompanyApiException;\nuse HubSpot\\Client\\Crm\\Contacts\\Model\\SimplePublicObjectWithAssociations as ContactsWithAssociations;\nuse HubSpot\\Client\\Crm\\Companies\\Model\\SimplePublicObjectWithAssociations as CompaniesWithAssociations;\nuse HubSpot\\Client\\Crm\\Deals\\Model\\SimplePublicObjectWithAssociations as DealWithAssociations;\nuse HubSpot\\Client\\Crm\\Objects\\Model\\SimplePublicObjectInput;\nuse HubSpot\\Client\\Crm\\Objects\\Model\\SimplePublicObjectWithAssociations as ObjectWithAssociations;\nuse HubSpot\\Client\\Crm\\Pipelines\\Model\\Error;\nuse HubSpot\\Client\\Crm\\Pipelines\\Model\\PipelineStage;\nuse HubSpot\\Client\\Crm\\Properties\\Model\\Property;\nuse HubSpot\\Discovery\\Discovery;\nuse Jiminny\\Component\\Utility\\Service\\ProviderRateLimiter;\nuse Jiminny\\Exceptions\\CrmException;\nuse Jiminny\\Exceptions\\RateLimitException;\nuse Jiminny\\Exceptions\\SocialAccountTokenInvalidException;\nuse Jiminny\\Jobs\\Crm\\NoteObject;\nuse Jiminny\\Models\\Crm\\Field;\nuse Jiminny\\Services\\Crm\\BaseClient;\nuse Jiminny\\Services\\Crm\\Hubspot\\DTO\\Response\\Owner;\nuse Jiminny\\Services\\SocialAccountService;\nuse SevenShores\\Hubspot\\Exceptions\\BadRequest;\nuse SevenShores\\Hubspot\\Exceptions\\HubspotException;\nuse SevenShores\\Hubspot\\Factory;\nuse SevenShores\\Hubspot\\Http\\Response;\nuse Jiminny\\Services\\Crm\\Hubspot\\Pagination\\HubspotPaginationService;\nuse Throwable;\n\n/**\n * @phpstan-type CrmFieldOption array{id:string, label:string, value?:string}\n */\nclass Client extends BaseClient implements HubspotClientInterface\n{\n public const string MIN_API_VERSION = '2';\n\n public const string BASE_URL = 'https://api.hubapi.com';\n\n public const int ASSOCIATIONS_BATCH_SIZE_LIMIT = 1000;\n\n private HubspotPaginationService $paginationService;\n private HubspotTokenManager $tokenManager;\n private ProviderRateLimiter $rateLimiter;\n\n public function __construct(\n SocialAccountService $socialAccountService,\n HubspotPaginationService $paginationService,\n HubspotTokenManager $tokenManager,\n ProviderRateLimiter $rateLimiter,\n ) {\n parent::__construct($socialAccountService);\n $this->paginationService = $paginationService;\n $this->tokenManager = $tokenManager;\n $this->rateLimiter = $rateLimiter;\n\n $this->setBaseUrl(self::BASE_URL);\n $this->setVersion(self::MIN_API_VERSION);\n }\n\n /**\n * Single entry point for every HubSpot API call. Enforces the per-portal\n * rate limit configured in the rate_limits table (morphed to the current\n * Configuration) and reacts to a real 429 from HubSpot by translating it\n * into a RateLimitException carrying Retry-After.\n *\n * Wrap any outbound HubSpot call (SDK or raw HTTP) like:\n *\n * $this->executeRequest(fn () => $this->getNewInstance()->crm()->...);\n *\n * @template T\n * @param callable(): T $apiCall\n * @return T\n *\n * @throws RateLimitException\n */\n private function executeRequest(callable $apiCall)\n {\n if (! $this->rateLimiter->canMakeRequest($this->config)) {\n $retryAfter = $this->rateLimiter->requestAvailableIn($this->config);\n\n $this->log->warning('[Hubspot] Rate limit exceeded, deferring request', [\n 'team_id' => $this->config->team_id,\n 'config_id' => $this->config->getId(),\n 'retry_after' => $retryAfter,\n ]);\n\n throw new RateLimitException(\n 'Hubspot rate limit reached for configuration ' . $this->config->getId(),\n $retryAfter,\n );\n }\n\n $this->rateLimiter->incrementRequestCount($this->config);\n\n try {\n return $apiCall();\n } catch (Throwable $e) {\n if ($this->isHubspotRateLimit($e)) {\n $retryAfter = $this->parseRetryAfter($e);\n\n $this->log->warning('[Hubspot] Received 429 from API', [\n 'team_id' => $this->config->team_id,\n 'config_id' => $this->config->getId(),\n 'retry_after' => $retryAfter,\n 'reason' => $e->getMessage(),\n ]);\n\n throw new RateLimitException('Hubspot returned 429', $retryAfter, $e);\n }\n\n throw $e;\n }\n }\n\n private function isHubspotRateLimit(Throwable $e): bool\n {\n return method_exists($e, 'getCode') && (int) $e->getCode() === 429;\n }\n\n private function parseRetryAfter(Throwable $e): int\n {\n if (method_exists($e, 'getResponseHeaders')) {\n $headers = $e->getResponseHeaders() ?: [];\n $value = $headers['Retry-After'] ?? $headers['retry-after'] ?? null;\n if (is_array($value)) {\n $value = $value[0] ?? null;\n }\n if (is_numeric($value)) {\n return (int) $value;\n }\n }\n\n return 10;\n }\n\n public function getMinimumApiVersion(): string\n {\n return self::MIN_API_VERSION;\n }\n\n public function getInstance(): Factory\n {\n return new Factory([\n 'key' => $this->accessToken,\n 'oauth2' => true,\n 'base_url' => $this->baseUrl,\n ]);\n }\n\n public function getNewInstance(): Discovery\n {\n return \\HubSpot\\Factory::createWithAccessToken($this->accessToken);\n }\n\n /**\n * Secondly and daily limits for Hubspot API\n *\n * Product Tier: Free & Starter | Professional & Enterprise | API add-on (any tier)\n * Burst: 100/10 seconds | 150/10 seconds | 200/10 seconds\n * Daily: 250,000 | 500,000 | 1,000,000\n *\n * Official documentation states: The search endpoints are rate limited to five requests per second.\n * Since with 5 RPS were still hitting secondly rate limits we lowered it to 4\n */\n public function getPaginatedData(array $payload, string $type, int $offset = 0): array\n {\n $total = 0;\n $lastId = null;\n $rows = [];\n foreach ($this->getPaginatedDataGenerator($payload, $type, $offset, $total, $lastId) as $row) {\n $rows[] = $row;\n }\n\n return ['results' => $rows, 'total' => $total, 'last_record' => $lastId];\n }\n\n /**\n * @throws HubspotException\n * @throws SocialAccountTokenInvalidException\n * @throws BadRequest\n */\n public function getPaginatedDataGenerator(\n array $payload,\n string $type,\n int $offset = 0,\n int &$total = 0,\n ?string &$lastRecordId = null\n ): \\Generator {\n return $this->paginationService->getPaginatedDataGenerator(\n $this,\n $payload,\n $type,\n $offset,\n $total,\n $lastRecordId\n );\n }\n\n /**\n * @throws DealApiException\n * @throws CrmException\n */\n public function getOpportunityById(string $crmId, array $fields): array\n {\n try {\n $deal = $this->executeRequest(fn () => $this->getNewInstance()->crm()->deals()->basicApi()->getById(\n $crmId,\n implode(',', $fields),\n 'companies,contacts'\n ));\n } catch (DealApiException $e) {\n $this->log->info('[Hubspot] Failed to fetch opportunity', [\n 'crm_id' => $crmId,\n 'reason' => $e->getMessage(),\n ]);\n\n throw $e;\n }\n\n if (! $deal instanceof DealWithAssociations) {\n throw new CrmException('Deal not found');\n }\n\n return [\n 'id' => $deal->getId(),\n 'properties' => $deal->getProperties(),\n 'associations' => $deal->getAssociations(),\n ];\n }\n\n /**\n * Generic batch read method for HubSpot objects\n *\n * @param string $objectType The object type ('deals', 'companies', 'contacts')\n * @param array<string> $crmIds Array of HubSpot object IDs (max 100)\n * @param array<string> $fields Array of property names to fetch\n *\n * @return array<string, array> Array keyed by CRM ID with object data\n */\n private function batchReadObjects(string $objectType, array $crmIds, array $fields): array\n {\n if (empty($crmIds)) {\n return [];\n }\n\n $this->validateBatchSize($objectType, $crmIds);\n $this->ensureValidToken();\n\n try {\n $batchConfig = $this->createBatchConfiguration($objectType);\n $batchReadRequest = $this->prepareBatchRequest($batchConfig, $crmIds, $fields);\n $response = $batchConfig['api']->read($batchReadRequest);\n\n $this->validateApiResponse($response, $objectType);\n\n $results = $this->processApiResults($response);\n $this->logBatchResults($objectType, $crmIds, $results);\n\n return $results;\n } catch (\\Throwable $e) {\n $this->handleBatchError($e, $objectType, $crmIds);\n }\n }\n\n private function validateBatchSize(string $objectType, array $crmIds): void\n {\n if (count($crmIds) > 100) {\n throw new \\InvalidArgumentException(\"Batch size cannot exceed 100 {$objectType}\");\n }\n }\n\n private function createBatchConfiguration(string $objectType): array\n {\n $configurations = [\n 'deals' => [\n 'batchReadRequest' => new \\HubSpot\\Client\\Crm\\Deals\\Model\\BatchReadInputSimplePublicObjectId(),\n 'inputClass' => \\HubSpot\\Client\\Crm\\Deals\\Model\\SimplePublicObjectId::class,\n 'api' => $this->getNewInstance()->crm()->deals()->batchApi(),\n ],\n 'companies' => [\n 'batchReadRequest' => new \\HubSpot\\Client\\Crm\\Companies\\Model\\BatchReadInputSimplePublicObjectId(),\n 'inputClass' => \\HubSpot\\Client\\Crm\\Companies\\Model\\SimplePublicObjectId::class,\n 'api' => $this->getNewInstance()->crm()->companies()->batchApi(),\n ],\n 'contacts' => [\n 'batchReadRequest' => new \\HubSpot\\Client\\Crm\\Contacts\\Model\\BatchReadInputSimplePublicObjectId(),\n 'inputClass' => \\HubSpot\\Client\\Crm\\Contacts\\Model\\SimplePublicObjectId::class,\n 'api' => $this->getNewInstance()->crm()->contacts()->batchApi(),\n ],\n ];\n\n if (! isset($configurations[$objectType])) {\n throw new \\InvalidArgumentException(\"Unsupported object type: {$objectType}\");\n }\n\n return $configurations[$objectType];\n }\n\n private function prepareBatchRequest(array $batchConfig, array $crmIds, array $fields): object\n {\n $batchReadRequest = $batchConfig['batchReadRequest'];\n $inputClass = $batchConfig['inputClass'];\n\n $inputs = array_map(function ($crmId) use ($inputClass) {\n $input = new $inputClass();\n $input->setId($crmId);\n\n return $input;\n }, $crmIds);\n\n $batchReadRequest->setInputs($inputs);\n $batchReadRequest->setProperties($fields);\n\n return $batchReadRequest;\n }\n\n private function validateApiResponse($response, string $objectType): void\n {\n if (! $response) {\n throw new CrmException(\"HubSpot API returned null response for {$objectType} batch read\");\n }\n }\n\n private function processApiResults($response): array\n {\n $results = [];\n $responseResults = $response->getResults();\n\n if ($responseResults) {\n foreach ($responseResults as $object) {\n if ($object && $object->getId()) {\n $results[$object->getId()] = [\n 'id' => $object->getId(),\n 'properties' => $object->getProperties() ?: [],\n ];\n }\n }\n }\n\n return $results;\n }\n\n private function logBatchResults(string $objectType, array $crmIds, array $results): void\n {\n $this->log->info(\"[HubSpot] Batch fetched {$objectType}\", [\n 'requested_count' => count($crmIds),\n 'returned_count' => count($results),\n 'crm_ids' => $crmIds,\n ]);\n }\n\n private function handleBatchError(\\Throwable $e, string $objectType, array $crmIds): void\n {\n $errorMessage = $e->getMessage() ?: 'Unknown error';\n $errorTrace = $e->getTraceAsString() ?: 'No trace available';\n\n $this->log->error(\"[HubSpot] Failed to batch fetch {$objectType}\", [\n 'crm_ids' => $crmIds,\n 'error' => $errorMessage,\n 'trace' => $errorTrace,\n ]);\n\n throw new CrmException(\"Failed to batch fetch {$objectType}: \" . $errorMessage);\n }\n\n /**\n * Batch read multiple opportunities by their CRM IDs\n *\n * @param array<string> $crmIds Array of HubSpot deal IDs (max 100)\n * @param array<string> $fields Array of property names to fetch\n *\n * @return array<string, array> Array keyed by CRM ID with opportunity data\n */\n public function getOpportunitiesByIds(array $crmIds, array $fields): array\n {\n return $this->batchReadObjects('deals', $crmIds, $fields);\n }\n\n /**\n * Batch read multiple companies by their CRM IDs\n *\n * @param array<string> $crmIds Array of HubSpot company IDs (max 100)\n * @param array<string> $fields Array of property names to fetch\n *\n * @return array<string, array> Array keyed by CRM ID with company data\n */\n public function getCompaniesByIds(array $crmIds, array $fields): array\n {\n return $this->batchReadObjects('companies', $crmIds, $fields);\n }\n\n /**\n * Batch read multiple contacts by their CRM IDs\n *\n * @param array<string> $crmIds Array of HubSpot contact IDs (max 100)\n * @param array<string> $fields Array of property names to fetch\n *\n * @return array<string, array> Array keyed by CRM ID with contact data\n */\n public function getContactsByIds(array $crmIds, array $fields): array\n {\n return $this->batchReadObjects('contacts', $crmIds, $fields);\n }\n\n /**\n * @throws CompanyApiException\n * @throws CrmException\n */\n public function getAccountById(string $crmId, array $fields): array\n {\n try {\n $company = $this->getNewInstance()->crm()->companies()->basicApi()->getById(\n $crmId,\n implode(',', $fields),\n );\n } catch (CompanyApiException $e) {\n $this->log->info('[Hubspot] Failed to fetch account', [\n 'crm_id' => $crmId,\n 'reason' => $e->getMessage(),\n ]);\n\n throw $e;\n }\n\n if (! $company instanceof CompaniesWithAssociations) {\n throw new CrmException('Account not found');\n }\n\n return [\n 'id' => $company->getId(),\n 'properties' => $company->getProperties(),\n ];\n }\n\n /**\n * @throws ContactApiException\n * @throws CrmException\n */\n public function getContactById(string $crmId, array $fields): array\n {\n try {\n $contact = $this->getNewInstance()->crm()->contacts()->basicApi()->getById(\n $crmId,\n implode(',', $fields)\n );\n } catch (ContactApiException $e) {\n $this->log->info('[Hubspot] Failed to fetch contact', [\n 'crm_id' => $crmId,\n 'reason' => $e->getMessage(),\n ]);\n\n throw $e;\n }\n\n if (! $contact instanceof ContactsWithAssociations) {\n throw new CrmException('Contact not found');\n }\n\n return [\n 'id' => $contact->getId(),\n 'properties' => $contact->getProperties(),\n ];\n }\n\n /**\n * This is email search request that Hubspot offers as GET (more generous quota)\n */\n public function getContactByEmail(string $email, array $fields = []): array\n {\n try {\n $contact = $this->getNewInstance()->crm()->contacts()->basicApi()->getById(\n $email,\n implode(',', $fields),\n null,\n false,\n 'email'\n );\n\n return [\n 'id' => $contact->getId(),\n 'properties' => $contact->getProperties(),\n ];\n } catch (ContactApiException $e) {\n $this->log->info('[Hubspot] Failed to fetch contact', [\n 'email' => $email,\n 'reason' => $e->getMessage(),\n ]);\n\n return [];\n }\n }\n\n /**\n * @throws CrmException\n */\n public function fetchProperty(string $objectType, string $propertyId): Property\n {\n $result = $this->getNewInstance()->crm()->properties()->coreApi()->getByName($objectType, $propertyId);\n\n if (! $result instanceof Property) {\n $this->log->error('[Hubspot] Failed to fetch property', [\n 'object_type' => $objectType,\n 'property_id' => $propertyId,\n 'reason' => $result->getMessage(),\n ]);\n\n throw new CrmException('Failed to fetch property');\n }\n\n return $result;\n }\n\n /**\n * @return array<CrmFieldOption>\n */\n public function fetchPropertyOptions(string $objectType, string $propertyId): array\n {\n /** @var array<CrmFieldOption> */\n return $this->fetchProperty($objectType, $propertyId)->getOptions();\n }\n\n /**\n * @return array<array{id:string, label:string, deleted:bool}>\n */\n public function fetchCallDispositions(): array\n {\n /** @var Response $response */\n $response = $this->getInstance()->engagements()->getCallDispositions();\n\n /**\n * @var array<array{\n * id:string,\n * label:string,\n * deleted: bool\n * }>\n */\n return $response->toArray();\n }\n\n /**\n * @return array<CrmFieldOption>\n */\n public function fetchOpportunityPipelineStages(): array\n {\n $stages = [];\n $apiResponse = $this->getNewInstance()->crm()->pipelines()->pipelinesApi()->getAll('deals');\n\n if ($apiResponse instanceof Error) {\n $this->log->error('[Hubspot] Failed to fetch opportunity pipelines', [\n 'reason' => $apiResponse->getMessage(),\n ]);\n\n return [];\n }\n\n foreach ($apiResponse->getResults() as $pipeline) {\n $pipelineStages = array_map(\n static function (PipelineStage $stage) {\n return [\n 'id' => $stage->getId(),\n 'label' => $stage->getLabel(),\n ];\n },\n $pipeline->getStages()\n );\n\n $stages = array_merge($stages, $pipelineStages);\n }\n\n return $stages;\n }\n\n public function fetchOpportunityPipelines(): array\n {\n $pipelines = [];\n\n try {\n $apiResponse = $this->makeRequest('/crm/v3/pipelines/deals');\n } catch (\\Exception $e) {\n $this->log->info('[Hubspot] Failed to fetch opportunity pipelines', [\n 'reason' => $e->getMessage(),\n ]);\n\n return [];\n }\n\n $response = $apiResponse->toArray();\n\n foreach ($response['results'] as $pipeline) {\n $pipelines[] = [\n 'id' => $pipeline['id'],\n 'label' => $pipeline['label'],\n ];\n }\n\n return $pipelines;\n }\n\n /**\n * @return array<CrmFieldOption>\n */\n public function fetchMeetingOutcomeFieldOptions(Field $field): array\n {\n return $field->getCrmProviderId() === 'meetingOutcome'\n ? $this->fetchMeetingOutcomeTypes()\n : $this->fetchCallActivityTypes();\n }\n\n public function fetchMeetingOutcomeTypes(): array\n {\n return $this->extractMeetingTypeOptions(\n 'https://api.hubapi.com/crm/v3/properties/meeting/hs_meeting_outcome'\n );\n }\n\n public function fetchCallActivityTypes(): array\n {\n return $this->extractMeetingTypeOptions(\n 'https://api.hubapi.com/crm/v3/properties/call/hs_activity_type'\n );\n }\n\n private function extractMeetingTypeOptions(string $endpoint): array\n {\n /** @var Response $response */\n $response = $this->getInstance()\n ->getClient()\n ->request('GET', $endpoint);\n\n /**\n * @var array<array{\n * value: string,\n * label: string,\n * displayOrder: int\n * }> $optionData\n */\n $optionData = $response->toArray()['options'] ?? [];\n\n $options = [];\n foreach ($optionData as $item) {\n $options[] = [\n 'id' => $item['value'],\n 'value' => $item['value'],\n 'label' => $item['label'],\n 'display_order' => $item['displayOrder'],\n ];\n }\n\n return $options;\n }\n\n /**\n * @return array<CrmFieldOption>\n */\n public function fetchDispositionFieldOptions(): array\n {\n $options = [];\n\n $dispositions = $this->fetchCallDispositions();\n\n foreach ($dispositions as $disposition) {\n if ($disposition['deleted'] !== false) {\n continue;\n }\n\n $option['value'] = $disposition['id'];\n $option['id'] = $disposition['id'];\n $option['label'] = $disposition['label'];\n\n $options[] = $option;\n }\n\n return $options;\n }\n\n /**\n * @return array<CrmFieldOption>\n */\n public function fetchOpportunityFieldOptions(Field $field): array\n {\n if ($field->isStageField()) {\n return $this->fetchOpportunityPipelineStages();\n }\n\n if ($field->isPipelineField()) {\n return $this->fetchOpportunityPipelines();\n }\n\n return $this->fetchPropertyOptions('deals', $field->getCrmProviderId());\n }\n\n /**\n * @throws BadRequest\n * @throws HubspotException\n */\n public function makeRequest(string $endpoint, $method = 'GET', $payload = [], ?string $queryString = null)\n {\n $endpoint = self::BASE_URL . $endpoint;\n\n if ($method === 'GET') {\n $response = $this->getInstance()->getClient()?->request(\n method: $method,\n endpoint: $endpoint,\n query_string: $queryString\n );\n } else {\n $response = $this->getInstance()->getClient()->request($method, $endpoint, [\n 'json' => ($payload),\n ]);\n }\n\n $max = $response->getHeaderLine('X-HubSpot-RateLimit-Max'); // \"110\"\n $remaining = $response->getHeaderLine('X-HubSpot-RateLimit-Remaining'); // \"109\"\n $interval = $response->getHeaderLine('X-HubSpot-RateLimit-Interval-Milliseconds'); // \"10000\"\n $body = json_decode((string) $response->getBody(), true);\n\n \\Illuminate\\Support\\Facades\\Log::channel('custom_channel')->info('$max ' . PHP_EOL . print_r($max, true));\n \\Illuminate\\Support\\Facades\\Log::channel('custom_channel')->info('$remaining ' . PHP_EOL . print_r($remaining, true));\n \\Illuminate\\Support\\Facades\\Log::channel('custom_channel')->info('$interval ' . PHP_EOL . print_r($interval, true));\n \\Illuminate\\Support\\Facades\\Log::channel('custom_channel')->info('$body ' . PHP_EOL . print_r($body, true));\n\n return $response;\n }\n\n /**\n * @throws BadRequest\n * @throws HubspotException\n */\n public function createMeeting(array $payload): Response\n {\n $endpoint = '/crm/v3/objects/meetings';\n\n return $this->makeRequest($endpoint, 'POST', $payload);\n }\n\n /**\n * @throws BadRequest\n * @throws HubspotException\n */\n public function updateMeeting(string $meetingId, array $payload): Response\n {\n $endpoint = '/crm/v3/objects/meetings/' . $meetingId;\n\n return $this->makeRequest($endpoint, 'PATCH', $payload);\n }\n\n /**\n * @throws \\Exception\n */\n public function createNote(\n string $body,\n string $ownerId,\n int $timestamp,\n string $objectId,\n NoteObject $noteObject\n ): ?string {\n try {\n $noteInput = new SimplePublicObjectInput([\n 'properties' => [\n 'hs_note_body' => $body,\n 'hubspot_owner_id' => $ownerId,\n 'hs_timestamp' => $timestamp,\n ],\n ]);\n\n // Create note\n $note = $this->getNewInstance()->crm()->objects()->basicApi()->create('note', $noteInput);\n\n $this->getNewInstance()->crm()->objects()->associationsApi()->create(\n 'note',\n $note->getId(),\n $this->getNoteObject($noteObject),\n $objectId,\n $this->getNoteAssociationType($noteObject),\n );\n\n return $note->getId();\n } catch (\\Exception $e) {\n $this->log->error('[Hubspot] Failed to create note', [\n 'objectId' => $objectId,\n 'noteObject' => $noteObject->getObjectType(),\n 'reason' => $e->getMessage(),\n ]);\n\n \\Sentry::captureException($e);\n }\n\n return null;\n }\n\n public function updateEngagement(string $objectId, array $engagement, array $metadata): void\n {\n $this->getInstance()->engagements()->update($objectId, $engagement, $metadata);\n }\n\n public function getEngagementData(string $engagementId): array\n {\n $engagement = $this->getInstance()->engagements()->get($engagementId);\n\n return $engagement->toArray();\n }\n\n public function createEngagement(array $engagement, array $associations, array $metadata): Response\n {\n return $this->getInstance()\n ->engagements()\n ->create($engagement, $associations, $metadata);\n }\n\n public function isUnauthorizedException(\\Exception $e): bool\n {\n // Check for specific HubSpot API exception types first\n if ($e instanceof BadRequest) {\n // BadRequest can contain 401 status codes\n return $e->getCode() === 401;\n }\n\n // Check for HTTP client exceptions with status codes\n if ($e instanceof \\GuzzleHttp\\Exception\\RequestException && $e->hasResponse()) {\n $response = $e->getResponse();\n if ($response !== null) {\n return $response->getStatusCode() === 401;\n }\n }\n\n // Check for Guzzle HTTP exceptions\n if ($e instanceof \\GuzzleHttp\\Exception\\ClientException) {\n return $e->getCode() === 401;\n }\n\n // Fallback to string matching as last resort, but be more specific\n $message = strtolower($e->getMessage());\n\n return str_contains($message, '401 unauthorized') ||\n str_contains($message, 'http 401') ||\n str_contains($message, 'status code 401') ||\n (preg_match('/\\b401\\b/', $message) && str_contains($message, 'unauthorized'));\n }\n\n /**\n * Validates and refreshes the access token if needed before API requests.\n * This ensures long-running processes don't fail due to token expiration.\n *\n * @throws SocialAccountTokenInvalidException\n */\n public function ensureValidToken(): void\n {\n if ($this->oauthAccount === null) {\n return;\n }\n\n $newToken = $this->tokenManager->ensureValidToken($this->oauthAccount);\n if ($newToken !== null) {\n $this->accessToken = $newToken;\n }\n }\n\n public function getConfig()\n {\n return $this->config;\n }\n\n // returns only active (archived=false)\n public function getOwners(): array\n {\n return $this->getNewInstance()->crm()->owners()->getAll();\n }\n\n /**\n * @param bool $archived\n *\n * @return array<Owner>|[]\n */\n public function getOwnersArchived(bool $archived = true): array\n {\n $endpoint = '/crm/v3/owners';\n $queryParams = [\n 'archived' => $archived ? 'true' : 'false',\n ];\n $queryString = http_build_query($queryParams);\n\n $owners = [];\n\n try {\n $response = $this->makeRequest(endpoint: $endpoint, queryString: $queryString);\n $responseData = $response?->toArray();\n\n foreach ($responseData['results'] as $result) {\n try {\n $owners[] = Owner::create($result);\n } catch (Throwable $e) {\n $this->log->error('[HubSpot] Failed to process owner data', [\n 'result' => $result,\n 'error' => $e->getMessage(),\n ]);\n\n continue;\n }\n }\n } catch (Throwable $e) {\n $this->log->error('HubSpot] Failed to fetch owners', [\n 'archived' => $archived,\n 'error' => $e->getMessage(),\n ]);\n\n return [];\n }\n\n return $owners;\n }\n\n public function getMeeting(string $engagementId): ObjectWithAssociations\n {\n return $this->getNewInstance()->crm()->objects()->basicApi()\n ->getById('meeting', $engagementId, null, 'contact,company,deal');\n }\n\n public function deleteEngagement(string $engagementId): void\n {\n $this->getInstance()->engagements()->delete((int) $engagementId);\n }\n\n public function getAssociationsData(array $ids, string $fromObject, string $toObject): array\n {\n $associationData = [];\n $idChunks = array_chunk($ids, self::ASSOCIATIONS_BATCH_SIZE_LIMIT);\n\n foreach ($idChunks as $idChunk) {\n try {\n $batchInput = new \\HubSpot\\Client\\Crm\\Associations\\Model\\BatchInputPublicObjectId();\n $batchInput->setInputs(array_map(function ($id) {\n $publicObjectId = new \\HubSpot\\Client\\Crm\\Associations\\Model\\PublicObjectId();\n $publicObjectId->setId($id);\n\n return $publicObjectId;\n }, $idChunk));\n\n $associatedObjectsData = $this\n ->getNewInstance()\n ->crm()\n ->associations()\n ->batchApi()\n ->read($fromObject, $toObject, $batchInput);\n\n if ($associatedObjectsData instanceof \\HubSpot\\Client\\Crm\\Associations\\Model\\BatchResponsePublicAssociationMulti) {\n foreach ($associatedObjectsData->getResults() as $association) {\n $from = $association->getFrom()->getId();\n $toAssociations = $association->getTo();\n\n if (! empty($toAssociations)) {\n $associationData[$from] = array_map(function ($item) {\n return $item->getId();\n }, $toAssociations);\n }\n }\n }\n } catch (\\Exception $e) {\n $this->log->error('[Hubspot] Failed to fetch associations', [\n 'from_object' => $fromObject,\n 'to_object' => $toObject,\n 'reason' => $e->getMessage(),\n ]);\n }\n }\n\n return $associationData;\n }\n\n /**\n * @throws \\Exception\n */\n private function getNoteAssociationType(NoteObject $noteObject): string\n {\n return match($noteObject) {\n NoteObject::Opportunity => 'note_to_deal',\n NoteObject::Lead, NoteObject::Contact => 'note_to_contact', // or 'note_to_lead' if your portal supports it\n NoteObject::Account => 'note_to_company',\n NoteObject::Call, NoteObject::Event => throw new \\Exception('Not supported'),\n };\n }\n\n /**\n * @throws \\Exception\n */\n private function getNoteObject(NoteObject $noteObject): string\n {\n return match($noteObject) {\n NoteObject::Opportunity => 'deal',\n NoteObject::Lead, NoteObject::Contact => 'contact',\n NoteObject::Account => 'company',\n NoteObject::Call, NoteObject::Event => throw new \\Exception('Not supported'),\n };\n }\n\n public function addAssociations(string $objectType, string $associationType, array $payload): Response\n {\n $endpoint = \"/crm/v4/associations/$objectType/$associationType/batch/create\";\n\n return $this->makeRequest($endpoint, 'POST', $payload);\n }\n\n public function removeAssociations(string $objectType, string $associationType, array $payload): Response\n {\n $endpoint = \"/crm/v4/associations/$objectType/$associationType/batch/archive\";\n\n return $this->makeRequest($endpoint, 'POST', $payload);\n }\n}","role_description":"text entry area","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"Project","depth":3,"on_screen":false,"role_description":"text"},{"role":"AXButton","text":"Project","depth":3,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"New File or Directory…","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.018055556,"height":0.026666667},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Expand Selected","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.018055556,"height":0.026666667},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Collapse All","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.018055556,"height":0.026666667},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Options","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.018055556,"height":0.026666667},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Hide","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.018055556,"height":0.026666667},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false}]...
|
291277805985683123
|
6378757184196315236
|
click
|
accessibility
|
NULL
|
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
2
60
Previous Highlighted Error
Next Highlighted Error
<?php
declare(strict_types=1);
namespace Jiminny\Services\Crm\Hubspot;
use HubSpot\Client\Crm\Deals\ApiException as DealApiException;
use HubSpot\Client\Crm\Contacts\ApiException as ContactApiException;
use HubSpot\Client\Crm\Companies\ApiException as CompanyApiException;
use HubSpot\Client\Crm\Contacts\Model\SimplePublicObjectWithAssociations as ContactsWithAssociations;
use HubSpot\Client\Crm\Companies\Model\SimplePublicObjectWithAssociations as CompaniesWithAssociations;
use HubSpot\Client\Crm\Deals\Model\SimplePublicObjectWithAssociations as DealWithAssociations;
use HubSpot\Client\Crm\Objects\Model\SimplePublicObjectInput;
use HubSpot\Client\Crm\Objects\Model\SimplePublicObjectWithAssociations as ObjectWithAssociations;
use HubSpot\Client\Crm\Pipelines\Model\Error;
use HubSpot\Client\Crm\Pipelines\Model\PipelineStage;
use HubSpot\Client\Crm\Properties\Model\Property;
use HubSpot\Discovery\Discovery;
use Jiminny\Component\Utility\Service\ProviderRateLimiter;
use Jiminny\Exceptions\CrmException;
use Jiminny\Exceptions\RateLimitException;
use Jiminny\Exceptions\SocialAccountTokenInvalidException;
use Jiminny\Jobs\Crm\NoteObject;
use Jiminny\Models\Crm\Field;
use Jiminny\Services\Crm\BaseClient;
use Jiminny\Services\Crm\Hubspot\DTO\Response\Owner;
use Jiminny\Services\SocialAccountService;
use SevenShores\Hubspot\Exceptions\BadRequest;
use SevenShores\Hubspot\Exceptions\HubspotException;
use SevenShores\Hubspot\Factory;
use SevenShores\Hubspot\Http\Response;
use Jiminny\Services\Crm\Hubspot\Pagination\HubspotPaginationService;
use Throwable;
/**
* @phpstan-type CrmFieldOption array{id:string, label:string, value?:string}
*/
class Client extends BaseClient implements HubspotClientInterface
{
public const string MIN_API_VERSION = '2';
public const string BASE_URL = '[URL_WITH_CREDENTIALS] T
* @param callable(): T $apiCall
* @return T
*
* @throws RateLimitException
*/
private function executeRequest(callable $apiCall)
{
if (! $this->rateLimiter->canMakeRequest($this->config)) {
$retryAfter = $this->rateLimiter->requestAvailableIn($this->config);
$this->log->warning('[Hubspot] Rate limit exceeded, deferring request', [
'team_id' => $this->config->team_id,
'config_id' => $this->config->getId(),
'retry_after' => $retryAfter,
]);
throw new RateLimitException(
'Hubspot rate limit reached for configuration ' . $this->config->getId(),
$retryAfter,
);
}
$this->rateLimiter->incrementRequestCount($this->config);
try {
return $apiCall();
} catch (Throwable $e) {
if ($this->isHubspotRateLimit($e)) {
$retryAfter = $this->parseRetryAfter($e);
$this->log->warning('[Hubspot] Received 429 from API', [
'team_id' => $this->config->team_id,
'config_id' => $this->config->getId(),
'retry_after' => $retryAfter,
'reason' => $e->getMessage(),
]);
throw new RateLimitException('Hubspot returned 429', $retryAfter, $e);
}
throw $e;
}
}
private function isHubspotRateLimit(Throwable $e): bool
{
return method_exists($e, 'getCode') && (int) $e->getCode() === 429;
}
private function parseRetryAfter(Throwable $e): int
{
if (method_exists($e, 'getResponseHeaders')) {
$headers = $e->getResponseHeaders() ?: [];
$value = $headers['Retry-After'] ?? $headers['retry-after'] ?? null;
if (is_array($value)) {
$value = $value[0] ?? null;
}
if (is_numeric($value)) {
return (int) $value;
}
}
return 10;
}
public function getMinimumApiVersion(): string
{
return self::MIN_API_VERSION;
}
public function getInstance(): Factory
{
return new Factory([
'key' => $this->accessToken,
'oauth2' => true,
'base_url' => $this->baseUrl,
]);
}
public function getNewInstance(): Discovery
{
return \HubSpot\Factory::createWithAccessToken($this->accessToken);
}
/**
* Secondly and daily limits for Hubspot API
*
* Product Tier: Free & Starter | Professional & Enterprise | API add-on (any tier)
* Burst: 100/10 seconds | 150/10 seconds | 200/10 seconds
* Daily: 250,000 | 500,000 | 1,000,000
*
* Official documentation states: The search endpoints are rate limited to five requests per second.
* Since with 5 RPS were still hitting secondly rate limits we lowered it to 4
*/
public function getPaginatedData(array $payload, string $type, int $offset = 0): array
{
$total = 0;
$lastId = null;
$rows = [];
foreach ($this->getPaginatedDataGenerator($payload, $type, $offset, $total, $lastId) as $row) {
$rows[] = $row;
}
return ['results' => $rows, 'total' => $total, 'last_record' => $lastId];
}
/**
* @throws HubspotException
* @throws SocialAccountTokenInvalidException
* @throws BadRequest
*/
public function getPaginatedDataGenerator(
array $payload,
string $type,
int $offset = 0,
int &$total = 0,
?string &$lastRecordId = null
): \Generator {
return $this->paginationService->getPaginatedDataGenerator(
$this,
$payload,
$type,
$offset,
$total,
$lastRecordId
);
}
/**
* @throws DealApiException
* @throws CrmException
*/
public function getOpportunityById(string $crmId, array $fields): array
{
try {
$deal = $this->executeRequest(fn () => $this->getNewInstance()->crm()->deals()->basicApi()->getById(
$crmId,
implode(',', $fields),
'companies,contacts'
));
} catch (DealApiException $e) {
$this->log->info('[Hubspot] Failed to fetch opportunity', [
'crm_id' => $crmId,
'reason' => $e->getMessage(),
]);
throw $e;
}
if (! $deal instanceof DealWithAssociations) {
throw new CrmException('Deal not found');
}
return [
'id' => $deal->getId(),
'properties' => $deal->getProperties(),
'associations' => $deal->getAssociations(),
];
}
/**
* Generic batch read method for HubSpot objects
*
* @param string $objectType The object type ('deals', 'companies', 'contacts')
* @param array<string> $crmIds Array of HubSpot object IDs (max 100)
* @param array<string> $fields Array of property names to fetch
*
* @return array<string, array> Array keyed by CRM ID with object data
*/
private function batchReadObjects(string $objectType, array $crmIds, array $fields): array
{
if (empty($crmIds)) {
return [];
}
$this->validateBatchSize($objectType, $crmIds);
$this->ensureValidToken();
try {
$batchConfig = $this->createBatchConfiguration($objectType);
$batchReadRequest = $this->prepareBatchRequest($batchConfig, $crmIds, $fields);
$response = $batchConfig['api']->read($batchReadRequest);
$this->validateApiResponse($response, $objectType);
$results = $this->processApiResults($response);
$this->logBatchResults($objectType, $crmIds, $results);
return $results;
} catch (\Throwable $e) {
$this->handleBatchError($e, $objectType, $crmIds);
}
}
private function validateBatchSize(string $objectType, array $crmIds): void
{
if (count($crmIds) > 100) {
throw new \InvalidArgumentException("Batch size cannot exceed 100 {$objectType}");
}
}
private function createBatchConfiguration(string $objectType): array
{
$configurations = [
'deals' => [
'batchReadRequest' => new \HubSpot\Client\Crm\Deals\Model\BatchReadInputSimplePublicObjectId(),
'inputClass' => \HubSpot\Client\Crm\Deals\Model\SimplePublicObjectId::class,
'api' => $this->getNewInstance()->crm()->deals()->batchApi(),
],
'companies' => [
'batchReadRequest' => new \HubSpot\Client\Crm\Companies\Model\BatchReadInputSimplePublicObjectId(),
'inputClass' => \HubSpot\Client\Crm\Companies\Model\SimplePublicObjectId::class,
'api' => $this->getNewInstance()->crm()->companies()->batchApi(),
],
'contacts' => [
'batchReadRequest' => new \HubSpot\Client\Crm\Contacts\Model\BatchReadInputSimplePublicObjectId(),
'inputClass' => \HubSpot\Client\Crm\Contacts\Model\SimplePublicObjectId::class,
'api' => $this->getNewInstance()->crm()->contacts()->batchApi(),
],
];
if (! isset($configurations[$objectType])) {
throw new \InvalidArgumentException("Unsupported object type: {$objectType}");
}
return $configurations[$objectType];
}
private function prepareBatchRequest(array $batchConfig, array $crmIds, array $fields): object
{
$batchReadRequest = $batchConfig['batchReadRequest'];
$inputClass = $batchConfig['inputClass'];
$inputs = array_map(function ($crmId) use ($inputClass) {
$input = new $inputClass();
$input->setId($crmId);
return $input;
}, $crmIds);
$batchReadRequest->setInputs($inputs);
$batchReadRequest->setProperties($fields);
return $batchReadRequest;
}
private function validateApiResponse($response, string $objectType): void
{
if (! $response) {
throw new CrmException("HubSpot API returned null response for {$objectType} batch read");
}
}
private function processApiResults($response): array
{
$results = [];
$responseResults = $response->getResults();
if ($responseResults) {
foreach ($responseResults as $object) {
if ($object && $object->getId()) {
$results[$object->getId()] = [
'id' => $object->getId(),
'properties' => $object->getProperties() ?: [],
];
}
}
}
return $results;
}
private function logBatchResults(string $objectType, array $crmIds, array $results): void
{
$this->log->info("[HubSpot] Batch fetched {$objectType}", [
'requested_count' => count($crmIds),
'returned_count' => count($results),
'crm_ids' => $crmIds,
]);
}
private function handleBatchError(\Throwable $e, string $objectType, array $crmIds): void
{
$errorMessage = $e->getMessage() ?: 'Unknown error';
$errorTrace = $e->getTraceAsString() ?: 'No trace available';
$this->log->error("[HubSpot] Failed to batch fetch {$objectType}", [
'crm_ids' => $crmIds,
'error' => $errorMessage,
'trace' => $errorTrace,
]);
throw new CrmException("Failed to batch fetch {$objectType}: " . $errorMessage);
}
/**
* Batch read multiple opportunities by their CRM IDs
*
* @param array<string> $crmIds Array of HubSpot deal IDs (max 100)
* @param array<string> $fields Array of property names to fetch
*
* @return array<string, array> Array keyed by CRM ID with opportunity data
*/
public function getOpportunitiesByIds(array $crmIds, array $fields): array
{
return $this->batchReadObjects('deals', $crmIds, $fields);
}
/**
* Batch read multiple companies by their CRM IDs
*
* @param array<string> $crmIds Array of HubSpot company IDs (max 100)
* @param array<string> $fields Array of property names to fetch
*
* @return array<string, array> Array keyed by CRM ID with company data
*/
public function getCompaniesByIds(array $crmIds, array $fields): array
{
return $this->batchReadObjects('companies', $crmIds, $fields);
}
/**
* Batch read multiple contacts by their CRM IDs
*
* @param array<string> $crmIds Array of HubSpot contact IDs (max 100)
* @param array<string> $fields Array of property names to fetch
*
* @return array<string, array> Array keyed by CRM ID with contact data
*/
public function getContactsByIds(array $crmIds, array $fields): array
{
return $this->batchReadObjects('contacts', $crmIds, $fields);
}
/**
* @throws CompanyApiException
* @throws CrmException
*/
public function getAccountById(string $crmId, array $fields): array
{
try {
$company = $this->getNewInstance()->crm()->companies()->basicApi()->getById(
$crmId,
implode(',', $fields),
);
} catch (CompanyApiException $e) {
$this->log->info('[Hubspot] Failed to fetch account', [
'crm_id' => $crmId,
'reason' => $e->getMessage(),
]);
throw $e;
}
if (! $company instanceof CompaniesWithAssociations) {
throw new CrmException('Account not found');
}
return [
'id' => $company->getId(),
'properties' => $company->getProperties(),
];
}
/**
* @throws ContactApiException
* @throws CrmException
*/
public function getContactById(string $crmId, array $fields): array
{
try {
$contact = $this->getNewInstance()->crm()->contacts()->basicApi()->getById(
$crmId,
implode(',', $fields)
);
} catch (ContactApiException $e) {
$this->log->info('[Hubspot] Failed to fetch contact', [
'crm_id' => $crmId,
'reason' => $e->getMessage(),
]);
throw $e;
}
if (! $contact instanceof ContactsWithAssociations) {
throw new CrmException('Contact not found');
}
return [
'id' => $contact->getId(),
'properties' => $contact->getProperties(),
];
}
/**
* This is email search request that Hubspot offers as GET (more generous quota)
*/
public function getContactByEmail(string $email, array $fields = []): array
{
try {
$contact = $this->getNewInstance()->crm()->contacts()->basicApi()->getById(
$email,
implode(',', $fields),
null,
false,
'email'
);
return [
'id' => $contact->getId(),
'properties' => $contact->getProperties(),
];
} catch (ContactApiException $e) {
$this->log->info('[Hubspot] Failed to fetch contact', [
'email' => $email,
'reason' => $e->getMessage(),
]);
return [];
}
}
/**
* @throws CrmException
*/
public function fetchProperty(string $objectType, string $propertyId): Property
{
$result = $this->getNewInstance()->crm()->properties()->coreApi()->getByName($objectType, $propertyId);
if (! $result instanceof Property) {
$this->log->error('[Hubspot] Failed to fetch property', [
'object_type' => $objectType,
'property_id' => $propertyId,
'reason' => $result->getMessage(),
]);
throw new CrmException('Failed to fetch property');
}
return $result;
}
/**
* @return array<CrmFieldOption>
*/
public function fetchPropertyOptions(string $objectType, string $propertyId): array
{
/** @var array<CrmFieldOption> */
return $this->fetchProperty($objectType, $propertyId)->getOptions();
}
/**
* @return array<array{id:string, label:string, deleted:bool}>
*/
public function fetchCallDispositions(): array
{
/** @var Response $response */
$response = $this->getInstance()->engagements()->getCallDispositions();
/**
* @var array<array{
* id:string,
* label:string,
* deleted: bool
* }>
*/
return $response->toArray();
}
/**
* @return array<CrmFieldOption>
*/
public function fetchOpportunityPipelineStages(): array
{
$stages = [];
$apiResponse = $this->getNewInstance()->crm()->pipelines()->pipelinesApi()->getAll('deals');
if ($apiResponse instanceof Error) {
$this->log->error('[Hubspot] Failed to fetch opportunity pipelines', [
'reason' => $apiResponse->getMessage(),
]);
return [];
}
foreach ($apiResponse->getResults() as $pipeline) {
$pipelineStages = array_map(
static function (PipelineStage $stage) {
return [
'id' => $stage->getId(),
'label' => $stage->getLabel(),
];
},
$pipeline->getStages()
);
$stages = array_merge($stages, $pipelineStages);
}
return $stages;
}
public function fetchOpportunityPipelines(): array
{
$pipelines = [];
try {
$apiResponse = $this->makeRequest('/crm/v3/pipelines/deals');
} catch (\Exception $e) {
$this->log->info('[Hubspot] Failed to fetch opportunity pipelines', [
'reason' => $e->getMessage(),
]);
return [];
}
$response = $apiResponse->toArray();
foreach ($response['results'] as $pipeline) {
$pipelines[] = [
'id' => $pipeline['id'],
'label' => $pipeline['label'],
];
}
return $pipelines;
}
/**
* @return array<CrmFieldOption>
*/
public function fetchMeetingOutcomeFieldOptions(Field $field): array
{
return $field->getCrmProviderId() === 'meetingOutcome'
? $this->fetchMeetingOutcomeTypes()
: $this->fetchCallActivityTypes();
}
public function fetchMeetingOutcomeTypes(): array
{
return $this->extractMeetingTypeOptions(
'[URL_WITH_CREDENTIALS] Response $response */
$response = $this->getInstance()
->getClient()
->request('GET', $endpoint);
/**
* @var array<array{
* value: string,
* label: string,
* displayOrder: int
* }> $optionData
*/
$optionData = $response->toArray()['options'] ?? [];
$options = [];
foreach ($optionData as $item) {
$options[] = [
'id' => $item['value'],
'value' => $item['value'],
'label' => $item['label'],
'display_order' => $item['displayOrder'],
];
}
return $options;
}
/**
* @return array<CrmFieldOption>
*/
public function fetchDispositionFieldOptions(): array
{
$options = [];
$dispositions = $this->fetchCallDispositions();
foreach ($dispositions as $disposition) {
if ($disposition['deleted'] !== false) {
continue;
}
$option['value'] = $disposition['id'];
$option['id'] = $disposition['id'];
$option['label'] = $disposition['label'];
$options[] = $option;
}
return $options;
}
/**
* @return array<CrmFieldOption>
*/
public function fetchOpportunityFieldOptions(Field $field): array
{
if ($field->isStageField()) {
return $this->fetchOpportunityPipelineStages();
}
if ($field->isPipelineField()) {
return $this->fetchOpportunityPipelines();
}
return $this->fetchPropertyOptions('deals', $field->getCrmProviderId());
}
/**
* @throws BadRequest
* @throws HubspotException
*/
public function makeRequest(string $endpoint, $method = 'GET', $payload = [], ?string $queryString = null)
{
$endpoint = self::BASE_URL . $endpoint;
if ($method === 'GET') {
$response = $this->getInstance()->getClient()?->request(
method: $method,
endpoint: $endpoint,
query_string: $queryString
);
} else {
$response = $this->getInstance()->getClient()->request($method, $endpoint, [
'json' => ($payload),
]);
}
$max = $response->getHeaderLine('X-HubSpot-RateLimit-Max'); // "110"
$remaining = $response->getHeaderLine('X-HubSpot-RateLimit-Remaining'); // "109"
$interval = $response->getHeaderLine('X-HubSpot-RateLimit-Interval-Milliseconds'); // "10000"
$body = json_decode((string) $response->getBody(), true);
\Illuminate\Support\Facades\Log::channel('custom_channel')->info('$max ' . PHP_EOL . print_r($max, true));
\Illuminate\Support\Facades\Log::channel('custom_channel')->info('$remaining ' . PHP_EOL . print_r($remaining, true));
\Illuminate\Support\Facades\Log::channel('custom_channel')->info('$interval ' . PHP_EOL . print_r($interval, true));
\Illuminate\Support\Facades\Log::channel('custom_channel')->info('$body ' . PHP_EOL . print_r($body, true));
return $response;
}
/**
* @throws BadRequest
* @throws HubspotException
*/
public function createMeeting(array $payload): Response
{
$endpoint = '/crm/v3/objects/meetings';
return $this->makeRequest($endpoint, 'POST', $payload);
}
/**
* @throws BadRequest
* @throws HubspotException
*/
public function updateMeeting(string $meetingId, array $payload): Response
{
$endpoint = '/crm/v3/objects/meetings/' . $meetingId;
return $this->makeRequest($endpoint, 'PATCH', $payload);
}
/**
* @throws \Exception
*/
public function createNote(
string $body,
string $ownerId,
int $timestamp,
string $objectId,
NoteObject $noteObject
): ?string {
try {
$noteInput = new SimplePublicObjectInput([
'properties' => [
'hs_note_body' => $body,
'hubspot_owner_id' => $ownerId,
'hs_timestamp' => $timestamp,
],
]);
// Create note
$note = $this->getNewInstance()->crm()->objects()->basicApi()->create('note', $noteInput);
$this->getNewInstance()->crm()->objects()->associationsApi()->create(
'note',
$note->getId(),
$this->getNoteObject($noteObject),
$objectId,
$this->getNoteAssociationType($noteObject),
);
return $note->getId();
} catch (\Exception $e) {
$this->log->error('[Hubspot] Failed to create note', [
'objectId' => $objectId,
'noteObject' => $noteObject->getObjectType(),
'reason' => $e->getMessage(),
]);
\Sentry::captureException($e);
}
return null;
}
public function updateEngagement(string $objectId, array $engagement, array $metadata): void
{
$this->getInstance()->engagements()->update($objectId, $engagement, $metadata);
}
public function getEngagementData(string $engagementId): array
{
$engagement = $this->getInstance()->engagements()->get($engagementId);
return $engagement->toArray();
}
public function createEngagement(array $engagement, array $associations, array $metadata): Response
{
return $this->getInstance()
->engagements()
->create($engagement, $associations, $metadata);
}
public function isUnauthorizedException(\Exception $e): bool
{
// Check for specific HubSpot API exception types first
if ($e instanceof BadRequest) {
// BadRequest can contain 401 status codes
return $e->getCode() === 401;
}
// Check for HTTP client exceptions with status codes
if ($e instanceof \GuzzleHttp\Exception\RequestException && $e->hasResponse()) {
$response = $e->getResponse();
if ($response !== null) {
return $response->getStatusCode() === 401;
}
}
// Check for Guzzle HTTP exceptions
if ($e instanceof \GuzzleHttp\Exception\ClientException) {
return $e->getCode() === 401;
}
// Fallback to string matching as last resort, but be more specific
$message = strtolower($e->getMessage());
return str_contains($message, '401 unauthorized') ||
str_contains($message, 'http 401') ||
str_contains($message, 'status code 401') ||
(preg_match('/\b401\b/', $message) && str_contains($message, 'unauthorized'));
}
/**
* Validates and refreshes the access token if needed before API requests.
* This ensures long-running processes don't fail due to token expiration.
*
* @throws SocialAccountTokenInvalidException
*/
public function ensureValidToken(): void
{
if ($this->oauthAccount === null) {
return;
}
$newToken = $this->tokenManager->ensureValidToken($this->oauthAccount);
if ($newToken !== null) {
$this->accessToken = $newToken;
}
}
public function getConfig()
{
return $this->config;
}
// returns only active (archived=false)
public function getOwners(): array
{
return $this->getNewInstance()->crm()->owners()->getAll();
}
/**
* @param bool $archived
*
* @return array<Owner>|[]
*/
public function getOwnersArchived(bool $archived = true): array
{
$endpoint = '/crm/v3/owners';
$queryParams = [
'archived' => $archived ? 'true' : 'false',
];
$queryString = http_build_query($queryParams);
$owners = [];
try {
$response = $this->makeRequest(endpoint: $endpoint, queryString: $queryString);
$responseData = $response?->toArray();
foreach ($responseData['results'] as $result) {
try {
$owners[] = Owner::create($result);
} catch (Throwable $e) {
$this->log->error('[HubSpot] Failed to process owner data', [
'result' => $result,
'error' => $e->getMessage(),
]);
continue;
}
}
} catch (Throwable $e) {
$this->log->error('HubSpot] Failed to fetch owners', [
'archived' => $archived,
'error' => $e->getMessage(),
]);
return [];
}
return $owners;
}
public function getMeeting(string $engagementId): ObjectWithAssociations
{
return $this->getNewInstance()->crm()->objects()->basicApi()
->getById('meeting', $engagementId, null, 'contact,company,deal');
}
public function deleteEngagement(string $engagementId): void
{
$this->getInstance()->engagements()->delete((int) $engagementId);
}
public function getAssociationsData(array $ids, string $fromObject, string $toObject): array
{
$associationData = [];
$idChunks = array_chunk($ids, self::ASSOCIATIONS_BATCH_SIZE_LIMIT);
foreach ($idChunks as $idChunk) {
try {
$batchInput = new \HubSpot\Client\Crm\Associations\Model\BatchInputPublicObjectId();
$batchInput->setInputs(array_map(function ($id) {
$publicObjectId = new \HubSpot\Client\Crm\Associations\Model\PublicObjectId();
$publicObjectId->setId($id);
return $publicObjectId;
}, $idChunk));
$associatedObjectsData = $this
->getNewInstance()
->crm()
->associations()
->batchApi()
->read($fromObject, $toObject, $batchInput);
if ($associatedObjectsData instanceof \HubSpot\Client\Crm\Associations\Model\BatchResponsePublicAssociationMulti) {
foreach ($associatedObjectsData->getResults() as $association) {
$from = $association->getFrom()->getId();
$toAssociations = $association->getTo();
if (! empty($toAssociations)) {
$associationData[$from] = array_map(function ($item) {
return $item->getId();
}, $toAssociations);
}
}
}
} catch (\Exception $e) {
$this->log->error('[Hubspot] Failed to fetch associations', [
'from_object' => $fromObject,
'to_object' => $toObject,
'reason' => $e->getMessage(),
]);
}
}
return $associationData;
}
/**
* @throws \Exception
*/
private function getNoteAssociationType(NoteObject $noteObject): string
{
return match($noteObject) {
NoteObject::Opportunity => 'note_to_deal',
NoteObject::Lead, NoteObject::Contact => 'note_to_contact', // or 'note_to_lead' if your portal supports it
NoteObject::Account => 'note_to_company',
NoteObject::Call, NoteObject::Event => throw new \Exception('Not supported'),
};
}
/**
* @throws \Exception
*/
private function getNoteObject(NoteObject $noteObject): string
{
return match($noteObject) {
NoteObject::Opportunity => 'deal',
NoteObject::Lead, NoteObject::Contact => 'contact',
NoteObject::Account => 'company',
NoteObject::Call, NoteObject::Event => throw new \Exception('Not supported'),
};
}
public function addAssociations(string $objectType, string $associationType, array $payload): Response
{
$endpoint = "/crm/v4/associations/$objectType/$associationType/batch/create";
return $this->makeRequest($endpoint, 'POST', $payload);
}
public function removeAssociations(string $objectType, string $associationType, array $payload): Response
{
$endpoint = "/crm/v4/associations/$objectType/$associationType/batch/archive";
return $this->makeRequest($endpoint, 'POST', $payload);
}
}
Project
Project
New File or Directory…
Expand Selected
Collapse All
Options
Hide...
|
2709
|
NULL
|
NULL
|
NULL
|
|
2713
|
112
|
29
|
2026-05-07T11:36:42.276314+00:00
|
/Users/lukas/.screenpipe/data/data/2026-05-07/1778 /Users/lukas/.screenpipe/data/data/2026-05-07/1778153802276_m2.jpg...
|
iTerm2
|
DEV (docker)
|
True
|
NULL
|
monitor_2
|
NULL
|
NULL
|
NULL
|
NULL
|
Last login: Thu May 7 09:44:56 on ttys006
Poetry Last login: Thu May 7 09:44:56 on ttys006
Poetry could not find a pyproject.toml file in /Users/lukas/jiminny/app or its parents
Poetry could not find a pyproject.toml file in /Users/lukas/jiminny/app or its parents
lukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/jiminny/app (JY-20773-fix-automated-reports-user-pilot-tracking) $ dev
root@docker_lamp_1:/home/jiminny# php artisan crm:sync-opportunity --teamId=2 --from='2026-05-01 00:00:00'
Syncing opportunity for Hubspot
Your HubSpot account has become disconnected. Please login to Jiminny to reconnect. skipping...
root@docker_lamp_1:/home/jiminny# php artisan jiminny:token-info -A 1499 -R
----------------------------------------------------------------------------------------------------
access_token => CNeR-JHgMxIZQlNQMl8kQEwrAgwACAkUAhIJBB4BAQEDBxiCiYwCIN7Y_Qwo0qwCMhTnG549n-YtNuc1jgj-2AsLPSmw3DoyQlNQMl8kQEwrAiUACBkGawEFThwBARIBAQEEATEEAQEBAQEBAQEBAQUBEggBAQEBAYlCFPAsBNZxoDp5kAcRyeBlQoE5SM7DSgNuYTFSAFoAYABo3tj9DHAAeAA
----------------------------------------------------------------------------------------------------
access_token_expires_at => 2026-05-07 11:41:20
----------------------------------------------------------------------------------------------------
refresh_token => d5ab04e2-2109-4c0b-b513-8cba1dd54371
----------------------------------------------------------------------------------------------------
refresh_token_expires_at =>
----------------------------------------------------------------------------------------------------
root@docker_lamp_1:/home/jiminny# php artisan crm:sync-opportunity --teamId=2 --from='2026-05-01 00:00:00'
DOCKER
Close Tab
DEV (docker)
Close Tab
APP (-zsh)
Close Tab
-zsh
Close Tab
screenpipe"
Close Tab
-zsh
Close Tab
⌥⌘1
DEV (docker)...
|
[{"role":"AXTextArea","text [{"role":"AXTextArea","text":"Last login: Thu May 7 09:44:56 on ttys006\n\nPoetry could not find a pyproject.toml file in /Users/lukas/jiminny/app or its parents\n\nPoetry could not find a pyproject.toml file in /Users/lukas/jiminny/app or its parents\nlukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/jiminny/app (JY-20773-fix-automated-reports-user-pilot-tracking) $ dev\nroot@docker_lamp_1:/home/jiminny# php artisan crm:sync-opportunity --teamId=2 --from='2026-05-01 00:00:00'\nSyncing opportunity for Hubspot\nYour HubSpot account has become disconnected. Please login to Jiminny to reconnect. skipping...\nroot@docker_lamp_1:/home/jiminny# php artisan jiminny:token-info -A 1499 -R\n----------------------------------------------------------------------------------------------------\naccess_token => CNeR-JHgMxIZQlNQMl8kQEwrAgwACAkUAhIJBB4BAQEDBxiCiYwCIN7Y_Qwo0qwCMhTnG549n-YtNuc1jgj-2AsLPSmw3DoyQlNQMl8kQEwrAiUACBkGawEFThwBARIBAQEEATEEAQEBAQEBAQEBAQUBEggBAQEBAYlCFPAsBNZxoDp5kAcRyeBlQoE5SM7DSgNuYTFSAFoAYABo3tj9DHAAeAA\n----------------------------------------------------------------------------------------------------\naccess_token_expires_at => 2026-05-07 11:41:20\n----------------------------------------------------------------------------------------------------\nrefresh_token => d5ab04e2-2109-4c0b-b513-8cba1dd54371\n----------------------------------------------------------------------------------------------------\nrefresh_token_expires_at => \n----------------------------------------------------------------------------------------------------\nroot@docker_lamp_1:/home/jiminny# php artisan crm:sync-opportunity --teamId=2 --from='2026-05-01 00:00:00'","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.4800532,"height":-0.06304872},"on_screen":true,"value":"Last login: Thu May 7 09:44:56 on ttys006\n\nPoetry could not find a pyproject.toml file in /Users/lukas/jiminny/app or its parents\n\nPoetry could not find a pyproject.toml file in /Users/lukas/jiminny/app or its parents\nlukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/jiminny/app (JY-20773-fix-automated-reports-user-pilot-tracking) $ dev\nroot@docker_lamp_1:/home/jiminny# php artisan crm:sync-opportunity --teamId=2 --from='2026-05-01 00:00:00'\nSyncing opportunity for Hubspot\nYour HubSpot account has become disconnected. Please login to Jiminny to reconnect. skipping...\nroot@docker_lamp_1:/home/jiminny# php artisan jiminny:token-info -A 1499 -R\n----------------------------------------------------------------------------------------------------\naccess_token => CNeR-JHgMxIZQlNQMl8kQEwrAgwACAkUAhIJBB4BAQEDBxiCiYwCIN7Y_Qwo0qwCMhTnG549n-YtNuc1jgj-2AsLPSmw3DoyQlNQMl8kQEwrAiUACBkGawEFThwBARIBAQEEATEEAQEBAQEBAQEBAQUBEggBAQEBAYlCFPAsBNZxoDp5kAcRyeBlQoE5SM7DSgNuYTFSAFoAYABo3tj9DHAAeAA\n----------------------------------------------------------------------------------------------------\naccess_token_expires_at => 2026-05-07 11:41:20\n----------------------------------------------------------------------------------------------------\nrefresh_token => d5ab04e2-2109-4c0b-b513-8cba1dd54371\n----------------------------------------------------------------------------------------------------\nrefresh_token_expires_at => \n----------------------------------------------------------------------------------------------------\nroot@docker_lamp_1:/home/jiminny# php artisan crm:sync-opportunity --teamId=2 --from='2026-05-01 00:00:00'","is_focused":true},{"role":"AXRadioButton","text":"DOCKER","depth":2,"bounds":{"left":0.27027926,"top":1.0,"width":0.0787899,"height":-0.042298436},"on_screen":true,"role_description":"radio button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Close Tab","depth":3,"bounds":{"left":0.27227393,"top":1.0,"width":0.005319149,"height":-0.04549086},"on_screen":true,"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXRadioButton","text":"DEV (docker)","depth":2,"bounds":{"left":0.34906915,"top":1.0,"width":0.0787899,"height":-0.042298436},"on_screen":true,"role_description":"radio button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Close Tab","depth":3,"bounds":{"left":0.35106382,"top":1.0,"width":0.005319149,"height":-0.04549086},"on_screen":true,"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXRadioButton","text":"APP (-zsh)","depth":2,"bounds":{"left":0.42785904,"top":1.0,"width":0.07862367,"height":-0.042298436},"on_screen":true,"role_description":"radio button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Close Tab","depth":3,"bounds":{"left":0.42985374,"top":1.0,"width":0.005319149,"height":-0.04549086},"on_screen":true,"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXRadioButton","text":"-zsh","depth":2,"bounds":{"left":0.5064827,"top":1.0,"width":0.07862367,"height":-0.042298436},"on_screen":true,"role_description":"radio button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Close Tab","depth":3,"bounds":{"left":0.5084774,"top":1.0,"width":0.005319149,"height":-0.04549086},"on_screen":true,"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXRadioButton","text":"screenpipe\"","depth":2,"bounds":{"left":0.5851064,"top":1.0,"width":0.07862367,"height":-0.042298436},"on_screen":true,"role_description":"radio button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Close Tab","depth":3,"bounds":{"left":0.58710104,"top":1.0,"width":0.005319149,"height":-0.04549086},"on_screen":true,"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXRadioButton","text":"-zsh","depth":2,"bounds":{"left":0.66373,"top":1.0,"width":0.07862367,"height":-0.042298436},"on_screen":true,"role_description":"radio button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Close Tab","depth":3,"bounds":{"left":0.66572475,"top":1.0,"width":0.005319149,"height":-0.04549086},"on_screen":true,"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"⌥⌘1","depth":1,"bounds":{"left":0.7287234,"top":1.0,"width":0.01861702,"height":-0.023144484},"on_screen":true,"automation_id":"_NS:8","role_description":"text"},{"role":"AXStaticText","text":"DEV (docker)","depth":1,"bounds":{"left":0.49534574,"top":1.0,"width":0.029920213,"height":-0.02394259},"on_screen":true,"role_description":"text"}]...
|
-8970535408088379666
|
4648522587191274515
|
click
|
accessibility
|
NULL
|
Last login: Thu May 7 09:44:56 on ttys006
Poetry Last login: Thu May 7 09:44:56 on ttys006
Poetry could not find a pyproject.toml file in /Users/lukas/jiminny/app or its parents
Poetry could not find a pyproject.toml file in /Users/lukas/jiminny/app or its parents
lukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/jiminny/app (JY-20773-fix-automated-reports-user-pilot-tracking) $ dev
root@docker_lamp_1:/home/jiminny# php artisan crm:sync-opportunity --teamId=2 --from='2026-05-01 00:00:00'
Syncing opportunity for Hubspot
Your HubSpot account has become disconnected. Please login to Jiminny to reconnect. skipping...
root@docker_lamp_1:/home/jiminny# php artisan jiminny:token-info -A 1499 -R
----------------------------------------------------------------------------------------------------
access_token => CNeR-JHgMxIZQlNQMl8kQEwrAgwACAkUAhIJBB4BAQEDBxiCiYwCIN7Y_Qwo0qwCMhTnG549n-YtNuc1jgj-2AsLPSmw3DoyQlNQMl8kQEwrAiUACBkGawEFThwBARIBAQEEATEEAQEBAQEBAQEBAQUBEggBAQEBAYlCFPAsBNZxoDp5kAcRyeBlQoE5SM7DSgNuYTFSAFoAYABo3tj9DHAAeAA
----------------------------------------------------------------------------------------------------
access_token_expires_at => 2026-05-07 11:41:20
----------------------------------------------------------------------------------------------------
refresh_token => d5ab04e2-2109-4c0b-b513-8cba1dd54371
----------------------------------------------------------------------------------------------------
refresh_token_expires_at =>
----------------------------------------------------------------------------------------------------
root@docker_lamp_1:/home/jiminny# php artisan crm:sync-opportunity --teamId=2 --from='2026-05-01 00:00:00'
DOCKER
Close Tab
DEV (docker)
Close Tab
APP (-zsh)
Close Tab
-zsh
Close Tab
screenpipe"
Close Tab
-zsh
Close Tab
⌥⌘1
DEV (docker)...
|
NULL
|
NULL
|
NULL
|
NULL
|
|
2714
|
111
|
27
|
2026-05-07T11:36:42.218647+00:00
|
/Users/lukas/.screenpipe/data/data/2026-05-07/1778 /Users/lukas/.screenpipe/data/data/2026-05-07/1778153802218_m1.jpg...
|
iTerm2
|
DEV (docker)
|
True
|
NULL
|
monitor_1
|
NULL
|
NULL
|
NULL
|
NULL
|
Last login: Thu May 7 09:44:56 on ttys006
Poetry Last login: Thu May 7 09:44:56 on ttys006
Poetry could not find a pyproject.toml file in /Users/lukas/jiminny/app or its parents
Poetry could not find a pyproject.toml file in /Users/lukas/jiminny/app or its parents
lukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/jiminny/app (JY-20773-fix-automated-reports-user-pilot-tracking) $ dev
root@docker_lamp_1:/home/jiminny# php artisan crm:sync-opportunity --teamId=2 --from='2026-05-01 00:00:00'
Syncing opportunity for Hubspot
Your HubSpot account has become disconnected. Please login to Jiminny to reconnect. skipping...
root@docker_lamp_1:/home/jiminny# php artisan jiminny:token-info -A 1499 -R
----------------------------------------------------------------------------------------------------
access_token => CNeR-JHgMxIZQlNQMl8kQEwrAgwACAkUAhIJBB4BAQEDBxiCiYwCIN7Y_Qwo0qwCMhTnG549n-YtNuc1jgj-2AsLPSmw3DoyQlNQMl8kQEwrAiUACBkGawEFThwBARIBAQEEATEEAQEBAQEBAQEBAQUBEggBAQEBAYlCFPAsBNZxoDp5kAcRyeBlQoE5SM7DSgNuYTFSAFoAYABo3tj9DHAAeAA
----------------------------------------------------------------------------------------------------
access_token_expires_at => 2026-05-07 11:41:20
----------------------------------------------------------------------------------------------------
refresh_token => d5ab04e2-2109-4c0b-b513-8cba1dd54371
----------------------------------------------------------------------------------------------------
refresh_token_expires_at =>
----------------------------------------------------------------------------------------------------
root@docker_lamp_1:/home/jiminny# php artisan crm:sync-opportunity --teamId=2 --from='2026-05-01 00:00:00'
DOCKER
Close Tab
DEV (docker)
Close Tab
APP (-zsh)
Close Tab
-zsh
Close Tab
screenpipe"
Close Tab
-zsh
Close Tab
⌥⌘1
DEV (docker)...
|
[{"role":"AXTextArea","text [{"role":"AXTextArea","text":"Last login: Thu May 7 09:44:56 on ttys006\n\nPoetry could not find a pyproject.toml file in /Users/lukas/jiminny/app or its parents\n\nPoetry could not find a pyproject.toml file in /Users/lukas/jiminny/app or its parents\nlukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/jiminny/app (JY-20773-fix-automated-reports-user-pilot-tracking) $ dev\nroot@docker_lamp_1:/home/jiminny# php artisan crm:sync-opportunity --teamId=2 --from='2026-05-01 00:00:00'\nSyncing opportunity for Hubspot\nYour HubSpot account has become disconnected. Please login to Jiminny to reconnect. skipping...\nroot@docker_lamp_1:/home/jiminny# php artisan jiminny:token-info -A 1499 -R\n----------------------------------------------------------------------------------------------------\naccess_token => CNeR-JHgMxIZQlNQMl8kQEwrAgwACAkUAhIJBB4BAQEDBxiCiYwCIN7Y_Qwo0qwCMhTnG549n-YtNuc1jgj-2AsLPSmw3DoyQlNQMl8kQEwrAiUACBkGawEFThwBARIBAQEEATEEAQEBAQEBAQEBAQUBEggBAQEBAYlCFPAsBNZxoDp5kAcRyeBlQoE5SM7DSgNuYTFSAFoAYABo3tj9DHAAeAA\n----------------------------------------------------------------------------------------------------\naccess_token_expires_at => 2026-05-07 11:41:20\n----------------------------------------------------------------------------------------------------\nrefresh_token => d5ab04e2-2109-4c0b-b513-8cba1dd54371\n----------------------------------------------------------------------------------------------------\nrefresh_token_expires_at => \n----------------------------------------------------------------------------------------------------\nroot@docker_lamp_1:/home/jiminny# php artisan crm:sync-opportunity --teamId=2 --from='2026-05-01 00:00:00'","depth":4,"bounds":{"left":0.0,"top":0.08777778,"width":1.0,"height":0.9122222},"on_screen":true,"lines":[{"char_start":0,"char_count":43,"bounds":{"left":0.0034722222,"top":0.08777778,"width":0.23888889,"height":0.02}},{"char_start":43,"char_count":1,"bounds":{"left":0.0034722222,"top":0.107777774,"width":0.0055555557,"height":0.02}},{"char_start":44,"char_count":87,"bounds":{"left":0.0034722222,"top":0.12777779,"width":0.48333332,"height":0.02}},{"char_start":131,"char_count":1,"bounds":{"left":0.0034722222,"top":0.14777778,"width":0.0055555557,"height":0.02}},{"char_start":132,"char_count":87,"bounds":{"left":0.0034722222,"top":0.16777778,"width":0.48333332,"height":0.02}},{"char_start":219,"char_count":114,"bounds":{"left":0.0034722222,"top":0.18777777,"width":0.6333333,"height":0.02}},{"char_start":333,"char_count":107,"bounds":{"left":0.0034722222,"top":0.20777778,"width":0.59444445,"height":0.02}},{"char_start":440,"char_count":32,"bounds":{"left":0.0034722222,"top":0.22777778,"width":0.17777778,"height":0.02}},{"char_start":472,"char_count":97,"bounds":{"left":0.0034722222,"top":0.24777777,"width":0.5388889,"height":0.02}},{"char_start":569,"char_count":76,"bounds":{"left":0.0034722222,"top":0.26777777,"width":0.42222223,"height":0.02}},{"char_start":645,"char_count":101,"bounds":{"left":0.0034722222,"top":0.28777778,"width":0.5611111,"height":0.02}},{"char_start":925,"char_count":57,"bounds":{"left":0.0034722222,"top":0.32777777,"width":0.31666666,"height":0.02}},{"char_start":982,"char_count":101,"bounds":{"left":0.0034722222,"top":0.34777778,"width":0.5611111,"height":0.02}},{"char_start":1083,"char_count":47,"bounds":{"left":0.0034722222,"top":0.36777776,"width":0.2611111,"height":0.02}},{"char_start":1130,"char_count":101,"bounds":{"left":0.0034722222,"top":0.38777778,"width":0.5611111,"height":0.02}},{"char_start":1231,"char_count":54,"bounds":{"left":0.0034722222,"top":0.4077778,"width":0.3,"height":0.02}},{"char_start":1285,"char_count":101,"bounds":{"left":0.0034722222,"top":0.42777777,"width":0.5611111,"height":0.02}},{"char_start":1386,"char_count":29,"bounds":{"left":0.0034722222,"top":0.44777778,"width":0.16111112,"height":0.02}},{"char_start":1415,"char_count":101,"bounds":{"left":0.0034722222,"top":0.4677778,"width":0.5611111,"height":0.02}},{"char_start":1516,"char_count":106,"bounds":{"left":0.0034722222,"top":0.48777777,"width":0.5888889,"height":0.02}}],"value":"Last login: Thu May 7 09:44:56 on ttys006\n\nPoetry could not find a pyproject.toml file in /Users/lukas/jiminny/app or its parents\n\nPoetry could not find a pyproject.toml file in /Users/lukas/jiminny/app or its parents\nlukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/jiminny/app (JY-20773-fix-automated-reports-user-pilot-tracking) $ dev\nroot@docker_lamp_1:/home/jiminny# php artisan crm:sync-opportunity --teamId=2 --from='2026-05-01 00:00:00'\nSyncing opportunity for Hubspot\nYour HubSpot account has become disconnected. Please login to Jiminny to reconnect. skipping...\nroot@docker_lamp_1:/home/jiminny# php artisan jiminny:token-info -A 1499 -R\n----------------------------------------------------------------------------------------------------\naccess_token => CNeR-JHgMxIZQlNQMl8kQEwrAgwACAkUAhIJBB4BAQEDBxiCiYwCIN7Y_Qwo0qwCMhTnG549n-YtNuc1jgj-2AsLPSmw3DoyQlNQMl8kQEwrAiUACBkGawEFThwBARIBAQEEATEEAQEBAQEBAQEBAQUBEggBAQEBAYlCFPAsBNZxoDp5kAcRyeBlQoE5SM7DSgNuYTFSAFoAYABo3tj9DHAAeAA\n----------------------------------------------------------------------------------------------------\naccess_token_expires_at => 2026-05-07 11:41:20\n----------------------------------------------------------------------------------------------------\nrefresh_token => d5ab04e2-2109-4c0b-b513-8cba1dd54371\n----------------------------------------------------------------------------------------------------\nrefresh_token_expires_at => \n----------------------------------------------------------------------------------------------------\nroot@docker_lamp_1:/home/jiminny# php artisan crm:sync-opportunity --teamId=2 --from='2026-05-01 00:00:00'","is_focused":true},{"role":"AXRadioButton","text":"DOCKER","depth":2,"bounds":{"left":0.0,"top":0.05888889,"width":0.16458334,"height":0.026666667},"on_screen":true,"role_description":"radio button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Close Tab","depth":3,"bounds":{"left":0.004166667,"top":0.06333333,"width":0.011111111,"height":0.017777778},"on_screen":true,"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXRadioButton","text":"DEV (docker)","depth":2,"bounds":{"left":0.16458334,"top":0.05888889,"width":0.16458334,"height":0.026666667},"on_screen":true,"role_description":"radio button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Close Tab","depth":3,"bounds":{"left":0.16875,"top":0.06333333,"width":0.011111111,"height":0.017777778},"on_screen":true,"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXRadioButton","text":"APP (-zsh)","depth":2,"bounds":{"left":0.32916668,"top":0.05888889,"width":0.16423611,"height":0.026666667},"on_screen":true,"role_description":"radio button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Close Tab","depth":3,"bounds":{"left":0.33333334,"top":0.06333333,"width":0.011111111,"height":0.017777778},"on_screen":true,"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXRadioButton","text":"-zsh","depth":2,"bounds":{"left":0.49340278,"top":0.05888889,"width":0.16423611,"height":0.026666667},"on_screen":true,"role_description":"radio button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Close Tab","depth":3,"bounds":{"left":0.49756944,"top":0.06333333,"width":0.011111111,"height":0.017777778},"on_screen":true,"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXRadioButton","text":"screenpipe\"","depth":2,"bounds":{"left":0.6576389,"top":0.05888889,"width":0.16423611,"height":0.026666667},"on_screen":true,"role_description":"radio button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Close Tab","depth":3,"bounds":{"left":0.66180557,"top":0.06333333,"width":0.011111111,"height":0.017777778},"on_screen":true,"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXRadioButton","text":"-zsh","depth":2,"bounds":{"left":0.821875,"top":0.05888889,"width":0.16423611,"height":0.026666667},"on_screen":true,"role_description":"radio button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Close Tab","depth":3,"bounds":{"left":0.82604164,"top":0.06333333,"width":0.011111111,"height":0.017777778},"on_screen":true,"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"⌥⌘1","depth":1,"bounds":{"left":0.95763886,"top":0.032222223,"width":0.03888889,"height":0.018888889},"on_screen":true,"automation_id":"_NS:8","role_description":"text"},{"role":"AXStaticText","text":"DEV (docker)","depth":1,"bounds":{"left":0.47013888,"top":0.033333335,"width":0.0625,"height":0.017777778},"on_screen":true,"role_description":"text"}]...
|
-8970535408088379666
|
4648522587191274515
|
click
|
accessibility
|
NULL
|
Last login: Thu May 7 09:44:56 on ttys006
Poetry Last login: Thu May 7 09:44:56 on ttys006
Poetry could not find a pyproject.toml file in /Users/lukas/jiminny/app or its parents
Poetry could not find a pyproject.toml file in /Users/lukas/jiminny/app or its parents
lukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/jiminny/app (JY-20773-fix-automated-reports-user-pilot-tracking) $ dev
root@docker_lamp_1:/home/jiminny# php artisan crm:sync-opportunity --teamId=2 --from='2026-05-01 00:00:00'
Syncing opportunity for Hubspot
Your HubSpot account has become disconnected. Please login to Jiminny to reconnect. skipping...
root@docker_lamp_1:/home/jiminny# php artisan jiminny:token-info -A 1499 -R
----------------------------------------------------------------------------------------------------
access_token => CNeR-JHgMxIZQlNQMl8kQEwrAgwACAkUAhIJBB4BAQEDBxiCiYwCIN7Y_Qwo0qwCMhTnG549n-YtNuc1jgj-2AsLPSmw3DoyQlNQMl8kQEwrAiUACBkGawEFThwBARIBAQEEATEEAQEBAQEBAQEBAQUBEggBAQEBAYlCFPAsBNZxoDp5kAcRyeBlQoE5SM7DSgNuYTFSAFoAYABo3tj9DHAAeAA
----------------------------------------------------------------------------------------------------
access_token_expires_at => 2026-05-07 11:41:20
----------------------------------------------------------------------------------------------------
refresh_token => d5ab04e2-2109-4c0b-b513-8cba1dd54371
----------------------------------------------------------------------------------------------------
refresh_token_expires_at =>
----------------------------------------------------------------------------------------------------
root@docker_lamp_1:/home/jiminny# php artisan crm:sync-opportunity --teamId=2 --from='2026-05-01 00:00:00'
DOCKER
Close Tab
DEV (docker)
Close Tab
APP (-zsh)
Close Tab
-zsh
Close Tab
screenpipe"
Close Tab
-zsh
Close Tab
⌥⌘1
DEV (docker)...
|
NULL
|
NULL
|
NULL
|
NULL
|
|
2715
|
111
|
28
|
2026-05-07T11:36:45.942789+00:00
|
/Users/lukas/.screenpipe/data/data/2026-05-07/1778 /Users/lukas/.screenpipe/data/data/2026-05-07/1778153805942_m1.jpg...
|
iTerm2
|
DEV (docker)
|
True
|
NULL
|
monitor_1
|
NULL
|
NULL
|
NULL
|
NULL
|
Last login: Thu May 7 09:44:56 on ttys006
Poetry Last login: Thu May 7 09:44:56 on ttys006
Poetry could not find a pyproject.toml file in /Users/lukas/jiminny/app or its parents
Poetry could not find a pyproject.toml file in /Users/lukas/jiminny/app or its parents
lukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/jiminny/app (JY-20773-fix-automated-reports-user-pilot-tracking) $ dev
root@docker_lamp_1:/home/jiminny# php artisan crm:sync-opportunity --teamId=2 --from='2026-05-01 00:00:00'
Syncing opportunity for Hubspot
Your HubSpot account has become disconnected. Please login to Jiminny to reconnect. skipping...
root@docker_lamp_1:/home/jiminny# php artisan jiminny:token-info -A 1499 -R
----------------------------------------------------------------------------------------------------
access_token => CNeR-JHgMxIZQlNQMl8kQEwrAgwACAkUAhIJBB4BAQEDBxiCiYwCIN7Y_Qwo0qwCMhTnG549n-YtNuc1jgj-2AsLPSmw3DoyQlNQMl8kQEwrAiUACBkGawEFThwBARIBAQEEATEEAQEBAQEBAQEBAQUBEggBAQEBAYlCFPAsBNZxoDp5kAcRyeBlQoE5SM7DSgNuYTFSAFoAYABo3tj9DHAAeAA
----------------------------------------------------------------------------------------------------
access_token_expires_at => 2026-05-07 11:41:20
----------------------------------------------------------------------------------------------------
refresh_token => d5ab04e2-2109-4c0b-b513-8cba1dd54371
----------------------------------------------------------------------------------------------------
refresh_token_expires_at =>
----------------------------------------------------------------------------------------------------
root@docker_lamp_1:/home/jiminny# php artisan crm:sync-opportunity --teamId=2 --from='2026-05-01 00:00:00'
Syncing opportunity for Hubspot
Syncing opportunities modified since 2026-05-01 00:00:00...
DOCKER
Close Tab
DEV (docker)
Close Tab
APP (-zsh)
Close Tab
-zsh
Close Tab
screenpipe"
Close Tab
-zsh
Close Tab
⌥⌘1
DEV (docker)...
|
[{"role":"AXTextArea","text [{"role":"AXTextArea","text":"Last login: Thu May 7 09:44:56 on ttys006\n\nPoetry could not find a pyproject.toml file in /Users/lukas/jiminny/app or its parents\n\nPoetry could not find a pyproject.toml file in /Users/lukas/jiminny/app or its parents\nlukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/jiminny/app (JY-20773-fix-automated-reports-user-pilot-tracking) $ dev\nroot@docker_lamp_1:/home/jiminny# php artisan crm:sync-opportunity --teamId=2 --from='2026-05-01 00:00:00'\nSyncing opportunity for Hubspot\nYour HubSpot account has become disconnected. Please login to Jiminny to reconnect. skipping...\nroot@docker_lamp_1:/home/jiminny# php artisan jiminny:token-info -A 1499 -R\n----------------------------------------------------------------------------------------------------\naccess_token => CNeR-JHgMxIZQlNQMl8kQEwrAgwACAkUAhIJBB4BAQEDBxiCiYwCIN7Y_Qwo0qwCMhTnG549n-YtNuc1jgj-2AsLPSmw3DoyQlNQMl8kQEwrAiUACBkGawEFThwBARIBAQEEATEEAQEBAQEBAQEBAQUBEggBAQEBAYlCFPAsBNZxoDp5kAcRyeBlQoE5SM7DSgNuYTFSAFoAYABo3tj9DHAAeAA\n----------------------------------------------------------------------------------------------------\naccess_token_expires_at => 2026-05-07 11:41:20\n----------------------------------------------------------------------------------------------------\nrefresh_token => d5ab04e2-2109-4c0b-b513-8cba1dd54371\n----------------------------------------------------------------------------------------------------\nrefresh_token_expires_at => \n----------------------------------------------------------------------------------------------------\nroot@docker_lamp_1:/home/jiminny# php artisan crm:sync-opportunity --teamId=2 --from='2026-05-01 00:00:00'\nSyncing opportunity for Hubspot\nSyncing opportunities modified since 2026-05-01 00:00:00...","depth":4,"bounds":{"left":0.0,"top":0.08777778,"width":1.0,"height":0.9122222},"on_screen":true,"lines":[{"char_start":0,"char_count":43,"bounds":{"left":0.0034722222,"top":0.08777778,"width":0.23888889,"height":0.02}},{"char_start":43,"char_count":1,"bounds":{"left":0.0034722222,"top":0.107777774,"width":0.0055555557,"height":0.02}},{"char_start":44,"char_count":87,"bounds":{"left":0.0034722222,"top":0.12777779,"width":0.48333332,"height":0.02}},{"char_start":131,"char_count":1,"bounds":{"left":0.0034722222,"top":0.14777778,"width":0.0055555557,"height":0.02}},{"char_start":132,"char_count":87,"bounds":{"left":0.0034722222,"top":0.16777778,"width":0.48333332,"height":0.02}},{"char_start":219,"char_count":114,"bounds":{"left":0.0034722222,"top":0.18777777,"width":0.6333333,"height":0.02}},{"char_start":333,"char_count":107,"bounds":{"left":0.0034722222,"top":0.20777778,"width":0.59444445,"height":0.02}},{"char_start":440,"char_count":32,"bounds":{"left":0.0034722222,"top":0.22777778,"width":0.17777778,"height":0.02}},{"char_start":472,"char_count":97,"bounds":{"left":0.0034722222,"top":0.24777777,"width":0.5388889,"height":0.02}},{"char_start":569,"char_count":76,"bounds":{"left":0.0034722222,"top":0.26777777,"width":0.42222223,"height":0.02}},{"char_start":645,"char_count":101,"bounds":{"left":0.0034722222,"top":0.28777778,"width":0.5611111,"height":0.02}},{"char_start":925,"char_count":57,"bounds":{"left":0.0034722222,"top":0.32777777,"width":0.31666666,"height":0.02}},{"char_start":982,"char_count":101,"bounds":{"left":0.0034722222,"top":0.34777778,"width":0.5611111,"height":0.02}},{"char_start":1083,"char_count":47,"bounds":{"left":0.0034722222,"top":0.36777776,"width":0.2611111,"height":0.02}},{"char_start":1130,"char_count":101,"bounds":{"left":0.0034722222,"top":0.38777778,"width":0.5611111,"height":0.02}},{"char_start":1231,"char_count":54,"bounds":{"left":0.0034722222,"top":0.4077778,"width":0.3,"height":0.02}},{"char_start":1285,"char_count":101,"bounds":{"left":0.0034722222,"top":0.42777777,"width":0.5611111,"height":0.02}},{"char_start":1386,"char_count":29,"bounds":{"left":0.0034722222,"top":0.44777778,"width":0.16111112,"height":0.02}},{"char_start":1415,"char_count":101,"bounds":{"left":0.0034722222,"top":0.4677778,"width":0.5611111,"height":0.02}},{"char_start":1516,"char_count":107,"bounds":{"left":0.0034722222,"top":0.48777777,"width":0.59444445,"height":0.02}},{"char_start":1623,"char_count":32,"bounds":{"left":0.0034722222,"top":0.50777775,"width":0.17777778,"height":0.02}},{"char_start":1655,"char_count":59,"bounds":{"left":0.0034722222,"top":0.5277778,"width":0.32777777,"height":0.02}}],"value":"Last login: Thu May 7 09:44:56 on ttys006\n\nPoetry could not find a pyproject.toml file in /Users/lukas/jiminny/app or its parents\n\nPoetry could not find a pyproject.toml file in /Users/lukas/jiminny/app or its parents\nlukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/jiminny/app (JY-20773-fix-automated-reports-user-pilot-tracking) $ dev\nroot@docker_lamp_1:/home/jiminny# php artisan crm:sync-opportunity --teamId=2 --from='2026-05-01 00:00:00'\nSyncing opportunity for Hubspot\nYour HubSpot account has become disconnected. Please login to Jiminny to reconnect. skipping...\nroot@docker_lamp_1:/home/jiminny# php artisan jiminny:token-info -A 1499 -R\n----------------------------------------------------------------------------------------------------\naccess_token => CNeR-JHgMxIZQlNQMl8kQEwrAgwACAkUAhIJBB4BAQEDBxiCiYwCIN7Y_Qwo0qwCMhTnG549n-YtNuc1jgj-2AsLPSmw3DoyQlNQMl8kQEwrAiUACBkGawEFThwBARIBAQEEATEEAQEBAQEBAQEBAQUBEggBAQEBAYlCFPAsBNZxoDp5kAcRyeBlQoE5SM7DSgNuYTFSAFoAYABo3tj9DHAAeAA\n----------------------------------------------------------------------------------------------------\naccess_token_expires_at => 2026-05-07 11:41:20\n----------------------------------------------------------------------------------------------------\nrefresh_token => d5ab04e2-2109-4c0b-b513-8cba1dd54371\n----------------------------------------------------------------------------------------------------\nrefresh_token_expires_at => \n----------------------------------------------------------------------------------------------------\nroot@docker_lamp_1:/home/jiminny# php artisan crm:sync-opportunity --teamId=2 --from='2026-05-01 00:00:00'\nSyncing opportunity for Hubspot\nSyncing opportunities modified since 2026-05-01 00:00:00...","is_focused":true},{"role":"AXRadioButton","text":"DOCKER","depth":2,"bounds":{"left":0.0,"top":0.05888889,"width":0.16458334,"height":0.026666667},"on_screen":true,"role_description":"radio button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Close Tab","depth":3,"bounds":{"left":0.004166667,"top":0.06333333,"width":0.011111111,"height":0.017777778},"on_screen":true,"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXRadioButton","text":"DEV (docker)","depth":2,"bounds":{"left":0.16458334,"top":0.05888889,"width":0.16458334,"height":0.026666667},"on_screen":true,"role_description":"radio button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Close Tab","depth":3,"bounds":{"left":0.16875,"top":0.06333333,"width":0.011111111,"height":0.017777778},"on_screen":true,"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXRadioButton","text":"APP (-zsh)","depth":2,"bounds":{"left":0.32916668,"top":0.05888889,"width":0.16423611,"height":0.026666667},"on_screen":true,"role_description":"radio button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Close Tab","depth":3,"bounds":{"left":0.33333334,"top":0.06333333,"width":0.011111111,"height":0.017777778},"on_screen":true,"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXRadioButton","text":"-zsh","depth":2,"bounds":{"left":0.49340278,"top":0.05888889,"width":0.16423611,"height":0.026666667},"on_screen":true,"role_description":"radio button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Close Tab","depth":3,"bounds":{"left":0.49756944,"top":0.06333333,"width":0.011111111,"height":0.017777778},"on_screen":true,"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXRadioButton","text":"screenpipe\"","depth":2,"bounds":{"left":0.6576389,"top":0.05888889,"width":0.16423611,"height":0.026666667},"on_screen":true,"role_description":"radio button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Close Tab","depth":3,"bounds":{"left":0.66180557,"top":0.06333333,"width":0.011111111,"height":0.017777778},"on_screen":true,"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXRadioButton","text":"-zsh","depth":2,"bounds":{"left":0.821875,"top":0.05888889,"width":0.16423611,"height":0.026666667},"on_screen":true,"role_description":"radio button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Close Tab","depth":3,"bounds":{"left":0.82604164,"top":0.06333333,"width":0.011111111,"height":0.017777778},"on_screen":true,"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"⌥⌘1","depth":1,"bounds":{"left":0.95763886,"top":0.032222223,"width":0.03888889,"height":0.018888889},"on_screen":true,"automation_id":"_NS:8","role_description":"text"},{"role":"AXStaticText","text":"DEV (docker)","depth":1,"bounds":{"left":0.47013888,"top":0.033333335,"width":0.0625,"height":0.017777778},"on_screen":true,"role_description":"text"}]...
|
1097897669823096626
|
4792637775245633811
|
visual_change
|
accessibility
|
NULL
|
Last login: Thu May 7 09:44:56 on ttys006
Poetry Last login: Thu May 7 09:44:56 on ttys006
Poetry could not find a pyproject.toml file in /Users/lukas/jiminny/app or its parents
Poetry could not find a pyproject.toml file in /Users/lukas/jiminny/app or its parents
lukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/jiminny/app (JY-20773-fix-automated-reports-user-pilot-tracking) $ dev
root@docker_lamp_1:/home/jiminny# php artisan crm:sync-opportunity --teamId=2 --from='2026-05-01 00:00:00'
Syncing opportunity for Hubspot
Your HubSpot account has become disconnected. Please login to Jiminny to reconnect. skipping...
root@docker_lamp_1:/home/jiminny# php artisan jiminny:token-info -A 1499 -R
----------------------------------------------------------------------------------------------------
access_token => CNeR-JHgMxIZQlNQMl8kQEwrAgwACAkUAhIJBB4BAQEDBxiCiYwCIN7Y_Qwo0qwCMhTnG549n-YtNuc1jgj-2AsLPSmw3DoyQlNQMl8kQEwrAiUACBkGawEFThwBARIBAQEEATEEAQEBAQEBAQEBAQUBEggBAQEBAYlCFPAsBNZxoDp5kAcRyeBlQoE5SM7DSgNuYTFSAFoAYABo3tj9DHAAeAA
----------------------------------------------------------------------------------------------------
access_token_expires_at => 2026-05-07 11:41:20
----------------------------------------------------------------------------------------------------
refresh_token => d5ab04e2-2109-4c0b-b513-8cba1dd54371
----------------------------------------------------------------------------------------------------
refresh_token_expires_at =>
----------------------------------------------------------------------------------------------------
root@docker_lamp_1:/home/jiminny# php artisan crm:sync-opportunity --teamId=2 --from='2026-05-01 00:00:00'
Syncing opportunity for Hubspot
Syncing opportunities modified since 2026-05-01 00:00:00...
DOCKER
Close Tab
DEV (docker)
Close Tab
APP (-zsh)
Close Tab
-zsh
Close Tab
screenpipe"
Close Tab
-zsh
Close Tab
⌥⌘1
DEV (docker)...
|
2714
|
NULL
|
NULL
|
NULL
|
|
2718
|
111
|
30
|
2026-05-07T11:36:49.362835+00:00
|
/Users/lukas/.screenpipe/data/data/2026-05-07/1778 /Users/lukas/.screenpipe/data/data/2026-05-07/1778153809362_m1.jpg...
|
PhpStorm
|
faVsco.js – custom.log
|
True
|
NULL
|
monitor_1
|
NULL
|
NULL
|
NULL
|
NULL
|
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
15
Previous Highlighted Error
Next Highlighted Error
lukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/.screenpipe/data/data $ npx screenpipe@latest record --help
Start recording screen, audio, and serve the API
Usage: screenpipe record [OPTIONS]
Options:
-d, --audio-chunk-duration <AUDIO_CHUNK_DURATION>
Audio chunk duration in seconds
[default: 30]
-p, --port <PORT>
Port to run the server on
[default: 3030]
--disable-audio
Disable audio recording
-i, --audio-device <AUDIO_DEVICE>
Audio devices to use (can be specified multiple times)
--use-system-default-audio
Follow system default audio devices
--experimental-coreaudio-system-audio
[experimental, macOS 14.4+] Capture System Audio via CoreAudio Process Tap instead of ScreenCaptureKit. Off by default; ignored on older macOS and non-macOS
--data-dir <DATA_DIR>
Data directory. Default to $HOME/.screenpipe
--debug
Enable debug logging for screenpipe modules
-a, --audio-transcription-engine <AUDIO_TRANSCRIPTION_ENGINE>
Audio transcription engine to use
Possible values:
- deepgram
- whisper-tiny
- whisper-tiny-quantized
- whisper-large
- whisper-large-quantized
- whisper-large-v3-turbo
- whisper-large-v3-turbo-quantized
- openai-compatible
- qwen3-asr
- parakeet
- disabled: Disable transcription (audio capture only, no speech-to-text)
[default: parakeet]
-m, --monitor-id <MONITOR_ID>
Monitor IDs to record. May be specified multiple times. When set, only the listed monitors are recorded (implies `--use-all-monitors=false`)
--use-all-monitors
Automatically record all monitors. Ignored when `--monitor-id` is passed
-l, --language <LANGUAGE>
Languages for OCR/transcription
[possible values: english, chinese, german, spanish, russian, korean, french, japanese, portuguese, turkish, polish, catalan, dutch, arabic, swedish, italian, indonesian, hindi, vietnamese, finnish, hebrew, ukrainian, greek, malay, czech, romanian, danish, hungarian, norwegian, thai, urdu, croatian, bulgarian, lithuanian, latin, malayalam, welsh, slovak, persian, latvian, bengali, serbian, azerbaijani, slovenian, estonian, macedonian, nepali, mongolian, bosnian, kazakh, albanian, swahili, galician, marathi, punjabi, sinhala, khmer, afrikaans, belarusian, gujarati, amharic, yiddish, lao, uzbek, faroese, pashto, maltese, sanskrit, luxembourgish, myanmar, tibetan, tagalog, assamese, tatar, hausa, javanese]
--use-pii-removal
Enable PII removal
--filter-music
Filter music-dominant audio before transcription (reduces Spotify/YouTube music noise)
--disable-vision
Disable vision recording
--ignored-windows <IGNORED_WINDOWS>
Windows to ignore (by title, uses contains matching)
--included-windows <INCLUDED_WINDOWS>
Windows to include (by title, uses contains matching)
--ignored-urls <IGNORED_URLS>
URLs to ignore for browser privacy filtering
--deepgram-api-key <DEEPGRAM_API_KEY>
Deepgram API Key for audio transcription
--transcription-mode <TRANSCRIPTION_MODE>
Audio transcription scheduling mode: batch (default, longer chunks for quality) or realtime
Possible values:
- realtime: Transcribe immediately as audio is captured
- batch: Accumulate longer audio batches for better transcription quality (default)
[default: batch]
--disable-telemetry
Disable telemetry
--video-quality <VIDEO_QUALITY>
Video quality preset: low, balanced, high, max
[default: balanced]
--enable-sync
Enable cloud sync
--sync-token <SYNC_TOKEN>
API token for cloud sync
[env: SCREENPIPE_SYNC_TOKEN=]
--sync-password <SYNC_PASSWORD>
Password for encrypting synced data
[env: SCREENPIPE_SYNC_PASSWORD=[PASSWORD]
--sync-interval-secs <SYNC_INTERVAL_SECS>
Interval between sync cycles in seconds
[default: 300]
--sync-machine-id <SYNC_MACHINE_ID>
Override the machine ID for this device
--pause-on-drm-content
Pause screen and audio capture when a DRM-protected streaming app (Netflix, Disney+, etc.) or a remote-desktop client (Omnissa/VMware Horizon) is focused — these blank their windows while any app is recording the screen
--api-auth
Require authentication for remote API access. When enabled, non-localhost requests must include Authorization: Bearer <SCREENPIPE_API_KEY>. Localhost requests are always allowed
--listen-on-lan
Bind the HTTP server to [IP_ADDRESS] so other devices on the LAN can reach it. Off by default — the server binds [IP_ADDRESS] only. `--api-auth` is forced on whenever this flag is used; you can't accidentally expose an unauthenticated API on your network
--encrypt-secrets
Encrypt secrets (API keys, OAuth tokens) at rest using the OS keychain. Creates a keychain key if one doesn't exist. Without this flag, the CLI will use an existing key (created by the desktop app) but won't create one
--retention-days <RETENTION_DAYS>
Local data retention in days. Old screen/audio data is auto-deleted after this period. Set to 0 to disable retention (keep data forever)
[default: 14]
-h, --help
Print help (see a summary with '-h')
Sync Changes
Hide This Notification
Code changed:
Hide
2
60
Previous Highlighted Error
Next Highlighted Error
<?php
declare(strict_types=1);
namespace Jiminny\Services\Crm\Hubspot;
use HubSpot\Client\Crm\Deals\ApiException as DealApiException;
use HubSpot\Client\Crm\Contacts\ApiException as ContactApiException;
use HubSpot\Client\Crm\Companies\ApiException as CompanyApiException;
use HubSpot\Client\Crm\Contacts\Model\SimplePublicObjectWithAssociations as ContactsWithAssociations;
use HubSpot\Client\Crm\Companies\Model\SimplePublicObjectWithAssociations as CompaniesWithAssociations;
use HubSpot\Client\Crm\Deals\Model\SimplePublicObjectWithAssociations as DealWithAssociations;
use HubSpot\Client\Crm\Objects\Model\SimplePublicObjectInput;
use HubSpot\Client\Crm\Objects\Model\SimplePublicObjectWithAssociations as ObjectWithAssociations;
use HubSpot\Client\Crm\Pipelines\Model\Error;
use HubSpot\Client\Crm\Pipelines\Model\PipelineStage;
use HubSpot\Client\Crm\Properties\Model\Property;
use HubSpot\Discovery\Discovery;
use Jiminny\Component\Utility\Service\ProviderRateLimiter;
use Jiminny\Exceptions\CrmException;
use Jiminny\Exceptions\RateLimitException;
use Jiminny\Exceptions\SocialAccountTokenInvalidException;
use Jiminny\Jobs\Crm\NoteObject;
use Jiminny\Models\Crm\Field;
use Jiminny\Services\Crm\BaseClient;
use Jiminny\Services\Crm\Hubspot\DTO\Response\Owner;
use Jiminny\Services\SocialAccountService;
use SevenShores\Hubspot\Exceptions\BadRequest;
use SevenShores\Hubspot\Exceptions\HubspotException;
use SevenShores\Hubspot\Factory;
use SevenShores\Hubspot\Http\Response;
use Jiminny\Services\Crm\Hubspot\Pagination\HubspotPaginationService;
use Throwable;
/**
* @phpstan-type CrmFieldOption array{id:string, label:string, value?:string}
*/
class Client extends BaseClient implements HubspotClientInterface
{
public const string MIN_API_VERSION = '2';
public const string BASE_URL = '[URL_WITH_CREDENTIALS] T
* @param callable(): T $apiCall
* @return T
*
* @throws RateLimitException
*/
private function executeRequest(callable $apiCall)
{
if (! $this->rateLimiter->canMakeRequest($this->config)) {
$retryAfter = $this->rateLimiter->requestAvailableIn($this->config);
$this->log->warning('[Hubspot] Rate limit exceeded, deferring request', [
'team_id' => $this->config->team_id,
'config_id' => $this->config->getId(),
'retry_after' => $retryAfter,
]);
throw new RateLimitException(
'Hubspot rate limit reached for configuration ' . $this->config->getId(),
$retryAfter,
);
}
$this->rateLimiter->incrementRequestCount($this->config);
try {
return $apiCall();
} catch (Throwable $e) {
if ($this->isHubspotRateLimit($e)) {
$retryAfter = $this->parseRetryAfter($e);
$this->log->warning('[Hubspot] Received 429 from API', [
'team_id' => $this->config->team_id,
'config_id' => $this->config->getId(),
'retry_after' => $retryAfter,
'reason' => $e->getMessage(),
]);
throw new RateLimitException('Hubspot returned 429', $retryAfter, $e);
}
throw $e;
}
}
private function isHubspotRateLimit(Throwable $e): bool
{
return method_exists($e, 'getCode') && (int) $e->getCode() === 429;
}
private function parseRetryAfter(Throwable $e): int
{
if (method_exists($e, 'getResponseHeaders')) {
$headers = $e->getResponseHeaders() ?: [];
$value = $headers['Retry-After'] ?? $headers['retry-after'] ?? null;
if (is_array($value)) {
$value = $value[0] ?? null;
}
if (is_numeric($value)) {
return (int) $value;
}
}
return 10;
}
public function getMinimumApiVersion(): string
{
return self::MIN_API_VERSION;
}
public function getInstance(): Factory
{
return new Factory([
'key' => $this->accessToken,
'oauth2' => true,
'base_url' => $this->baseUrl,
]);
}
public function getNewInstance(): Discovery
{
return \HubSpot\Factory::createWithAccessToken($this->accessToken);
}
/**
* Secondly and daily limits for Hubspot API
*
* Product Tier: Free & Starter | Professional & Enterprise | API add-on (any tier)
* Burst: 100/10 seconds | 150/10 seconds | 200/10 seconds
* Daily: 250,000 | 500,000 | 1,000,000
*
* Official documentation states: The search endpoints are rate limited to five requests per second.
* Since with 5 RPS were still hitting secondly rate limits we lowered it to 4
*/
public function getPaginatedData(array $payload, string $type, int $offset = 0): array
{
$total = 0;
$lastId = null;
$rows = [];
foreach ($this->getPaginatedDataGenerator($payload, $type, $offset, $total, $lastId) as $row) {
$rows[] = $row;
}
return ['results' => $rows, 'total' => $total, 'last_record' => $lastId];
}
/**
* @throws HubspotException
* @throws SocialAccountTokenInvalidException
* @throws BadRequest
*/
public function getPaginatedDataGenerator(
array $payload,
string $type,
int $offset = 0,
int &$total = 0,
?string &$lastRecordId = null
): \Generator {
return $this->paginationService->getPaginatedDataGenerator(
$this,
$payload,
$type,
$offset,
$total,
$lastRecordId
);
}
/**
* @throws DealApiException
* @throws CrmException
*/
public function getOpportunityById(string $crmId, array $fields): array
{
try {
$deal = $this->executeRequest(fn () => $this->getNewInstance()->crm()->deals()->basicApi()->getById(
$crmId,
implode(',', $fields),
'companies,contacts'
));
} catch (DealApiException $e) {
$this->log->info('[Hubspot] Failed to fetch opportunity', [
'crm_id' => $crmId,
'reason' => $e->getMessage(),
]);
throw $e;
}
if (! $deal instanceof DealWithAssociations) {
throw new CrmException('Deal not found');
}
return [
'id' => $deal->getId(),
'properties' => $deal->getProperties(),
'associations' => $deal->getAssociations(),
];
}
/**
* Generic batch read method for HubSpot objects
*
* @param string $objectType The object type ('deals', 'companies', 'contacts')
* @param array<string> $crmIds Array of HubSpot object IDs (max 100)
* @param array<string> $fields Array of property names to fetch
*
* @return array<string, array> Array keyed by CRM ID with object data
*/
private function batchReadObjects(string $objectType, array $crmIds, array $fields): array
{
if (empty($crmIds)) {
return [];
}
$this->validateBatchSize($objectType, $crmIds);
$this->ensureValidToken();
try {
$batchConfig = $this->createBatchConfiguration($objectType);
$batchReadRequest = $this->prepareBatchRequest($batchConfig, $crmIds, $fields);
$response = $batchConfig['api']->read($batchReadRequest);
$this->validateApiResponse($response, $objectType);
$results = $this->processApiResults($response);
$this->logBatchResults($objectType, $crmIds, $results);
return $results;
} catch (\Throwable $e) {
$this->handleBatchError($e, $objectType, $crmIds);
}
}
private function validateBatchSize(string $objectType, array $crmIds): void
{
if (count($crmIds) > 100) {
throw new \InvalidArgumentException("Batch size cannot exceed 100 {$objectType}");
}
}
private function createBatchConfiguration(string $objectType): array
{
$configurations = [
'deals' => [
'batchReadRequest' => new \HubSpot\Client\Crm\Deals\Model\BatchReadInputSimplePublicObjectId(),
'inputClass' => \HubSpot\Client\Crm\Deals\Model\SimplePublicObjectId::class,
'api' => $this->getNewInstance()->crm()->deals()->batchApi(),
],
'companies' => [
'batchReadRequest' => new \HubSpot\Client\Crm\Companies\Model\BatchReadInputSimplePublicObjectId(),
'inputClass' => \HubSpot\Client\Crm\Companies\Model\SimplePublicObjectId::class,
'api' => $this->getNewInstance()->crm()->companies()->batchApi(),
],
'contacts' => [
'batchReadRequest' => new \HubSpot\Client\Crm\Contacts\Model\BatchReadInputSimplePublicObjectId(),
'inputClass' => \HubSpot\Client\Crm\Contacts\Model\SimplePublicObjectId::class,
'api' => $this->getNewInstance()->crm()->contacts()->batchApi(),
],
];
if (! isset($configurations[$objectType])) {
throw new \InvalidArgumentException("Unsupported object type: {$objectType}");
}
return $configurations[$objectType];
}
private function prepareBatchRequest(array $batchConfig, array $crmIds, array $fields): object
{
$batchReadRequest = $batchConfig['batchReadRequest'];
$inputClass = $batchConfig['inputClass'];
$inputs = array_map(function ($crmId) use ($inputClass) {
$input = new $inputClass();
$input->setId($crmId);
return $input;
}, $crmIds);
$batchReadRequest->setInputs($inputs);
$batchReadRequest->setProperties($fields);
return $batchReadRequest;
}
private function validateApiResponse($response, string $objectType): void
{
if (! $response) {
throw new CrmException("HubSpot API returned null response for {$objectType} batch read");
}
}
private function processApiResults($response): array
{
$results = [];
$responseResults = $response->getResults();
if ($responseResults) {
foreach ($responseResults as $object) {
if ($object && $object->getId()) {
$results[$object->getId()] = [
'id' => $object->getId(),
'properties' => $object->getProperties() ?: [],
];
}
}
}
return $results;
}
private function logBatchResults(string $objectType, array $crmIds, array $results): void
{
$this->log->info("[HubSpot] Batch fetched {$objectType}", [
'requested_count' => count($crmIds),
'returned_count' => count($results),
'crm_ids' => $crmIds,
]);
}
private function handleBatchError(\Throwable $e, string $objectType, array $crmIds): void
{
$errorMessage = $e->getMessage() ?: 'Unknown error';
$errorTrace = $e->getTraceAsString() ?: 'No trace available';
$this->log->error("[HubSpot] Failed to batch fetch {$objectType}", [
'crm_ids' => $crmIds,
'error' => $errorMessage,
'trace' => $errorTrace,
]);
throw new CrmException("Failed to batch fetch {$objectType}: " . $errorMessage);
}
/**
* Batch read multiple opportunities by their CRM IDs
*
* @param array<string> $crmIds Array of HubSpot deal IDs (max 100)
* @param array<string> $fields Array of property names to fetch
*
* @return array<string, array> Array keyed by CRM ID with opportunity data
*/
public function getOpportunitiesByIds(array $crmIds, array $fields): array
{
return $this->batchReadObjects('deals', $crmIds, $fields);
}
/**
* Batch read multiple companies by their CRM IDs
*
* @param array<string> $crmIds Array of HubSpot company IDs (max 100)
* @param array<string> $fields Array of property names to fetch
*
* @return array<string, array> Array keyed by CRM ID with company data
*/
public function getCompaniesByIds(array $crmIds, array $fields): array
{
return $this->batchReadObjects('companies', $crmIds, $fields);
}
/**
* Batch read multiple contacts by their CRM IDs
*
* @param array<string> $crmIds Array of HubSpot contact IDs (max 100)
* @param array<string> $fields Array of property names to fetch
*
* @return array<string, array> Array keyed by CRM ID with contact data
*/
public function getContactsByIds(array $crmIds, array $fields): array
{
return $this->batchReadObjects('contacts', $crmIds, $fields);
}
/**
* @throws CompanyApiException
* @throws CrmException
*/
public function getAccountById(string $crmId, array $fields): array
{
try {
$company = $this->getNewInstance()->crm()->companies()->basicApi()->getById(
$crmId,
implode(',', $fields),
);
} catch (CompanyApiException $e) {
$this->log->info('[Hubspot] Failed to fetch account', [
'crm_id' => $crmId,
'reason' => $e->getMessage(),
]);
throw $e;
}
if (! $company instanceof CompaniesWithAssociations) {
throw new CrmException('Account not found');
}
return [
'id' => $company->getId(),
'properties' => $company->getProperties(),
];
}
/**
* @throws ContactApiException
* @throws CrmException
*/
public function getContactById(string $crmId, array $fields): array
{
try {
$contact = $this->getNewInstance()->crm()->contacts()->basicApi()->getById(
$crmId,
implode(',', $fields)
);
} catch (ContactApiException $e) {
$this->log->info('[Hubspot] Failed to fetch contact', [
'crm_id' => $crmId,
'reason' => $e->getMessage(),
]);
throw $e;
}
if (! $contact instanceof ContactsWithAssociations) {
throw new CrmException('Contact not found');
}
return [
'id' => $contact->getId(),
'properties' => $contact->getProperties(),
];
}
/**
* This is email search request that Hubspot offers as GET (more generous quota)
*/
public function getContactByEmail(string $email, array $fields = []): array
{
try {
$contact = $this->getNewInstance()->crm()->contacts()->basicApi()->getById(
$email,
implode(',', $fields),
null,
false,
'email'
);
return [
'id' => $contact->getId(),
'properties' => $contact->getProperties(),
];
} catch (ContactApiException $e) {
$this->log->info('[Hubspot] Failed to fetch contact', [
'email' => $email,
'reason' => $e->getMessage(),
]);
return [];
}
}
/**
* @throws CrmException
*/
public function fetchProperty(string $objectType, string $propertyId): Property
{
$result = $this->getNewInstance()->crm()->properties()->coreApi()->getByName($objectType, $propertyId);
if (! $result instanceof Property) {
$this->log->error('[Hubspot] Failed to fetch property', [
'object_type' => $objectType,
'property_id' => $propertyId,
'reason' => $result->getMessage(),
]);
throw new CrmException('Failed to fetch property');
}
return $result;
}
/**
* @return array<CrmFieldOption>
*/
public function fetchPropertyOptions(string $objectType, string $propertyId): array
{
/** @var array<CrmFieldOption> */
return $this->fetchProperty($objectType, $propertyId)->getOptions();
}
/**
* @return array<array{id:string, label:string, deleted:bool}>
*/
public function fetchCallDispositions(): array
{
/** @var Response $response */
$response = $this->getInstance()->engagements()->getCallDispositions();
/**
* @var array<array{
* id:string,
* label:string,
* deleted: bool
* }>
*/
return $response->toArray();
}
/**
* @return array<CrmFieldOption>
*/
public function fetchOpportunityPipelineStages(): array
{
$stages = [];
$apiResponse = $this->getNewInstance()->crm()->pipelines()->pipelinesApi()->getAll('deals');
if ($apiResponse instanceof Error) {
$this->log->error('[Hubspot] Failed to fetch opportunity pipelines', [
'reason' => $apiResponse->getMessage(),
]);
return [];
}
foreach ($apiResponse->getResults() as $pipeline) {
$pipelineStages = array_map(
static function (PipelineStage $stage) {
return [
'id' => $stage->getId(),
'label' => $stage->getLabel(),
];
},
$pipeline->getStages()
);
$stages = array_merge($stages, $pipelineStages);
}
return $stages;
}
public function fetchOpportunityPipelines(): array
{
$pipelines = [];
try {
$apiResponse = $this->makeRequest('/crm/v3/pipelines/deals');
} catch (\Exception $e) {
$this->log->info('[Hubspot] Failed to fetch opportunity pipelines', [
'reason' => $e->getMessage(),
]);
return [];
}
$response = $apiResponse->toArray();
foreach ($response['results'] as $pipeline) {
$pipelines[] = [
'id' => $pipeline['id'],
'label' => $pipeline['label'],
];
}
return $pipelines;
}
/**
* @return array<CrmFieldOption>
*/
public function fetchMeetingOutcomeFieldOptions(Field $field): array
{
return $field->getCrmProviderId() === 'meetingOutcome'
? $this->fetchMeetingOutcomeTypes()
: $this->fetchCallActivityTypes();
}
public function fetchMeetingOutcomeTypes(): array
{
return $this->extractMeetingTypeOptions(
'[URL_WITH_CREDENTIALS] Response $response */
$response = $this->getInstance()
->getClient()
->request('GET', $endpoint);
/**
* @var array<array{
* value: string,
* label: string,
* displayOrder: int
* }> $optionData
*/
$optionData = $response->toArray()['options'] ?? [];
$options = [];
foreach ($optionData as $item) {
$options[] = [
'id' => $item['value'],
'value' => $item['value'],
'label' => $item['label'],
'display_order' => $item['displayOrder'],
];
}
return $options;
}
/**
* @return array<CrmFieldOption>
*/
public function fetchDispositionFieldOptions(): array
{
$options = [];
$dispositions = $this->fetchCallDispositions();
foreach ($dispositions as $disposition) {
if ($disposition['deleted'] !== false) {
continue;
}
$option['value'] = $disposition['id'];
$option['id'] = $disposition['id'];
$option['label'] = $disposition['label'];
$options[] = $option;
}
return $options;
}
/**
* @return array<CrmFieldOption>
*/
public function fetchOpportunityFieldOptions(Field $field): array
{
if ($field->isStageField()) {
return $this->fetchOpportunityPipelineStages();
}
if ($field->isPipelineField()) {
return $this->fetchOpportunityPipelines();
}
return $this->fetchPropertyOptions('deals', $field->getCrmProviderId());
}
/**
* @throws BadRequest
* @throws HubspotException
*/
public function makeRequest(string $endpoint, $method = 'GET', $payload = [], ?string $queryString = null)
{
$endpoint = self::BASE_URL . $endpoint;
if ($method === 'GET') {
$response = $this->getInstance()->getClient()?->request(
method: $method,
endpoint: $endpoint,
query_string: $queryString
);
} else {
$response = $this->getInstance()->getClient()->request($method, $endpoint, [
'json' => ($payload),
]);
}
$max = $response->getHeaderLine('X-HubSpot-RateLimit-Max'); // "110"
$remaining = $response->getHeaderLine('X-HubSpot-RateLimit-Remaining'); // "109"
$interval = $response->getHeaderLine('X-HubSpot-RateLimit-Interval-Milliseconds'); // "10000"
$body = json_decode((string) $response->getBody(), true);
\Illuminate\Support\Facades\Log::channel('custom_channel')->info('$max ' . PHP_EOL . print_r($max, true));
\Illuminate\Support\Facades\Log::channel('custom_channel')->info('$remaining ' . PHP_EOL . print_r($remaining, true));
\Illuminate\Support\Facades\Log::channel('custom_channel')->info('$interval ' . PHP_EOL . print_r($interval, true));
\Illuminate\Support\Facades\Log::channel('custom_channel')->info('$body ' . PHP_EOL . print_r($body, true));
return $response;
}
/**
* @throws BadRequest
* @throws HubspotException
*/
public function createMeeting(array $payload): Response
{
$endpoint = '/crm/v3/objects/meetings';
return $this->makeRequest($endpoint, 'POST', $payload);
}
/**
* @throws BadRequest
* @throws HubspotException
*/
public function updateMeeting(string $meetingId, array $payload): Response
{
$endpoint = '/crm/v3/objects/meetings/' . $meetingId;
return $this->makeRequest($endpoint, 'PATCH', $payload);
}
/**
* @throws \Exception
*/
public function createNote(
string $body,
string $ownerId,
int $timestamp,
string $objectId,
NoteObject $noteObject
): ?string {
try {
$noteInput = new SimplePublicObjectInput([
'properties' => [
'hs_note_body' => $body,
'hubspot_owner_id' => $ownerId,
'hs_timestamp' => $timestamp,
],
]);
// Create note
$note = $this->getNewInstance()->crm()->objects()->basicApi()->create('note', $noteInput);
$this->getNewInstance()->crm()->objects()->associationsApi()->create(
'note',
$note->getId(),
$this->getNoteObject($noteObject),
$objectId,
$this->getNoteAssociationType($noteObject),
);
return $note->getId();
} catch (\Exception $e) {
$this->log->error('[Hubspot] Failed to create note', [
'objectId' => $objectId,
'noteObject' => $noteObject->getObjectType(),
'reason' => $e->getMessage(),
]);
\Sentry::captureException($e);
}
return null;
}
public function updateEngagement(string $objectId, array $engagement, array $metadata): void
{
$this->getInstance()->engagements()->update($objectId, $engagement, $metadata);
}
public function getEngagementData(string $engagementId): array
{
$engagement = $this->getInstance()->engagements()->get($engagementId);
return $engagement->toArray();
}
public function createEngagement(array $engagement, array $associations, array $metadata): Response
{
return $this->getInstance()
->engagements()
->create($engagement, $associations, $metadata);
}
public function isUnauthorizedException(\Exception $e): bool
{
// Check for specific HubSpot API exception types first
if ($e instanceof BadRequest) {
// BadRequest can contain 401 status codes
return $e->getCode() === 401;
}
// Check for HTTP client exceptions with status codes
if ($e instanceof \GuzzleHttp\Exception\RequestException && $e->hasResponse()) {
$response = $e->getResponse();
if ($response !== null) {
return $response->getStatusCode() === 401;
}
}
// Check for Guzzle HTTP exceptions
if ($e instanceof \GuzzleHttp\Exception\ClientException) {
return $e->getCode() === 401;
}
// Fallback to string matching as last resort, but be more specific
$message = strtolower($e->getMessage());
return str_contains($message, '401 unauthorized') ||
str_contains($message, 'http 401') ||
str_contains($message, 'status code 401') ||
(preg_match('/\b401\b/', $message) && str_contains($message, 'unauthorized'));
}
/**
* Validates and refreshes the access token if needed before API requests.
* This ensures long-running processes don't fail due to token expiration.
*
* @throws SocialAccountTokenInvalidException
*/
public function ensureValidToken(): void
{
if ($this->oauthAccount === null) {
return;
}
$newToken = $this->tokenManager->ensureValidToken($this->oauthAccount);
if ($newToken !== null) {
$this->accessToken = $newToken;
}
}
public function getConfig()
{
return $this->config;
}
// returns only active (archived=false)
public function getOwners(): array
{
return $this->getNewInstance()->crm()->owners()->getAll();
}
/**
* @param bool $archived
*
* @return array<Owner>|[]
*/
public function getOwnersArchived(bool $archived = true): array
{
$endpoint = '/crm/v3/owners';
$queryParams = [
'archived' => $archived ? 'true' : 'false',
];
$queryString = http_build_query($queryParams);
$owners = [];
try {
$response = $this->makeRequest(endpoint: $endpoint, queryString: $queryString);
$responseData = $response?->toArray();
foreach ($responseData['results'] as $result) {
try {
$owners[] = Owner::create($result);
} catch (Throwable $e) {
$this->log->error('[HubSpot] Failed to process owner data', [
'result' => $result,
'error' => $e->getMessage(),
]);
continue;
}
}
} catch (Throwable $e) {
$this->log->error('HubSpot] Failed to fetch owners', [
'archived' => $archived,
'error' => $e->getMessage(),
]);
return [];
}
return $owners;
}
public function getMeeting(string $engagementId): ObjectWithAssociations
{
return $this->getNewInstance()->crm()->objects()->basicApi()
->getById('meeting', $engagementId, null, 'contact,company,deal');
}
public function deleteEngagement(string $engagementId): void
{
$this->getInstance()->engagements()->delete((int) $engagementId);
}
public function getAssociationsData(array $ids, string $fromObject, string $toObject): array
{
$associationData = [];
$idChunks = array_chunk($ids, self::ASSOCIATIONS_BATCH_SIZE_LIMIT);
foreach ($idChunks as $idChunk) {
try {
$batchInput = new \HubSpot\Client\Crm\Associations\Model\BatchInputPublicObjectId();
$batchInput->setInputs(array_map(function ($id) {
$publicObjectId = new \HubSpot\Client\Crm\Associations\Model\PublicObjectId();
$publicObjectId->setId($id);
return $publicObjectId;
}, $idChunk));
$associatedObjectsData = $this
->getNewInstance()
->crm()
->associations()
->batchApi()
->read($fromObject, $toObject, $batchInput);
if ($associatedObjectsData instanceof \HubSpot\Client\Crm\Associations\Model\BatchResponsePublicAssociationMulti) {
foreach ($associatedObjectsData->getResults() as $association) {
$from = $association->getFrom()->getId();
$toAssociations = $association->getTo();
if (! empty($toAssociations)) {
$associationData[$from] = array_map(function ($item) {
return $item->getId();
}, $toAssociations);
}
}
}
} catch (\Exception $e) {
$this->log->error('[Hubspot] Failed to fetch associations', [
'from_object' => $fromObject,
'to_object' => $toObject,
'reason' => $e->getMessage(),
]);
}
}
return $associationData;
}
/**
* @throws \Exception
*/
private function getNoteAssociationType(NoteObject $noteObject): string
{
return match($noteObject) {
NoteObject::Opportunity => 'note_to_deal',
NoteObject::Lead, NoteObject::Contact => 'note_to_contact', // or 'note_to_lead' if your portal supports it
NoteObject::Account => 'note_to_company',
NoteObject::Call, NoteObject::Event => throw new \Exception('Not supported'),
};
}
/**
* @throws \Exception
*/
private function getNoteObject(NoteObject $noteObject): string
{
return match($noteObject) {
NoteObject::Opportunity => 'deal',
NoteObject::Lead, NoteObject::Contact => 'contact',
NoteObject::Account => 'company',
NoteObject::Call, NoteObject::Event => throw new \Exception('Not supported'),
};
}
public function addAssociations(string $objectType, string $associationType, array $payload): Response
{
$endpoint = "/crm/v4/associations/$objectType/$associationType/batch/create";
return $this->makeRequest($endpoint, 'POST', $payload);
}
public function removeAssociations(string $objectType, string $associationType, array $payload): Response
{
$endpoint = "/crm/v4/associations/$objectType/$associationType/batch/archive";
return $this->makeRequest($endpoint, 'POST', $payload);
}
}
Project
Project
New File or Directory…
Expand Selected
Collapse All
Options
Hide...
|
[{"role":"AXButton","text" [{"role":"AXButton","text":"Project: faVsco.js, menu","depth":5,"on_screen":true,"help_text":"~/jiminny/app","role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"master, menu","depth":5,"on_screen":true,"help_text":"Git Branch: master","role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Start Listening for PHP Debug Connections","depth":5,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"AskJiminnyReportActivityServiceTest","depth":6,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Run 'AskJiminnyReportActivityServiceTest'","depth":6,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Debug 'AskJiminnyReportActivityServiceTest'","depth":6,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"More Actions","depth":6,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"JetBrains AI","depth":5,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Search Everywhere","depth":5,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"IDE and Project Settings","depth":5,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Sync Changes","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.018055556,"height":0.026666667},"on_screen":false,"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Hide This Notification","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.018055556,"height":0.026666667},"on_screen":false,"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Code changed:","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.088194445,"height":0.027777778},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Hide","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.018055556,"height":0.026666667},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"15","depth":4,"on_screen":true,"role_description":"text"},{"role":"AXButton","text":"Previous Highlighted Error","depth":4,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Next Highlighted Error","depth":4,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXTextArea","text":"lukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/.screenpipe/data/data $ npx screenpipe@latest record --help\nStart recording screen, audio, and serve the API\n\nUsage: screenpipe record [OPTIONS]\n\nOptions:\n -d, --audio-chunk-duration <AUDIO_CHUNK_DURATION>\n Audio chunk duration in seconds\n\n [default: 30]\n\n -p, --port <PORT>\n Port to run the server on\n\n [default: 3030]\n\n --disable-audio\n Disable audio recording\n\n -i, --audio-device <AUDIO_DEVICE>\n Audio devices to use (can be specified multiple times)\n\n --use-system-default-audio\n Follow system default audio devices\n\n --experimental-coreaudio-system-audio\n [experimental, macOS 14.4+] Capture System Audio via CoreAudio Process Tap instead of ScreenCaptureKit. Off by default; ignored on older macOS and non-macOS\n\n --data-dir <DATA_DIR>\n Data directory. Default to $HOME/.screenpipe\n\n --debug\n Enable debug logging for screenpipe modules\n\n -a, --audio-transcription-engine <AUDIO_TRANSCRIPTION_ENGINE>\n Audio transcription engine to use\n\n Possible values:\n - deepgram\n - whisper-tiny\n - whisper-tiny-quantized\n - whisper-large\n - whisper-large-quantized\n - whisper-large-v3-turbo\n - whisper-large-v3-turbo-quantized\n - openai-compatible\n - qwen3-asr\n - parakeet\n - disabled: Disable transcription (audio capture only, no speech-to-text)\n\n [default: parakeet]\n\n -m, --monitor-id <MONITOR_ID>\n Monitor IDs to record. May be specified multiple times. When set, only the listed monitors are recorded (implies `--use-all-monitors=false`)\n\n --use-all-monitors\n Automatically record all monitors. Ignored when `--monitor-id` is passed\n\n -l, --language <LANGUAGE>\n Languages for OCR/transcription\n\n [possible values: english, chinese, german, spanish, russian, korean, french, japanese, portuguese, turkish, polish, catalan, dutch, arabic, swedish, italian, indonesian, hindi, vietnamese, finnish, hebrew, ukrainian, greek, malay, czech, romanian, danish, hungarian, norwegian, thai, urdu, croatian, bulgarian, lithuanian, latin, malayalam, welsh, slovak, persian, latvian, bengali, serbian, azerbaijani, slovenian, estonian, macedonian, nepali, mongolian, bosnian, kazakh, albanian, swahili, galician, marathi, punjabi, sinhala, khmer, afrikaans, belarusian, gujarati, amharic, yiddish, lao, uzbek, faroese, pashto, maltese, sanskrit, luxembourgish, myanmar, tibetan, tagalog, assamese, tatar, hausa, javanese]\n\n --use-pii-removal\n Enable PII removal\n\n --filter-music\n Filter music-dominant audio before transcription (reduces Spotify/YouTube music noise)\n\n --disable-vision\n Disable vision recording\n\n --ignored-windows <IGNORED_WINDOWS>\n Windows to ignore (by title, uses contains matching)\n\n --included-windows <INCLUDED_WINDOWS>\n Windows to include (by title, uses contains matching)\n\n --ignored-urls <IGNORED_URLS>\n URLs to ignore for browser privacy filtering\n\n --deepgram-api-key <DEEPGRAM_API_KEY>\n Deepgram API Key for audio transcription\n\n --transcription-mode <TRANSCRIPTION_MODE>\n Audio transcription scheduling mode: batch (default, longer chunks for quality) or realtime\n\n Possible values:\n - realtime: Transcribe immediately as audio is captured\n - batch: Accumulate longer audio batches for better transcription quality (default)\n\n [default: batch]\n\n --disable-telemetry\n Disable telemetry\n\n --video-quality <VIDEO_QUALITY>\n Video quality preset: low, balanced, high, max\n\n [default: balanced]\n\n --enable-sync\n Enable cloud sync\n\n --sync-token <SYNC_TOKEN>\n API token for cloud sync\n\n [env: SCREENPIPE_SYNC_TOKEN=]\n\n --sync-password <SYNC_PASSWORD>\n Password for encrypting synced data\n\n [env: SCREENPIPE_SYNC_PASSWORD=]\n\n --sync-interval-secs <SYNC_INTERVAL_SECS>\n Interval between sync cycles in seconds\n\n [default: 300]\n\n --sync-machine-id <SYNC_MACHINE_ID>\n Override the machine ID for this device\n\n --pause-on-drm-content\n Pause screen and audio capture when a DRM-protected streaming app (Netflix, Disney+, etc.) or a remote-desktop client (Omnissa/VMware Horizon) is focused — these blank their windows while any app is recording the screen\n\n --api-auth\n Require authentication for remote API access. When enabled, non-localhost requests must include Authorization: Bearer <SCREENPIPE_API_KEY>. Localhost requests are always allowed\n\n --listen-on-lan\n Bind the HTTP server to 0.0.0.0 so other devices on the LAN can reach it. Off by default — the server binds 127.0.0.1 only. `--api-auth` is forced on whenever this flag is used; you can't accidentally expose an unauthenticated API on your network\n\n --encrypt-secrets\n Encrypt secrets (API keys, OAuth tokens) at rest using the OS keychain. Creates a keychain key if one doesn't exist. Without this flag, the CLI will use an existing key (created by the desktop app) but won't create one\n\n --retention-days <RETENTION_DAYS>\n Local data retention in days. Old screen/audio data is auto-deleted after this period. Set to 0 to disable retention (keep data forever)\n\n [default: 14]\n\n -h, --help\n Print help (see a summary with '-h')","depth":4,"on_screen":true,"value":"lukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/.screenpipe/data/data $ npx screenpipe@latest record --help\nStart recording screen, audio, and serve the API\n\nUsage: screenpipe record [OPTIONS]\n\nOptions:\n -d, --audio-chunk-duration <AUDIO_CHUNK_DURATION>\n Audio chunk duration in seconds\n\n [default: 30]\n\n -p, --port <PORT>\n Port to run the server on\n\n [default: 3030]\n\n --disable-audio\n Disable audio recording\n\n -i, --audio-device <AUDIO_DEVICE>\n Audio devices to use (can be specified multiple times)\n\n --use-system-default-audio\n Follow system default audio devices\n\n --experimental-coreaudio-system-audio\n [experimental, macOS 14.4+] Capture System Audio via CoreAudio Process Tap instead of ScreenCaptureKit. Off by default; ignored on older macOS and non-macOS\n\n --data-dir <DATA_DIR>\n Data directory. Default to $HOME/.screenpipe\n\n --debug\n Enable debug logging for screenpipe modules\n\n -a, --audio-transcription-engine <AUDIO_TRANSCRIPTION_ENGINE>\n Audio transcription engine to use\n\n Possible values:\n - deepgram\n - whisper-tiny\n - whisper-tiny-quantized\n - whisper-large\n - whisper-large-quantized\n - whisper-large-v3-turbo\n - whisper-large-v3-turbo-quantized\n - openai-compatible\n - qwen3-asr\n - parakeet\n - disabled: Disable transcription (audio capture only, no speech-to-text)\n\n [default: parakeet]\n\n -m, --monitor-id <MONITOR_ID>\n Monitor IDs to record. May be specified multiple times. When set, only the listed monitors are recorded (implies `--use-all-monitors=false`)\n\n --use-all-monitors\n Automatically record all monitors. Ignored when `--monitor-id` is passed\n\n -l, --language <LANGUAGE>\n Languages for OCR/transcription\n\n [possible values: english, chinese, german, spanish, russian, korean, french, japanese, portuguese, turkish, polish, catalan, dutch, arabic, swedish, italian, indonesian, hindi, vietnamese, finnish, hebrew, ukrainian, greek, malay, czech, romanian, danish, hungarian, norwegian, thai, urdu, croatian, bulgarian, lithuanian, latin, malayalam, welsh, slovak, persian, latvian, bengali, serbian, azerbaijani, slovenian, estonian, macedonian, nepali, mongolian, bosnian, kazakh, albanian, swahili, galician, marathi, punjabi, sinhala, khmer, afrikaans, belarusian, gujarati, amharic, yiddish, lao, uzbek, faroese, pashto, maltese, sanskrit, luxembourgish, myanmar, tibetan, tagalog, assamese, tatar, hausa, javanese]\n\n --use-pii-removal\n Enable PII removal\n\n --filter-music\n Filter music-dominant audio before transcription (reduces Spotify/YouTube music noise)\n\n --disable-vision\n Disable vision recording\n\n --ignored-windows <IGNORED_WINDOWS>\n Windows to ignore (by title, uses contains matching)\n\n --included-windows <INCLUDED_WINDOWS>\n Windows to include (by title, uses contains matching)\n\n --ignored-urls <IGNORED_URLS>\n URLs to ignore for browser privacy filtering\n\n --deepgram-api-key <DEEPGRAM_API_KEY>\n Deepgram API Key for audio transcription\n\n --transcription-mode <TRANSCRIPTION_MODE>\n Audio transcription scheduling mode: batch (default, longer chunks for quality) or realtime\n\n Possible values:\n - realtime: Transcribe immediately as audio is captured\n - batch: Accumulate longer audio batches for better transcription quality (default)\n\n [default: batch]\n\n --disable-telemetry\n Disable telemetry\n\n --video-quality <VIDEO_QUALITY>\n Video quality preset: low, balanced, high, max\n\n [default: balanced]\n\n --enable-sync\n Enable cloud sync\n\n --sync-token <SYNC_TOKEN>\n API token for cloud sync\n\n [env: SCREENPIPE_SYNC_TOKEN=]\n\n --sync-password <SYNC_PASSWORD>\n Password for encrypting synced data\n\n [env: SCREENPIPE_SYNC_PASSWORD=]\n\n --sync-interval-secs <SYNC_INTERVAL_SECS>\n Interval between sync cycles in seconds\n\n [default: 300]\n\n --sync-machine-id <SYNC_MACHINE_ID>\n Override the machine ID for this device\n\n --pause-on-drm-content\n Pause screen and audio capture when a DRM-protected streaming app (Netflix, Disney+, etc.) or a remote-desktop client (Omnissa/VMware Horizon) is focused — these blank their windows while any app is recording the screen\n\n --api-auth\n Require authentication for remote API access. When enabled, non-localhost requests must include Authorization: Bearer <SCREENPIPE_API_KEY>. Localhost requests are always allowed\n\n --listen-on-lan\n Bind the HTTP server to 0.0.0.0 so other devices on the LAN can reach it. Off by default — the server binds 127.0.0.1 only. `--api-auth` is forced on whenever this flag is used; you can't accidentally expose an unauthenticated API on your network\n\n --encrypt-secrets\n Encrypt secrets (API keys, OAuth tokens) at rest using the OS keychain. Creates a keychain key if one doesn't exist. Without this flag, the CLI will use an existing key (created by the desktop app) but won't create one\n\n --retention-days <RETENTION_DAYS>\n Local data retention in days. Old screen/audio data is auto-deleted after this period. Set to 0 to disable retention (keep data forever)\n\n [default: 14]\n\n -h, --help\n Print help (see a summary with '-h')","role_description":"text entry area","is_enabled":true,"is_focused":true,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Sync Changes","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.018055556,"height":0.026666667},"on_screen":false,"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Hide This Notification","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.018055556,"height":0.026666667},"on_screen":false,"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Code changed:","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.088194445,"height":0.027777778},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Hide","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.018055556,"height":0.026666667},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"2","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.016666668,"height":0.02111111},"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"60","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.021527778,"height":0.02111111},"on_screen":false,"role_description":"text"},{"role":"AXButton","text":"Previous Highlighted Error","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.015277778,"height":0.025555555},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Next Highlighted Error","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.014583333,"height":0.025555555},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXTextArea","text":"<?php\n\ndeclare(strict_types=1);\n\nnamespace Jiminny\\Services\\Crm\\Hubspot;\n\nuse HubSpot\\Client\\Crm\\Deals\\ApiException as DealApiException;\nuse HubSpot\\Client\\Crm\\Contacts\\ApiException as ContactApiException;\nuse HubSpot\\Client\\Crm\\Companies\\ApiException as CompanyApiException;\nuse HubSpot\\Client\\Crm\\Contacts\\Model\\SimplePublicObjectWithAssociations as ContactsWithAssociations;\nuse HubSpot\\Client\\Crm\\Companies\\Model\\SimplePublicObjectWithAssociations as CompaniesWithAssociations;\nuse HubSpot\\Client\\Crm\\Deals\\Model\\SimplePublicObjectWithAssociations as DealWithAssociations;\nuse HubSpot\\Client\\Crm\\Objects\\Model\\SimplePublicObjectInput;\nuse HubSpot\\Client\\Crm\\Objects\\Model\\SimplePublicObjectWithAssociations as ObjectWithAssociations;\nuse HubSpot\\Client\\Crm\\Pipelines\\Model\\Error;\nuse HubSpot\\Client\\Crm\\Pipelines\\Model\\PipelineStage;\nuse HubSpot\\Client\\Crm\\Properties\\Model\\Property;\nuse HubSpot\\Discovery\\Discovery;\nuse Jiminny\\Component\\Utility\\Service\\ProviderRateLimiter;\nuse Jiminny\\Exceptions\\CrmException;\nuse Jiminny\\Exceptions\\RateLimitException;\nuse Jiminny\\Exceptions\\SocialAccountTokenInvalidException;\nuse Jiminny\\Jobs\\Crm\\NoteObject;\nuse Jiminny\\Models\\Crm\\Field;\nuse Jiminny\\Services\\Crm\\BaseClient;\nuse Jiminny\\Services\\Crm\\Hubspot\\DTO\\Response\\Owner;\nuse Jiminny\\Services\\SocialAccountService;\nuse SevenShores\\Hubspot\\Exceptions\\BadRequest;\nuse SevenShores\\Hubspot\\Exceptions\\HubspotException;\nuse SevenShores\\Hubspot\\Factory;\nuse SevenShores\\Hubspot\\Http\\Response;\nuse Jiminny\\Services\\Crm\\Hubspot\\Pagination\\HubspotPaginationService;\nuse Throwable;\n\n/**\n * @phpstan-type CrmFieldOption array{id:string, label:string, value?:string}\n */\nclass Client extends BaseClient implements HubspotClientInterface\n{\n public const string MIN_API_VERSION = '2';\n\n public const string BASE_URL = 'https://api.hubapi.com';\n\n public const int ASSOCIATIONS_BATCH_SIZE_LIMIT = 1000;\n\n private HubspotPaginationService $paginationService;\n private HubspotTokenManager $tokenManager;\n private ProviderRateLimiter $rateLimiter;\n\n public function __construct(\n SocialAccountService $socialAccountService,\n HubspotPaginationService $paginationService,\n HubspotTokenManager $tokenManager,\n ProviderRateLimiter $rateLimiter,\n ) {\n parent::__construct($socialAccountService);\n $this->paginationService = $paginationService;\n $this->tokenManager = $tokenManager;\n $this->rateLimiter = $rateLimiter;\n\n $this->setBaseUrl(self::BASE_URL);\n $this->setVersion(self::MIN_API_VERSION);\n }\n\n /**\n * Single entry point for every HubSpot API call. Enforces the per-portal\n * rate limit configured in the rate_limits table (morphed to the current\n * Configuration) and reacts to a real 429 from HubSpot by translating it\n * into a RateLimitException carrying Retry-After.\n *\n * Wrap any outbound HubSpot call (SDK or raw HTTP) like:\n *\n * $this->executeRequest(fn () => $this->getNewInstance()->crm()->...);\n *\n * @template T\n * @param callable(): T $apiCall\n * @return T\n *\n * @throws RateLimitException\n */\n private function executeRequest(callable $apiCall)\n {\n if (! $this->rateLimiter->canMakeRequest($this->config)) {\n $retryAfter = $this->rateLimiter->requestAvailableIn($this->config);\n\n $this->log->warning('[Hubspot] Rate limit exceeded, deferring request', [\n 'team_id' => $this->config->team_id,\n 'config_id' => $this->config->getId(),\n 'retry_after' => $retryAfter,\n ]);\n\n throw new RateLimitException(\n 'Hubspot rate limit reached for configuration ' . $this->config->getId(),\n $retryAfter,\n );\n }\n\n $this->rateLimiter->incrementRequestCount($this->config);\n\n try {\n return $apiCall();\n } catch (Throwable $e) {\n if ($this->isHubspotRateLimit($e)) {\n $retryAfter = $this->parseRetryAfter($e);\n\n $this->log->warning('[Hubspot] Received 429 from API', [\n 'team_id' => $this->config->team_id,\n 'config_id' => $this->config->getId(),\n 'retry_after' => $retryAfter,\n 'reason' => $e->getMessage(),\n ]);\n\n throw new RateLimitException('Hubspot returned 429', $retryAfter, $e);\n }\n\n throw $e;\n }\n }\n\n private function isHubspotRateLimit(Throwable $e): bool\n {\n return method_exists($e, 'getCode') && (int) $e->getCode() === 429;\n }\n\n private function parseRetryAfter(Throwable $e): int\n {\n if (method_exists($e, 'getResponseHeaders')) {\n $headers = $e->getResponseHeaders() ?: [];\n $value = $headers['Retry-After'] ?? $headers['retry-after'] ?? null;\n if (is_array($value)) {\n $value = $value[0] ?? null;\n }\n if (is_numeric($value)) {\n return (int) $value;\n }\n }\n\n return 10;\n }\n\n public function getMinimumApiVersion(): string\n {\n return self::MIN_API_VERSION;\n }\n\n public function getInstance(): Factory\n {\n return new Factory([\n 'key' => $this->accessToken,\n 'oauth2' => true,\n 'base_url' => $this->baseUrl,\n ]);\n }\n\n public function getNewInstance(): Discovery\n {\n return \\HubSpot\\Factory::createWithAccessToken($this->accessToken);\n }\n\n /**\n * Secondly and daily limits for Hubspot API\n *\n * Product Tier: Free & Starter | Professional & Enterprise | API add-on (any tier)\n * Burst: 100/10 seconds | 150/10 seconds | 200/10 seconds\n * Daily: 250,000 | 500,000 | 1,000,000\n *\n * Official documentation states: The search endpoints are rate limited to five requests per second.\n * Since with 5 RPS were still hitting secondly rate limits we lowered it to 4\n */\n public function getPaginatedData(array $payload, string $type, int $offset = 0): array\n {\n $total = 0;\n $lastId = null;\n $rows = [];\n foreach ($this->getPaginatedDataGenerator($payload, $type, $offset, $total, $lastId) as $row) {\n $rows[] = $row;\n }\n\n return ['results' => $rows, 'total' => $total, 'last_record' => $lastId];\n }\n\n /**\n * @throws HubspotException\n * @throws SocialAccountTokenInvalidException\n * @throws BadRequest\n */\n public function getPaginatedDataGenerator(\n array $payload,\n string $type,\n int $offset = 0,\n int &$total = 0,\n ?string &$lastRecordId = null\n ): \\Generator {\n return $this->paginationService->getPaginatedDataGenerator(\n $this,\n $payload,\n $type,\n $offset,\n $total,\n $lastRecordId\n );\n }\n\n /**\n * @throws DealApiException\n * @throws CrmException\n */\n public function getOpportunityById(string $crmId, array $fields): array\n {\n try {\n $deal = $this->executeRequest(fn () => $this->getNewInstance()->crm()->deals()->basicApi()->getById(\n $crmId,\n implode(',', $fields),\n 'companies,contacts'\n ));\n } catch (DealApiException $e) {\n $this->log->info('[Hubspot] Failed to fetch opportunity', [\n 'crm_id' => $crmId,\n 'reason' => $e->getMessage(),\n ]);\n\n throw $e;\n }\n\n if (! $deal instanceof DealWithAssociations) {\n throw new CrmException('Deal not found');\n }\n\n return [\n 'id' => $deal->getId(),\n 'properties' => $deal->getProperties(),\n 'associations' => $deal->getAssociations(),\n ];\n }\n\n /**\n * Generic batch read method for HubSpot objects\n *\n * @param string $objectType The object type ('deals', 'companies', 'contacts')\n * @param array<string> $crmIds Array of HubSpot object IDs (max 100)\n * @param array<string> $fields Array of property names to fetch\n *\n * @return array<string, array> Array keyed by CRM ID with object data\n */\n private function batchReadObjects(string $objectType, array $crmIds, array $fields): array\n {\n if (empty($crmIds)) {\n return [];\n }\n\n $this->validateBatchSize($objectType, $crmIds);\n $this->ensureValidToken();\n\n try {\n $batchConfig = $this->createBatchConfiguration($objectType);\n $batchReadRequest = $this->prepareBatchRequest($batchConfig, $crmIds, $fields);\n $response = $batchConfig['api']->read($batchReadRequest);\n\n $this->validateApiResponse($response, $objectType);\n\n $results = $this->processApiResults($response);\n $this->logBatchResults($objectType, $crmIds, $results);\n\n return $results;\n } catch (\\Throwable $e) {\n $this->handleBatchError($e, $objectType, $crmIds);\n }\n }\n\n private function validateBatchSize(string $objectType, array $crmIds): void\n {\n if (count($crmIds) > 100) {\n throw new \\InvalidArgumentException(\"Batch size cannot exceed 100 {$objectType}\");\n }\n }\n\n private function createBatchConfiguration(string $objectType): array\n {\n $configurations = [\n 'deals' => [\n 'batchReadRequest' => new \\HubSpot\\Client\\Crm\\Deals\\Model\\BatchReadInputSimplePublicObjectId(),\n 'inputClass' => \\HubSpot\\Client\\Crm\\Deals\\Model\\SimplePublicObjectId::class,\n 'api' => $this->getNewInstance()->crm()->deals()->batchApi(),\n ],\n 'companies' => [\n 'batchReadRequest' => new \\HubSpot\\Client\\Crm\\Companies\\Model\\BatchReadInputSimplePublicObjectId(),\n 'inputClass' => \\HubSpot\\Client\\Crm\\Companies\\Model\\SimplePublicObjectId::class,\n 'api' => $this->getNewInstance()->crm()->companies()->batchApi(),\n ],\n 'contacts' => [\n 'batchReadRequest' => new \\HubSpot\\Client\\Crm\\Contacts\\Model\\BatchReadInputSimplePublicObjectId(),\n 'inputClass' => \\HubSpot\\Client\\Crm\\Contacts\\Model\\SimplePublicObjectId::class,\n 'api' => $this->getNewInstance()->crm()->contacts()->batchApi(),\n ],\n ];\n\n if (! isset($configurations[$objectType])) {\n throw new \\InvalidArgumentException(\"Unsupported object type: {$objectType}\");\n }\n\n return $configurations[$objectType];\n }\n\n private function prepareBatchRequest(array $batchConfig, array $crmIds, array $fields): object\n {\n $batchReadRequest = $batchConfig['batchReadRequest'];\n $inputClass = $batchConfig['inputClass'];\n\n $inputs = array_map(function ($crmId) use ($inputClass) {\n $input = new $inputClass();\n $input->setId($crmId);\n\n return $input;\n }, $crmIds);\n\n $batchReadRequest->setInputs($inputs);\n $batchReadRequest->setProperties($fields);\n\n return $batchReadRequest;\n }\n\n private function validateApiResponse($response, string $objectType): void\n {\n if (! $response) {\n throw new CrmException(\"HubSpot API returned null response for {$objectType} batch read\");\n }\n }\n\n private function processApiResults($response): array\n {\n $results = [];\n $responseResults = $response->getResults();\n\n if ($responseResults) {\n foreach ($responseResults as $object) {\n if ($object && $object->getId()) {\n $results[$object->getId()] = [\n 'id' => $object->getId(),\n 'properties' => $object->getProperties() ?: [],\n ];\n }\n }\n }\n\n return $results;\n }\n\n private function logBatchResults(string $objectType, array $crmIds, array $results): void\n {\n $this->log->info(\"[HubSpot] Batch fetched {$objectType}\", [\n 'requested_count' => count($crmIds),\n 'returned_count' => count($results),\n 'crm_ids' => $crmIds,\n ]);\n }\n\n private function handleBatchError(\\Throwable $e, string $objectType, array $crmIds): void\n {\n $errorMessage = $e->getMessage() ?: 'Unknown error';\n $errorTrace = $e->getTraceAsString() ?: 'No trace available';\n\n $this->log->error(\"[HubSpot] Failed to batch fetch {$objectType}\", [\n 'crm_ids' => $crmIds,\n 'error' => $errorMessage,\n 'trace' => $errorTrace,\n ]);\n\n throw new CrmException(\"Failed to batch fetch {$objectType}: \" . $errorMessage);\n }\n\n /**\n * Batch read multiple opportunities by their CRM IDs\n *\n * @param array<string> $crmIds Array of HubSpot deal IDs (max 100)\n * @param array<string> $fields Array of property names to fetch\n *\n * @return array<string, array> Array keyed by CRM ID with opportunity data\n */\n public function getOpportunitiesByIds(array $crmIds, array $fields): array\n {\n return $this->batchReadObjects('deals', $crmIds, $fields);\n }\n\n /**\n * Batch read multiple companies by their CRM IDs\n *\n * @param array<string> $crmIds Array of HubSpot company IDs (max 100)\n * @param array<string> $fields Array of property names to fetch\n *\n * @return array<string, array> Array keyed by CRM ID with company data\n */\n public function getCompaniesByIds(array $crmIds, array $fields): array\n {\n return $this->batchReadObjects('companies', $crmIds, $fields);\n }\n\n /**\n * Batch read multiple contacts by their CRM IDs\n *\n * @param array<string> $crmIds Array of HubSpot contact IDs (max 100)\n * @param array<string> $fields Array of property names to fetch\n *\n * @return array<string, array> Array keyed by CRM ID with contact data\n */\n public function getContactsByIds(array $crmIds, array $fields): array\n {\n return $this->batchReadObjects('contacts', $crmIds, $fields);\n }\n\n /**\n * @throws CompanyApiException\n * @throws CrmException\n */\n public function getAccountById(string $crmId, array $fields): array\n {\n try {\n $company = $this->getNewInstance()->crm()->companies()->basicApi()->getById(\n $crmId,\n implode(',', $fields),\n );\n } catch (CompanyApiException $e) {\n $this->log->info('[Hubspot] Failed to fetch account', [\n 'crm_id' => $crmId,\n 'reason' => $e->getMessage(),\n ]);\n\n throw $e;\n }\n\n if (! $company instanceof CompaniesWithAssociations) {\n throw new CrmException('Account not found');\n }\n\n return [\n 'id' => $company->getId(),\n 'properties' => $company->getProperties(),\n ];\n }\n\n /**\n * @throws ContactApiException\n * @throws CrmException\n */\n public function getContactById(string $crmId, array $fields): array\n {\n try {\n $contact = $this->getNewInstance()->crm()->contacts()->basicApi()->getById(\n $crmId,\n implode(',', $fields)\n );\n } catch (ContactApiException $e) {\n $this->log->info('[Hubspot] Failed to fetch contact', [\n 'crm_id' => $crmId,\n 'reason' => $e->getMessage(),\n ]);\n\n throw $e;\n }\n\n if (! $contact instanceof ContactsWithAssociations) {\n throw new CrmException('Contact not found');\n }\n\n return [\n 'id' => $contact->getId(),\n 'properties' => $contact->getProperties(),\n ];\n }\n\n /**\n * This is email search request that Hubspot offers as GET (more generous quota)\n */\n public function getContactByEmail(string $email, array $fields = []): array\n {\n try {\n $contact = $this->getNewInstance()->crm()->contacts()->basicApi()->getById(\n $email,\n implode(',', $fields),\n null,\n false,\n 'email'\n );\n\n return [\n 'id' => $contact->getId(),\n 'properties' => $contact->getProperties(),\n ];\n } catch (ContactApiException $e) {\n $this->log->info('[Hubspot] Failed to fetch contact', [\n 'email' => $email,\n 'reason' => $e->getMessage(),\n ]);\n\n return [];\n }\n }\n\n /**\n * @throws CrmException\n */\n public function fetchProperty(string $objectType, string $propertyId): Property\n {\n $result = $this->getNewInstance()->crm()->properties()->coreApi()->getByName($objectType, $propertyId);\n\n if (! $result instanceof Property) {\n $this->log->error('[Hubspot] Failed to fetch property', [\n 'object_type' => $objectType,\n 'property_id' => $propertyId,\n 'reason' => $result->getMessage(),\n ]);\n\n throw new CrmException('Failed to fetch property');\n }\n\n return $result;\n }\n\n /**\n * @return array<CrmFieldOption>\n */\n public function fetchPropertyOptions(string $objectType, string $propertyId): array\n {\n /** @var array<CrmFieldOption> */\n return $this->fetchProperty($objectType, $propertyId)->getOptions();\n }\n\n /**\n * @return array<array{id:string, label:string, deleted:bool}>\n */\n public function fetchCallDispositions(): array\n {\n /** @var Response $response */\n $response = $this->getInstance()->engagements()->getCallDispositions();\n\n /**\n * @var array<array{\n * id:string,\n * label:string,\n * deleted: bool\n * }>\n */\n return $response->toArray();\n }\n\n /**\n * @return array<CrmFieldOption>\n */\n public function fetchOpportunityPipelineStages(): array\n {\n $stages = [];\n $apiResponse = $this->getNewInstance()->crm()->pipelines()->pipelinesApi()->getAll('deals');\n\n if ($apiResponse instanceof Error) {\n $this->log->error('[Hubspot] Failed to fetch opportunity pipelines', [\n 'reason' => $apiResponse->getMessage(),\n ]);\n\n return [];\n }\n\n foreach ($apiResponse->getResults() as $pipeline) {\n $pipelineStages = array_map(\n static function (PipelineStage $stage) {\n return [\n 'id' => $stage->getId(),\n 'label' => $stage->getLabel(),\n ];\n },\n $pipeline->getStages()\n );\n\n $stages = array_merge($stages, $pipelineStages);\n }\n\n return $stages;\n }\n\n public function fetchOpportunityPipelines(): array\n {\n $pipelines = [];\n\n try {\n $apiResponse = $this->makeRequest('/crm/v3/pipelines/deals');\n } catch (\\Exception $e) {\n $this->log->info('[Hubspot] Failed to fetch opportunity pipelines', [\n 'reason' => $e->getMessage(),\n ]);\n\n return [];\n }\n\n $response = $apiResponse->toArray();\n\n foreach ($response['results'] as $pipeline) {\n $pipelines[] = [\n 'id' => $pipeline['id'],\n 'label' => $pipeline['label'],\n ];\n }\n\n return $pipelines;\n }\n\n /**\n * @return array<CrmFieldOption>\n */\n public function fetchMeetingOutcomeFieldOptions(Field $field): array\n {\n return $field->getCrmProviderId() === 'meetingOutcome'\n ? $this->fetchMeetingOutcomeTypes()\n : $this->fetchCallActivityTypes();\n }\n\n public function fetchMeetingOutcomeTypes(): array\n {\n return $this->extractMeetingTypeOptions(\n 'https://api.hubapi.com/crm/v3/properties/meeting/hs_meeting_outcome'\n );\n }\n\n public function fetchCallActivityTypes(): array\n {\n return $this->extractMeetingTypeOptions(\n 'https://api.hubapi.com/crm/v3/properties/call/hs_activity_type'\n );\n }\n\n private function extractMeetingTypeOptions(string $endpoint): array\n {\n /** @var Response $response */\n $response = $this->getInstance()\n ->getClient()\n ->request('GET', $endpoint);\n\n /**\n * @var array<array{\n * value: string,\n * label: string,\n * displayOrder: int\n * }> $optionData\n */\n $optionData = $response->toArray()['options'] ?? [];\n\n $options = [];\n foreach ($optionData as $item) {\n $options[] = [\n 'id' => $item['value'],\n 'value' => $item['value'],\n 'label' => $item['label'],\n 'display_order' => $item['displayOrder'],\n ];\n }\n\n return $options;\n }\n\n /**\n * @return array<CrmFieldOption>\n */\n public function fetchDispositionFieldOptions(): array\n {\n $options = [];\n\n $dispositions = $this->fetchCallDispositions();\n\n foreach ($dispositions as $disposition) {\n if ($disposition['deleted'] !== false) {\n continue;\n }\n\n $option['value'] = $disposition['id'];\n $option['id'] = $disposition['id'];\n $option['label'] = $disposition['label'];\n\n $options[] = $option;\n }\n\n return $options;\n }\n\n /**\n * @return array<CrmFieldOption>\n */\n public function fetchOpportunityFieldOptions(Field $field): array\n {\n if ($field->isStageField()) {\n return $this->fetchOpportunityPipelineStages();\n }\n\n if ($field->isPipelineField()) {\n return $this->fetchOpportunityPipelines();\n }\n\n return $this->fetchPropertyOptions('deals', $field->getCrmProviderId());\n }\n\n /**\n * @throws BadRequest\n * @throws HubspotException\n */\n public function makeRequest(string $endpoint, $method = 'GET', $payload = [], ?string $queryString = null)\n {\n $endpoint = self::BASE_URL . $endpoint;\n\n if ($method === 'GET') {\n $response = $this->getInstance()->getClient()?->request(\n method: $method,\n endpoint: $endpoint,\n query_string: $queryString\n );\n } else {\n $response = $this->getInstance()->getClient()->request($method, $endpoint, [\n 'json' => ($payload),\n ]);\n }\n\n $max = $response->getHeaderLine('X-HubSpot-RateLimit-Max'); // \"110\"\n $remaining = $response->getHeaderLine('X-HubSpot-RateLimit-Remaining'); // \"109\"\n $interval = $response->getHeaderLine('X-HubSpot-RateLimit-Interval-Milliseconds'); // \"10000\"\n $body = json_decode((string) $response->getBody(), true);\n\n \\Illuminate\\Support\\Facades\\Log::channel('custom_channel')->info('$max ' . PHP_EOL . print_r($max, true));\n \\Illuminate\\Support\\Facades\\Log::channel('custom_channel')->info('$remaining ' . PHP_EOL . print_r($remaining, true));\n \\Illuminate\\Support\\Facades\\Log::channel('custom_channel')->info('$interval ' . PHP_EOL . print_r($interval, true));\n \\Illuminate\\Support\\Facades\\Log::channel('custom_channel')->info('$body ' . PHP_EOL . print_r($body, true));\n\n return $response;\n }\n\n /**\n * @throws BadRequest\n * @throws HubspotException\n */\n public function createMeeting(array $payload): Response\n {\n $endpoint = '/crm/v3/objects/meetings';\n\n return $this->makeRequest($endpoint, 'POST', $payload);\n }\n\n /**\n * @throws BadRequest\n * @throws HubspotException\n */\n public function updateMeeting(string $meetingId, array $payload): Response\n {\n $endpoint = '/crm/v3/objects/meetings/' . $meetingId;\n\n return $this->makeRequest($endpoint, 'PATCH', $payload);\n }\n\n /**\n * @throws \\Exception\n */\n public function createNote(\n string $body,\n string $ownerId,\n int $timestamp,\n string $objectId,\n NoteObject $noteObject\n ): ?string {\n try {\n $noteInput = new SimplePublicObjectInput([\n 'properties' => [\n 'hs_note_body' => $body,\n 'hubspot_owner_id' => $ownerId,\n 'hs_timestamp' => $timestamp,\n ],\n ]);\n\n // Create note\n $note = $this->getNewInstance()->crm()->objects()->basicApi()->create('note', $noteInput);\n\n $this->getNewInstance()->crm()->objects()->associationsApi()->create(\n 'note',\n $note->getId(),\n $this->getNoteObject($noteObject),\n $objectId,\n $this->getNoteAssociationType($noteObject),\n );\n\n return $note->getId();\n } catch (\\Exception $e) {\n $this->log->error('[Hubspot] Failed to create note', [\n 'objectId' => $objectId,\n 'noteObject' => $noteObject->getObjectType(),\n 'reason' => $e->getMessage(),\n ]);\n\n \\Sentry::captureException($e);\n }\n\n return null;\n }\n\n public function updateEngagement(string $objectId, array $engagement, array $metadata): void\n {\n $this->getInstance()->engagements()->update($objectId, $engagement, $metadata);\n }\n\n public function getEngagementData(string $engagementId): array\n {\n $engagement = $this->getInstance()->engagements()->get($engagementId);\n\n return $engagement->toArray();\n }\n\n public function createEngagement(array $engagement, array $associations, array $metadata): Response\n {\n return $this->getInstance()\n ->engagements()\n ->create($engagement, $associations, $metadata);\n }\n\n public function isUnauthorizedException(\\Exception $e): bool\n {\n // Check for specific HubSpot API exception types first\n if ($e instanceof BadRequest) {\n // BadRequest can contain 401 status codes\n return $e->getCode() === 401;\n }\n\n // Check for HTTP client exceptions with status codes\n if ($e instanceof \\GuzzleHttp\\Exception\\RequestException && $e->hasResponse()) {\n $response = $e->getResponse();\n if ($response !== null) {\n return $response->getStatusCode() === 401;\n }\n }\n\n // Check for Guzzle HTTP exceptions\n if ($e instanceof \\GuzzleHttp\\Exception\\ClientException) {\n return $e->getCode() === 401;\n }\n\n // Fallback to string matching as last resort, but be more specific\n $message = strtolower($e->getMessage());\n\n return str_contains($message, '401 unauthorized') ||\n str_contains($message, 'http 401') ||\n str_contains($message, 'status code 401') ||\n (preg_match('/\\b401\\b/', $message) && str_contains($message, 'unauthorized'));\n }\n\n /**\n * Validates and refreshes the access token if needed before API requests.\n * This ensures long-running processes don't fail due to token expiration.\n *\n * @throws SocialAccountTokenInvalidException\n */\n public function ensureValidToken(): void\n {\n if ($this->oauthAccount === null) {\n return;\n }\n\n $newToken = $this->tokenManager->ensureValidToken($this->oauthAccount);\n if ($newToken !== null) {\n $this->accessToken = $newToken;\n }\n }\n\n public function getConfig()\n {\n return $this->config;\n }\n\n // returns only active (archived=false)\n public function getOwners(): array\n {\n return $this->getNewInstance()->crm()->owners()->getAll();\n }\n\n /**\n * @param bool $archived\n *\n * @return array<Owner>|[]\n */\n public function getOwnersArchived(bool $archived = true): array\n {\n $endpoint = '/crm/v3/owners';\n $queryParams = [\n 'archived' => $archived ? 'true' : 'false',\n ];\n $queryString = http_build_query($queryParams);\n\n $owners = [];\n\n try {\n $response = $this->makeRequest(endpoint: $endpoint, queryString: $queryString);\n $responseData = $response?->toArray();\n\n foreach ($responseData['results'] as $result) {\n try {\n $owners[] = Owner::create($result);\n } catch (Throwable $e) {\n $this->log->error('[HubSpot] Failed to process owner data', [\n 'result' => $result,\n 'error' => $e->getMessage(),\n ]);\n\n continue;\n }\n }\n } catch (Throwable $e) {\n $this->log->error('HubSpot] Failed to fetch owners', [\n 'archived' => $archived,\n 'error' => $e->getMessage(),\n ]);\n\n return [];\n }\n\n return $owners;\n }\n\n public function getMeeting(string $engagementId): ObjectWithAssociations\n {\n return $this->getNewInstance()->crm()->objects()->basicApi()\n ->getById('meeting', $engagementId, null, 'contact,company,deal');\n }\n\n public function deleteEngagement(string $engagementId): void\n {\n $this->getInstance()->engagements()->delete((int) $engagementId);\n }\n\n public function getAssociationsData(array $ids, string $fromObject, string $toObject): array\n {\n $associationData = [];\n $idChunks = array_chunk($ids, self::ASSOCIATIONS_BATCH_SIZE_LIMIT);\n\n foreach ($idChunks as $idChunk) {\n try {\n $batchInput = new \\HubSpot\\Client\\Crm\\Associations\\Model\\BatchInputPublicObjectId();\n $batchInput->setInputs(array_map(function ($id) {\n $publicObjectId = new \\HubSpot\\Client\\Crm\\Associations\\Model\\PublicObjectId();\n $publicObjectId->setId($id);\n\n return $publicObjectId;\n }, $idChunk));\n\n $associatedObjectsData = $this\n ->getNewInstance()\n ->crm()\n ->associations()\n ->batchApi()\n ->read($fromObject, $toObject, $batchInput);\n\n if ($associatedObjectsData instanceof \\HubSpot\\Client\\Crm\\Associations\\Model\\BatchResponsePublicAssociationMulti) {\n foreach ($associatedObjectsData->getResults() as $association) {\n $from = $association->getFrom()->getId();\n $toAssociations = $association->getTo();\n\n if (! empty($toAssociations)) {\n $associationData[$from] = array_map(function ($item) {\n return $item->getId();\n }, $toAssociations);\n }\n }\n }\n } catch (\\Exception $e) {\n $this->log->error('[Hubspot] Failed to fetch associations', [\n 'from_object' => $fromObject,\n 'to_object' => $toObject,\n 'reason' => $e->getMessage(),\n ]);\n }\n }\n\n return $associationData;\n }\n\n /**\n * @throws \\Exception\n */\n private function getNoteAssociationType(NoteObject $noteObject): string\n {\n return match($noteObject) {\n NoteObject::Opportunity => 'note_to_deal',\n NoteObject::Lead, NoteObject::Contact => 'note_to_contact', // or 'note_to_lead' if your portal supports it\n NoteObject::Account => 'note_to_company',\n NoteObject::Call, NoteObject::Event => throw new \\Exception('Not supported'),\n };\n }\n\n /**\n * @throws \\Exception\n */\n private function getNoteObject(NoteObject $noteObject): string\n {\n return match($noteObject) {\n NoteObject::Opportunity => 'deal',\n NoteObject::Lead, NoteObject::Contact => 'contact',\n NoteObject::Account => 'company',\n NoteObject::Call, NoteObject::Event => throw new \\Exception('Not supported'),\n };\n }\n\n public function addAssociations(string $objectType, string $associationType, array $payload): Response\n {\n $endpoint = \"/crm/v4/associations/$objectType/$associationType/batch/create\";\n\n return $this->makeRequest($endpoint, 'POST', $payload);\n }\n\n public function removeAssociations(string $objectType, string $associationType, array $payload): Response\n {\n $endpoint = \"/crm/v4/associations/$objectType/$associationType/batch/archive\";\n\n return $this->makeRequest($endpoint, 'POST', $payload);\n }\n}","depth":4,"on_screen":true,"value":"<?php\n\ndeclare(strict_types=1);\n\nnamespace Jiminny\\Services\\Crm\\Hubspot;\n\nuse HubSpot\\Client\\Crm\\Deals\\ApiException as DealApiException;\nuse HubSpot\\Client\\Crm\\Contacts\\ApiException as ContactApiException;\nuse HubSpot\\Client\\Crm\\Companies\\ApiException as CompanyApiException;\nuse HubSpot\\Client\\Crm\\Contacts\\Model\\SimplePublicObjectWithAssociations as ContactsWithAssociations;\nuse HubSpot\\Client\\Crm\\Companies\\Model\\SimplePublicObjectWithAssociations as CompaniesWithAssociations;\nuse HubSpot\\Client\\Crm\\Deals\\Model\\SimplePublicObjectWithAssociations as DealWithAssociations;\nuse HubSpot\\Client\\Crm\\Objects\\Model\\SimplePublicObjectInput;\nuse HubSpot\\Client\\Crm\\Objects\\Model\\SimplePublicObjectWithAssociations as ObjectWithAssociations;\nuse HubSpot\\Client\\Crm\\Pipelines\\Model\\Error;\nuse HubSpot\\Client\\Crm\\Pipelines\\Model\\PipelineStage;\nuse HubSpot\\Client\\Crm\\Properties\\Model\\Property;\nuse HubSpot\\Discovery\\Discovery;\nuse Jiminny\\Component\\Utility\\Service\\ProviderRateLimiter;\nuse Jiminny\\Exceptions\\CrmException;\nuse Jiminny\\Exceptions\\RateLimitException;\nuse Jiminny\\Exceptions\\SocialAccountTokenInvalidException;\nuse Jiminny\\Jobs\\Crm\\NoteObject;\nuse Jiminny\\Models\\Crm\\Field;\nuse Jiminny\\Services\\Crm\\BaseClient;\nuse Jiminny\\Services\\Crm\\Hubspot\\DTO\\Response\\Owner;\nuse Jiminny\\Services\\SocialAccountService;\nuse SevenShores\\Hubspot\\Exceptions\\BadRequest;\nuse SevenShores\\Hubspot\\Exceptions\\HubspotException;\nuse SevenShores\\Hubspot\\Factory;\nuse SevenShores\\Hubspot\\Http\\Response;\nuse Jiminny\\Services\\Crm\\Hubspot\\Pagination\\HubspotPaginationService;\nuse Throwable;\n\n/**\n * @phpstan-type CrmFieldOption array{id:string, label:string, value?:string}\n */\nclass Client extends BaseClient implements HubspotClientInterface\n{\n public const string MIN_API_VERSION = '2';\n\n public const string BASE_URL = 'https://api.hubapi.com';\n\n public const int ASSOCIATIONS_BATCH_SIZE_LIMIT = 1000;\n\n private HubspotPaginationService $paginationService;\n private HubspotTokenManager $tokenManager;\n private ProviderRateLimiter $rateLimiter;\n\n public function __construct(\n SocialAccountService $socialAccountService,\n HubspotPaginationService $paginationService,\n HubspotTokenManager $tokenManager,\n ProviderRateLimiter $rateLimiter,\n ) {\n parent::__construct($socialAccountService);\n $this->paginationService = $paginationService;\n $this->tokenManager = $tokenManager;\n $this->rateLimiter = $rateLimiter;\n\n $this->setBaseUrl(self::BASE_URL);\n $this->setVersion(self::MIN_API_VERSION);\n }\n\n /**\n * Single entry point for every HubSpot API call. Enforces the per-portal\n * rate limit configured in the rate_limits table (morphed to the current\n * Configuration) and reacts to a real 429 from HubSpot by translating it\n * into a RateLimitException carrying Retry-After.\n *\n * Wrap any outbound HubSpot call (SDK or raw HTTP) like:\n *\n * $this->executeRequest(fn () => $this->getNewInstance()->crm()->...);\n *\n * @template T\n * @param callable(): T $apiCall\n * @return T\n *\n * @throws RateLimitException\n */\n private function executeRequest(callable $apiCall)\n {\n if (! $this->rateLimiter->canMakeRequest($this->config)) {\n $retryAfter = $this->rateLimiter->requestAvailableIn($this->config);\n\n $this->log->warning('[Hubspot] Rate limit exceeded, deferring request', [\n 'team_id' => $this->config->team_id,\n 'config_id' => $this->config->getId(),\n 'retry_after' => $retryAfter,\n ]);\n\n throw new RateLimitException(\n 'Hubspot rate limit reached for configuration ' . $this->config->getId(),\n $retryAfter,\n );\n }\n\n $this->rateLimiter->incrementRequestCount($this->config);\n\n try {\n return $apiCall();\n } catch (Throwable $e) {\n if ($this->isHubspotRateLimit($e)) {\n $retryAfter = $this->parseRetryAfter($e);\n\n $this->log->warning('[Hubspot] Received 429 from API', [\n 'team_id' => $this->config->team_id,\n 'config_id' => $this->config->getId(),\n 'retry_after' => $retryAfter,\n 'reason' => $e->getMessage(),\n ]);\n\n throw new RateLimitException('Hubspot returned 429', $retryAfter, $e);\n }\n\n throw $e;\n }\n }\n\n private function isHubspotRateLimit(Throwable $e): bool\n {\n return method_exists($e, 'getCode') && (int) $e->getCode() === 429;\n }\n\n private function parseRetryAfter(Throwable $e): int\n {\n if (method_exists($e, 'getResponseHeaders')) {\n $headers = $e->getResponseHeaders() ?: [];\n $value = $headers['Retry-After'] ?? $headers['retry-after'] ?? null;\n if (is_array($value)) {\n $value = $value[0] ?? null;\n }\n if (is_numeric($value)) {\n return (int) $value;\n }\n }\n\n return 10;\n }\n\n public function getMinimumApiVersion(): string\n {\n return self::MIN_API_VERSION;\n }\n\n public function getInstance(): Factory\n {\n return new Factory([\n 'key' => $this->accessToken,\n 'oauth2' => true,\n 'base_url' => $this->baseUrl,\n ]);\n }\n\n public function getNewInstance(): Discovery\n {\n return \\HubSpot\\Factory::createWithAccessToken($this->accessToken);\n }\n\n /**\n * Secondly and daily limits for Hubspot API\n *\n * Product Tier: Free & Starter | Professional & Enterprise | API add-on (any tier)\n * Burst: 100/10 seconds | 150/10 seconds | 200/10 seconds\n * Daily: 250,000 | 500,000 | 1,000,000\n *\n * Official documentation states: The search endpoints are rate limited to five requests per second.\n * Since with 5 RPS were still hitting secondly rate limits we lowered it to 4\n */\n public function getPaginatedData(array $payload, string $type, int $offset = 0): array\n {\n $total = 0;\n $lastId = null;\n $rows = [];\n foreach ($this->getPaginatedDataGenerator($payload, $type, $offset, $total, $lastId) as $row) {\n $rows[] = $row;\n }\n\n return ['results' => $rows, 'total' => $total, 'last_record' => $lastId];\n }\n\n /**\n * @throws HubspotException\n * @throws SocialAccountTokenInvalidException\n * @throws BadRequest\n */\n public function getPaginatedDataGenerator(\n array $payload,\n string $type,\n int $offset = 0,\n int &$total = 0,\n ?string &$lastRecordId = null\n ): \\Generator {\n return $this->paginationService->getPaginatedDataGenerator(\n $this,\n $payload,\n $type,\n $offset,\n $total,\n $lastRecordId\n );\n }\n\n /**\n * @throws DealApiException\n * @throws CrmException\n */\n public function getOpportunityById(string $crmId, array $fields): array\n {\n try {\n $deal = $this->executeRequest(fn () => $this->getNewInstance()->crm()->deals()->basicApi()->getById(\n $crmId,\n implode(',', $fields),\n 'companies,contacts'\n ));\n } catch (DealApiException $e) {\n $this->log->info('[Hubspot] Failed to fetch opportunity', [\n 'crm_id' => $crmId,\n 'reason' => $e->getMessage(),\n ]);\n\n throw $e;\n }\n\n if (! $deal instanceof DealWithAssociations) {\n throw new CrmException('Deal not found');\n }\n\n return [\n 'id' => $deal->getId(),\n 'properties' => $deal->getProperties(),\n 'associations' => $deal->getAssociations(),\n ];\n }\n\n /**\n * Generic batch read method for HubSpot objects\n *\n * @param string $objectType The object type ('deals', 'companies', 'contacts')\n * @param array<string> $crmIds Array of HubSpot object IDs (max 100)\n * @param array<string> $fields Array of property names to fetch\n *\n * @return array<string, array> Array keyed by CRM ID with object data\n */\n private function batchReadObjects(string $objectType, array $crmIds, array $fields): array\n {\n if (empty($crmIds)) {\n return [];\n }\n\n $this->validateBatchSize($objectType, $crmIds);\n $this->ensureValidToken();\n\n try {\n $batchConfig = $this->createBatchConfiguration($objectType);\n $batchReadRequest = $this->prepareBatchRequest($batchConfig, $crmIds, $fields);\n $response = $batchConfig['api']->read($batchReadRequest);\n\n $this->validateApiResponse($response, $objectType);\n\n $results = $this->processApiResults($response);\n $this->logBatchResults($objectType, $crmIds, $results);\n\n return $results;\n } catch (\\Throwable $e) {\n $this->handleBatchError($e, $objectType, $crmIds);\n }\n }\n\n private function validateBatchSize(string $objectType, array $crmIds): void\n {\n if (count($crmIds) > 100) {\n throw new \\InvalidArgumentException(\"Batch size cannot exceed 100 {$objectType}\");\n }\n }\n\n private function createBatchConfiguration(string $objectType): array\n {\n $configurations = [\n 'deals' => [\n 'batchReadRequest' => new \\HubSpot\\Client\\Crm\\Deals\\Model\\BatchReadInputSimplePublicObjectId(),\n 'inputClass' => \\HubSpot\\Client\\Crm\\Deals\\Model\\SimplePublicObjectId::class,\n 'api' => $this->getNewInstance()->crm()->deals()->batchApi(),\n ],\n 'companies' => [\n 'batchReadRequest' => new \\HubSpot\\Client\\Crm\\Companies\\Model\\BatchReadInputSimplePublicObjectId(),\n 'inputClass' => \\HubSpot\\Client\\Crm\\Companies\\Model\\SimplePublicObjectId::class,\n 'api' => $this->getNewInstance()->crm()->companies()->batchApi(),\n ],\n 'contacts' => [\n 'batchReadRequest' => new \\HubSpot\\Client\\Crm\\Contacts\\Model\\BatchReadInputSimplePublicObjectId(),\n 'inputClass' => \\HubSpot\\Client\\Crm\\Contacts\\Model\\SimplePublicObjectId::class,\n 'api' => $this->getNewInstance()->crm()->contacts()->batchApi(),\n ],\n ];\n\n if (! isset($configurations[$objectType])) {\n throw new \\InvalidArgumentException(\"Unsupported object type: {$objectType}\");\n }\n\n return $configurations[$objectType];\n }\n\n private function prepareBatchRequest(array $batchConfig, array $crmIds, array $fields): object\n {\n $batchReadRequest = $batchConfig['batchReadRequest'];\n $inputClass = $batchConfig['inputClass'];\n\n $inputs = array_map(function ($crmId) use ($inputClass) {\n $input = new $inputClass();\n $input->setId($crmId);\n\n return $input;\n }, $crmIds);\n\n $batchReadRequest->setInputs($inputs);\n $batchReadRequest->setProperties($fields);\n\n return $batchReadRequest;\n }\n\n private function validateApiResponse($response, string $objectType): void\n {\n if (! $response) {\n throw new CrmException(\"HubSpot API returned null response for {$objectType} batch read\");\n }\n }\n\n private function processApiResults($response): array\n {\n $results = [];\n $responseResults = $response->getResults();\n\n if ($responseResults) {\n foreach ($responseResults as $object) {\n if ($object && $object->getId()) {\n $results[$object->getId()] = [\n 'id' => $object->getId(),\n 'properties' => $object->getProperties() ?: [],\n ];\n }\n }\n }\n\n return $results;\n }\n\n private function logBatchResults(string $objectType, array $crmIds, array $results): void\n {\n $this->log->info(\"[HubSpot] Batch fetched {$objectType}\", [\n 'requested_count' => count($crmIds),\n 'returned_count' => count($results),\n 'crm_ids' => $crmIds,\n ]);\n }\n\n private function handleBatchError(\\Throwable $e, string $objectType, array $crmIds): void\n {\n $errorMessage = $e->getMessage() ?: 'Unknown error';\n $errorTrace = $e->getTraceAsString() ?: 'No trace available';\n\n $this->log->error(\"[HubSpot] Failed to batch fetch {$objectType}\", [\n 'crm_ids' => $crmIds,\n 'error' => $errorMessage,\n 'trace' => $errorTrace,\n ]);\n\n throw new CrmException(\"Failed to batch fetch {$objectType}: \" . $errorMessage);\n }\n\n /**\n * Batch read multiple opportunities by their CRM IDs\n *\n * @param array<string> $crmIds Array of HubSpot deal IDs (max 100)\n * @param array<string> $fields Array of property names to fetch\n *\n * @return array<string, array> Array keyed by CRM ID with opportunity data\n */\n public function getOpportunitiesByIds(array $crmIds, array $fields): array\n {\n return $this->batchReadObjects('deals', $crmIds, $fields);\n }\n\n /**\n * Batch read multiple companies by their CRM IDs\n *\n * @param array<string> $crmIds Array of HubSpot company IDs (max 100)\n * @param array<string> $fields Array of property names to fetch\n *\n * @return array<string, array> Array keyed by CRM ID with company data\n */\n public function getCompaniesByIds(array $crmIds, array $fields): array\n {\n return $this->batchReadObjects('companies', $crmIds, $fields);\n }\n\n /**\n * Batch read multiple contacts by their CRM IDs\n *\n * @param array<string> $crmIds Array of HubSpot contact IDs (max 100)\n * @param array<string> $fields Array of property names to fetch\n *\n * @return array<string, array> Array keyed by CRM ID with contact data\n */\n public function getContactsByIds(array $crmIds, array $fields): array\n {\n return $this->batchReadObjects('contacts', $crmIds, $fields);\n }\n\n /**\n * @throws CompanyApiException\n * @throws CrmException\n */\n public function getAccountById(string $crmId, array $fields): array\n {\n try {\n $company = $this->getNewInstance()->crm()->companies()->basicApi()->getById(\n $crmId,\n implode(',', $fields),\n );\n } catch (CompanyApiException $e) {\n $this->log->info('[Hubspot] Failed to fetch account', [\n 'crm_id' => $crmId,\n 'reason' => $e->getMessage(),\n ]);\n\n throw $e;\n }\n\n if (! $company instanceof CompaniesWithAssociations) {\n throw new CrmException('Account not found');\n }\n\n return [\n 'id' => $company->getId(),\n 'properties' => $company->getProperties(),\n ];\n }\n\n /**\n * @throws ContactApiException\n * @throws CrmException\n */\n public function getContactById(string $crmId, array $fields): array\n {\n try {\n $contact = $this->getNewInstance()->crm()->contacts()->basicApi()->getById(\n $crmId,\n implode(',', $fields)\n );\n } catch (ContactApiException $e) {\n $this->log->info('[Hubspot] Failed to fetch contact', [\n 'crm_id' => $crmId,\n 'reason' => $e->getMessage(),\n ]);\n\n throw $e;\n }\n\n if (! $contact instanceof ContactsWithAssociations) {\n throw new CrmException('Contact not found');\n }\n\n return [\n 'id' => $contact->getId(),\n 'properties' => $contact->getProperties(),\n ];\n }\n\n /**\n * This is email search request that Hubspot offers as GET (more generous quota)\n */\n public function getContactByEmail(string $email, array $fields = []): array\n {\n try {\n $contact = $this->getNewInstance()->crm()->contacts()->basicApi()->getById(\n $email,\n implode(',', $fields),\n null,\n false,\n 'email'\n );\n\n return [\n 'id' => $contact->getId(),\n 'properties' => $contact->getProperties(),\n ];\n } catch (ContactApiException $e) {\n $this->log->info('[Hubspot] Failed to fetch contact', [\n 'email' => $email,\n 'reason' => $e->getMessage(),\n ]);\n\n return [];\n }\n }\n\n /**\n * @throws CrmException\n */\n public function fetchProperty(string $objectType, string $propertyId): Property\n {\n $result = $this->getNewInstance()->crm()->properties()->coreApi()->getByName($objectType, $propertyId);\n\n if (! $result instanceof Property) {\n $this->log->error('[Hubspot] Failed to fetch property', [\n 'object_type' => $objectType,\n 'property_id' => $propertyId,\n 'reason' => $result->getMessage(),\n ]);\n\n throw new CrmException('Failed to fetch property');\n }\n\n return $result;\n }\n\n /**\n * @return array<CrmFieldOption>\n */\n public function fetchPropertyOptions(string $objectType, string $propertyId): array\n {\n /** @var array<CrmFieldOption> */\n return $this->fetchProperty($objectType, $propertyId)->getOptions();\n }\n\n /**\n * @return array<array{id:string, label:string, deleted:bool}>\n */\n public function fetchCallDispositions(): array\n {\n /** @var Response $response */\n $response = $this->getInstance()->engagements()->getCallDispositions();\n\n /**\n * @var array<array{\n * id:string,\n * label:string,\n * deleted: bool\n * }>\n */\n return $response->toArray();\n }\n\n /**\n * @return array<CrmFieldOption>\n */\n public function fetchOpportunityPipelineStages(): array\n {\n $stages = [];\n $apiResponse = $this->getNewInstance()->crm()->pipelines()->pipelinesApi()->getAll('deals');\n\n if ($apiResponse instanceof Error) {\n $this->log->error('[Hubspot] Failed to fetch opportunity pipelines', [\n 'reason' => $apiResponse->getMessage(),\n ]);\n\n return [];\n }\n\n foreach ($apiResponse->getResults() as $pipeline) {\n $pipelineStages = array_map(\n static function (PipelineStage $stage) {\n return [\n 'id' => $stage->getId(),\n 'label' => $stage->getLabel(),\n ];\n },\n $pipeline->getStages()\n );\n\n $stages = array_merge($stages, $pipelineStages);\n }\n\n return $stages;\n }\n\n public function fetchOpportunityPipelines(): array\n {\n $pipelines = [];\n\n try {\n $apiResponse = $this->makeRequest('/crm/v3/pipelines/deals');\n } catch (\\Exception $e) {\n $this->log->info('[Hubspot] Failed to fetch opportunity pipelines', [\n 'reason' => $e->getMessage(),\n ]);\n\n return [];\n }\n\n $response = $apiResponse->toArray();\n\n foreach ($response['results'] as $pipeline) {\n $pipelines[] = [\n 'id' => $pipeline['id'],\n 'label' => $pipeline['label'],\n ];\n }\n\n return $pipelines;\n }\n\n /**\n * @return array<CrmFieldOption>\n */\n public function fetchMeetingOutcomeFieldOptions(Field $field): array\n {\n return $field->getCrmProviderId() === 'meetingOutcome'\n ? $this->fetchMeetingOutcomeTypes()\n : $this->fetchCallActivityTypes();\n }\n\n public function fetchMeetingOutcomeTypes(): array\n {\n return $this->extractMeetingTypeOptions(\n 'https://api.hubapi.com/crm/v3/properties/meeting/hs_meeting_outcome'\n );\n }\n\n public function fetchCallActivityTypes(): array\n {\n return $this->extractMeetingTypeOptions(\n 'https://api.hubapi.com/crm/v3/properties/call/hs_activity_type'\n );\n }\n\n private function extractMeetingTypeOptions(string $endpoint): array\n {\n /** @var Response $response */\n $response = $this->getInstance()\n ->getClient()\n ->request('GET', $endpoint);\n\n /**\n * @var array<array{\n * value: string,\n * label: string,\n * displayOrder: int\n * }> $optionData\n */\n $optionData = $response->toArray()['options'] ?? [];\n\n $options = [];\n foreach ($optionData as $item) {\n $options[] = [\n 'id' => $item['value'],\n 'value' => $item['value'],\n 'label' => $item['label'],\n 'display_order' => $item['displayOrder'],\n ];\n }\n\n return $options;\n }\n\n /**\n * @return array<CrmFieldOption>\n */\n public function fetchDispositionFieldOptions(): array\n {\n $options = [];\n\n $dispositions = $this->fetchCallDispositions();\n\n foreach ($dispositions as $disposition) {\n if ($disposition['deleted'] !== false) {\n continue;\n }\n\n $option['value'] = $disposition['id'];\n $option['id'] = $disposition['id'];\n $option['label'] = $disposition['label'];\n\n $options[] = $option;\n }\n\n return $options;\n }\n\n /**\n * @return array<CrmFieldOption>\n */\n public function fetchOpportunityFieldOptions(Field $field): array\n {\n if ($field->isStageField()) {\n return $this->fetchOpportunityPipelineStages();\n }\n\n if ($field->isPipelineField()) {\n return $this->fetchOpportunityPipelines();\n }\n\n return $this->fetchPropertyOptions('deals', $field->getCrmProviderId());\n }\n\n /**\n * @throws BadRequest\n * @throws HubspotException\n */\n public function makeRequest(string $endpoint, $method = 'GET', $payload = [], ?string $queryString = null)\n {\n $endpoint = self::BASE_URL . $endpoint;\n\n if ($method === 'GET') {\n $response = $this->getInstance()->getClient()?->request(\n method: $method,\n endpoint: $endpoint,\n query_string: $queryString\n );\n } else {\n $response = $this->getInstance()->getClient()->request($method, $endpoint, [\n 'json' => ($payload),\n ]);\n }\n\n $max = $response->getHeaderLine('X-HubSpot-RateLimit-Max'); // \"110\"\n $remaining = $response->getHeaderLine('X-HubSpot-RateLimit-Remaining'); // \"109\"\n $interval = $response->getHeaderLine('X-HubSpot-RateLimit-Interval-Milliseconds'); // \"10000\"\n $body = json_decode((string) $response->getBody(), true);\n\n \\Illuminate\\Support\\Facades\\Log::channel('custom_channel')->info('$max ' . PHP_EOL . print_r($max, true));\n \\Illuminate\\Support\\Facades\\Log::channel('custom_channel')->info('$remaining ' . PHP_EOL . print_r($remaining, true));\n \\Illuminate\\Support\\Facades\\Log::channel('custom_channel')->info('$interval ' . PHP_EOL . print_r($interval, true));\n \\Illuminate\\Support\\Facades\\Log::channel('custom_channel')->info('$body ' . PHP_EOL . print_r($body, true));\n\n return $response;\n }\n\n /**\n * @throws BadRequest\n * @throws HubspotException\n */\n public function createMeeting(array $payload): Response\n {\n $endpoint = '/crm/v3/objects/meetings';\n\n return $this->makeRequest($endpoint, 'POST', $payload);\n }\n\n /**\n * @throws BadRequest\n * @throws HubspotException\n */\n public function updateMeeting(string $meetingId, array $payload): Response\n {\n $endpoint = '/crm/v3/objects/meetings/' . $meetingId;\n\n return $this->makeRequest($endpoint, 'PATCH', $payload);\n }\n\n /**\n * @throws \\Exception\n */\n public function createNote(\n string $body,\n string $ownerId,\n int $timestamp,\n string $objectId,\n NoteObject $noteObject\n ): ?string {\n try {\n $noteInput = new SimplePublicObjectInput([\n 'properties' => [\n 'hs_note_body' => $body,\n 'hubspot_owner_id' => $ownerId,\n 'hs_timestamp' => $timestamp,\n ],\n ]);\n\n // Create note\n $note = $this->getNewInstance()->crm()->objects()->basicApi()->create('note', $noteInput);\n\n $this->getNewInstance()->crm()->objects()->associationsApi()->create(\n 'note',\n $note->getId(),\n $this->getNoteObject($noteObject),\n $objectId,\n $this->getNoteAssociationType($noteObject),\n );\n\n return $note->getId();\n } catch (\\Exception $e) {\n $this->log->error('[Hubspot] Failed to create note', [\n 'objectId' => $objectId,\n 'noteObject' => $noteObject->getObjectType(),\n 'reason' => $e->getMessage(),\n ]);\n\n \\Sentry::captureException($e);\n }\n\n return null;\n }\n\n public function updateEngagement(string $objectId, array $engagement, array $metadata): void\n {\n $this->getInstance()->engagements()->update($objectId, $engagement, $metadata);\n }\n\n public function getEngagementData(string $engagementId): array\n {\n $engagement = $this->getInstance()->engagements()->get($engagementId);\n\n return $engagement->toArray();\n }\n\n public function createEngagement(array $engagement, array $associations, array $metadata): Response\n {\n return $this->getInstance()\n ->engagements()\n ->create($engagement, $associations, $metadata);\n }\n\n public function isUnauthorizedException(\\Exception $e): bool\n {\n // Check for specific HubSpot API exception types first\n if ($e instanceof BadRequest) {\n // BadRequest can contain 401 status codes\n return $e->getCode() === 401;\n }\n\n // Check for HTTP client exceptions with status codes\n if ($e instanceof \\GuzzleHttp\\Exception\\RequestException && $e->hasResponse()) {\n $response = $e->getResponse();\n if ($response !== null) {\n return $response->getStatusCode() === 401;\n }\n }\n\n // Check for Guzzle HTTP exceptions\n if ($e instanceof \\GuzzleHttp\\Exception\\ClientException) {\n return $e->getCode() === 401;\n }\n\n // Fallback to string matching as last resort, but be more specific\n $message = strtolower($e->getMessage());\n\n return str_contains($message, '401 unauthorized') ||\n str_contains($message, 'http 401') ||\n str_contains($message, 'status code 401') ||\n (preg_match('/\\b401\\b/', $message) && str_contains($message, 'unauthorized'));\n }\n\n /**\n * Validates and refreshes the access token if needed before API requests.\n * This ensures long-running processes don't fail due to token expiration.\n *\n * @throws SocialAccountTokenInvalidException\n */\n public function ensureValidToken(): void\n {\n if ($this->oauthAccount === null) {\n return;\n }\n\n $newToken = $this->tokenManager->ensureValidToken($this->oauthAccount);\n if ($newToken !== null) {\n $this->accessToken = $newToken;\n }\n }\n\n public function getConfig()\n {\n return $this->config;\n }\n\n // returns only active (archived=false)\n public function getOwners(): array\n {\n return $this->getNewInstance()->crm()->owners()->getAll();\n }\n\n /**\n * @param bool $archived\n *\n * @return array<Owner>|[]\n */\n public function getOwnersArchived(bool $archived = true): array\n {\n $endpoint = '/crm/v3/owners';\n $queryParams = [\n 'archived' => $archived ? 'true' : 'false',\n ];\n $queryString = http_build_query($queryParams);\n\n $owners = [];\n\n try {\n $response = $this->makeRequest(endpoint: $endpoint, queryString: $queryString);\n $responseData = $response?->toArray();\n\n foreach ($responseData['results'] as $result) {\n try {\n $owners[] = Owner::create($result);\n } catch (Throwable $e) {\n $this->log->error('[HubSpot] Failed to process owner data', [\n 'result' => $result,\n 'error' => $e->getMessage(),\n ]);\n\n continue;\n }\n }\n } catch (Throwable $e) {\n $this->log->error('HubSpot] Failed to fetch owners', [\n 'archived' => $archived,\n 'error' => $e->getMessage(),\n ]);\n\n return [];\n }\n\n return $owners;\n }\n\n public function getMeeting(string $engagementId): ObjectWithAssociations\n {\n return $this->getNewInstance()->crm()->objects()->basicApi()\n ->getById('meeting', $engagementId, null, 'contact,company,deal');\n }\n\n public function deleteEngagement(string $engagementId): void\n {\n $this->getInstance()->engagements()->delete((int) $engagementId);\n }\n\n public function getAssociationsData(array $ids, string $fromObject, string $toObject): array\n {\n $associationData = [];\n $idChunks = array_chunk($ids, self::ASSOCIATIONS_BATCH_SIZE_LIMIT);\n\n foreach ($idChunks as $idChunk) {\n try {\n $batchInput = new \\HubSpot\\Client\\Crm\\Associations\\Model\\BatchInputPublicObjectId();\n $batchInput->setInputs(array_map(function ($id) {\n $publicObjectId = new \\HubSpot\\Client\\Crm\\Associations\\Model\\PublicObjectId();\n $publicObjectId->setId($id);\n\n return $publicObjectId;\n }, $idChunk));\n\n $associatedObjectsData = $this\n ->getNewInstance()\n ->crm()\n ->associations()\n ->batchApi()\n ->read($fromObject, $toObject, $batchInput);\n\n if ($associatedObjectsData instanceof \\HubSpot\\Client\\Crm\\Associations\\Model\\BatchResponsePublicAssociationMulti) {\n foreach ($associatedObjectsData->getResults() as $association) {\n $from = $association->getFrom()->getId();\n $toAssociations = $association->getTo();\n\n if (! empty($toAssociations)) {\n $associationData[$from] = array_map(function ($item) {\n return $item->getId();\n }, $toAssociations);\n }\n }\n }\n } catch (\\Exception $e) {\n $this->log->error('[Hubspot] Failed to fetch associations', [\n 'from_object' => $fromObject,\n 'to_object' => $toObject,\n 'reason' => $e->getMessage(),\n ]);\n }\n }\n\n return $associationData;\n }\n\n /**\n * @throws \\Exception\n */\n private function getNoteAssociationType(NoteObject $noteObject): string\n {\n return match($noteObject) {\n NoteObject::Opportunity => 'note_to_deal',\n NoteObject::Lead, NoteObject::Contact => 'note_to_contact', // or 'note_to_lead' if your portal supports it\n NoteObject::Account => 'note_to_company',\n NoteObject::Call, NoteObject::Event => throw new \\Exception('Not supported'),\n };\n }\n\n /**\n * @throws \\Exception\n */\n private function getNoteObject(NoteObject $noteObject): string\n {\n return match($noteObject) {\n NoteObject::Opportunity => 'deal',\n NoteObject::Lead, NoteObject::Contact => 'contact',\n NoteObject::Account => 'company',\n NoteObject::Call, NoteObject::Event => throw new \\Exception('Not supported'),\n };\n }\n\n public function addAssociations(string $objectType, string $associationType, array $payload): Response\n {\n $endpoint = \"/crm/v4/associations/$objectType/$associationType/batch/create\";\n\n return $this->makeRequest($endpoint, 'POST', $payload);\n }\n\n public function removeAssociations(string $objectType, string $associationType, array $payload): Response\n {\n $endpoint = \"/crm/v4/associations/$objectType/$associationType/batch/archive\";\n\n return $this->makeRequest($endpoint, 'POST', $payload);\n }\n}","role_description":"text entry area","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"Project","depth":3,"on_screen":false,"role_description":"text"},{"role":"AXButton","text":"Project","depth":3,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"New File or Directory…","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.018055556,"height":0.026666667},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Expand Selected","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.018055556,"height":0.026666667},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Collapse All","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.018055556,"height":0.026666667},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Options","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.018055556,"height":0.026666667},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Hide","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.018055556,"height":0.026666667},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false}]...
|
3216904061287467739
|
7531555552090524004
|
visual_change
|
accessibility
|
NULL
|
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
15
Previous Highlighted Error
Next Highlighted Error
lukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/.screenpipe/data/data $ npx screenpipe@latest record --help
Start recording screen, audio, and serve the API
Usage: screenpipe record [OPTIONS]
Options:
-d, --audio-chunk-duration <AUDIO_CHUNK_DURATION>
Audio chunk duration in seconds
[default: 30]
-p, --port <PORT>
Port to run the server on
[default: 3030]
--disable-audio
Disable audio recording
-i, --audio-device <AUDIO_DEVICE>
Audio devices to use (can be specified multiple times)
--use-system-default-audio
Follow system default audio devices
--experimental-coreaudio-system-audio
[experimental, macOS 14.4+] Capture System Audio via CoreAudio Process Tap instead of ScreenCaptureKit. Off by default; ignored on older macOS and non-macOS
--data-dir <DATA_DIR>
Data directory. Default to $HOME/.screenpipe
--debug
Enable debug logging for screenpipe modules
-a, --audio-transcription-engine <AUDIO_TRANSCRIPTION_ENGINE>
Audio transcription engine to use
Possible values:
- deepgram
- whisper-tiny
- whisper-tiny-quantized
- whisper-large
- whisper-large-quantized
- whisper-large-v3-turbo
- whisper-large-v3-turbo-quantized
- openai-compatible
- qwen3-asr
- parakeet
- disabled: Disable transcription (audio capture only, no speech-to-text)
[default: parakeet]
-m, --monitor-id <MONITOR_ID>
Monitor IDs to record. May be specified multiple times. When set, only the listed monitors are recorded (implies `--use-all-monitors=false`)
--use-all-monitors
Automatically record all monitors. Ignored when `--monitor-id` is passed
-l, --language <LANGUAGE>
Languages for OCR/transcription
[possible values: english, chinese, german, spanish, russian, korean, french, japanese, portuguese, turkish, polish, catalan, dutch, arabic, swedish, italian, indonesian, hindi, vietnamese, finnish, hebrew, ukrainian, greek, malay, czech, romanian, danish, hungarian, norwegian, thai, urdu, croatian, bulgarian, lithuanian, latin, malayalam, welsh, slovak, persian, latvian, bengali, serbian, azerbaijani, slovenian, estonian, macedonian, nepali, mongolian, bosnian, kazakh, albanian, swahili, galician, marathi, punjabi, sinhala, khmer, afrikaans, belarusian, gujarati, amharic, yiddish, lao, uzbek, faroese, pashto, maltese, sanskrit, luxembourgish, myanmar, tibetan, tagalog, assamese, tatar, hausa, javanese]
--use-pii-removal
Enable PII removal
--filter-music
Filter music-dominant audio before transcription (reduces Spotify/YouTube music noise)
--disable-vision
Disable vision recording
--ignored-windows <IGNORED_WINDOWS>
Windows to ignore (by title, uses contains matching)
--included-windows <INCLUDED_WINDOWS>
Windows to include (by title, uses contains matching)
--ignored-urls <IGNORED_URLS>
URLs to ignore for browser privacy filtering
--deepgram-api-key <DEEPGRAM_API_KEY>
Deepgram API Key for audio transcription
--transcription-mode <TRANSCRIPTION_MODE>
Audio transcription scheduling mode: batch (default, longer chunks for quality) or realtime
Possible values:
- realtime: Transcribe immediately as audio is captured
- batch: Accumulate longer audio batches for better transcription quality (default)
[default: batch]
--disable-telemetry
Disable telemetry
--video-quality <VIDEO_QUALITY>
Video quality preset: low, balanced, high, max
[default: balanced]
--enable-sync
Enable cloud sync
--sync-token <SYNC_TOKEN>
API token for cloud sync
[env: SCREENPIPE_SYNC_TOKEN=]
--sync-password <SYNC_PASSWORD>
Password for encrypting synced data
[env: SCREENPIPE_SYNC_PASSWORD=[PASSWORD]
--sync-interval-secs <SYNC_INTERVAL_SECS>
Interval between sync cycles in seconds
[default: 300]
--sync-machine-id <SYNC_MACHINE_ID>
Override the machine ID for this device
--pause-on-drm-content
Pause screen and audio capture when a DRM-protected streaming app (Netflix, Disney+, etc.) or a remote-desktop client (Omnissa/VMware Horizon) is focused — these blank their windows while any app is recording the screen
--api-auth
Require authentication for remote API access. When enabled, non-localhost requests must include Authorization: Bearer <SCREENPIPE_API_KEY>. Localhost requests are always allowed
--listen-on-lan
Bind the HTTP server to [IP_ADDRESS] so other devices on the LAN can reach it. Off by default — the server binds [IP_ADDRESS] only. `--api-auth` is forced on whenever this flag is used; you can't accidentally expose an unauthenticated API on your network
--encrypt-secrets
Encrypt secrets (API keys, OAuth tokens) at rest using the OS keychain. Creates a keychain key if one doesn't exist. Without this flag, the CLI will use an existing key (created by the desktop app) but won't create one
--retention-days <RETENTION_DAYS>
Local data retention in days. Old screen/audio data is auto-deleted after this period. Set to 0 to disable retention (keep data forever)
[default: 14]
-h, --help
Print help (see a summary with '-h')
Sync Changes
Hide This Notification
Code changed:
Hide
2
60
Previous Highlighted Error
Next Highlighted Error
<?php
declare(strict_types=1);
namespace Jiminny\Services\Crm\Hubspot;
use HubSpot\Client\Crm\Deals\ApiException as DealApiException;
use HubSpot\Client\Crm\Contacts\ApiException as ContactApiException;
use HubSpot\Client\Crm\Companies\ApiException as CompanyApiException;
use HubSpot\Client\Crm\Contacts\Model\SimplePublicObjectWithAssociations as ContactsWithAssociations;
use HubSpot\Client\Crm\Companies\Model\SimplePublicObjectWithAssociations as CompaniesWithAssociations;
use HubSpot\Client\Crm\Deals\Model\SimplePublicObjectWithAssociations as DealWithAssociations;
use HubSpot\Client\Crm\Objects\Model\SimplePublicObjectInput;
use HubSpot\Client\Crm\Objects\Model\SimplePublicObjectWithAssociations as ObjectWithAssociations;
use HubSpot\Client\Crm\Pipelines\Model\Error;
use HubSpot\Client\Crm\Pipelines\Model\PipelineStage;
use HubSpot\Client\Crm\Properties\Model\Property;
use HubSpot\Discovery\Discovery;
use Jiminny\Component\Utility\Service\ProviderRateLimiter;
use Jiminny\Exceptions\CrmException;
use Jiminny\Exceptions\RateLimitException;
use Jiminny\Exceptions\SocialAccountTokenInvalidException;
use Jiminny\Jobs\Crm\NoteObject;
use Jiminny\Models\Crm\Field;
use Jiminny\Services\Crm\BaseClient;
use Jiminny\Services\Crm\Hubspot\DTO\Response\Owner;
use Jiminny\Services\SocialAccountService;
use SevenShores\Hubspot\Exceptions\BadRequest;
use SevenShores\Hubspot\Exceptions\HubspotException;
use SevenShores\Hubspot\Factory;
use SevenShores\Hubspot\Http\Response;
use Jiminny\Services\Crm\Hubspot\Pagination\HubspotPaginationService;
use Throwable;
/**
* @phpstan-type CrmFieldOption array{id:string, label:string, value?:string}
*/
class Client extends BaseClient implements HubspotClientInterface
{
public const string MIN_API_VERSION = '2';
public const string BASE_URL = '[URL_WITH_CREDENTIALS] T
* @param callable(): T $apiCall
* @return T
*
* @throws RateLimitException
*/
private function executeRequest(callable $apiCall)
{
if (! $this->rateLimiter->canMakeRequest($this->config)) {
$retryAfter = $this->rateLimiter->requestAvailableIn($this->config);
$this->log->warning('[Hubspot] Rate limit exceeded, deferring request', [
'team_id' => $this->config->team_id,
'config_id' => $this->config->getId(),
'retry_after' => $retryAfter,
]);
throw new RateLimitException(
'Hubspot rate limit reached for configuration ' . $this->config->getId(),
$retryAfter,
);
}
$this->rateLimiter->incrementRequestCount($this->config);
try {
return $apiCall();
} catch (Throwable $e) {
if ($this->isHubspotRateLimit($e)) {
$retryAfter = $this->parseRetryAfter($e);
$this->log->warning('[Hubspot] Received 429 from API', [
'team_id' => $this->config->team_id,
'config_id' => $this->config->getId(),
'retry_after' => $retryAfter,
'reason' => $e->getMessage(),
]);
throw new RateLimitException('Hubspot returned 429', $retryAfter, $e);
}
throw $e;
}
}
private function isHubspotRateLimit(Throwable $e): bool
{
return method_exists($e, 'getCode') && (int) $e->getCode() === 429;
}
private function parseRetryAfter(Throwable $e): int
{
if (method_exists($e, 'getResponseHeaders')) {
$headers = $e->getResponseHeaders() ?: [];
$value = $headers['Retry-After'] ?? $headers['retry-after'] ?? null;
if (is_array($value)) {
$value = $value[0] ?? null;
}
if (is_numeric($value)) {
return (int) $value;
}
}
return 10;
}
public function getMinimumApiVersion(): string
{
return self::MIN_API_VERSION;
}
public function getInstance(): Factory
{
return new Factory([
'key' => $this->accessToken,
'oauth2' => true,
'base_url' => $this->baseUrl,
]);
}
public function getNewInstance(): Discovery
{
return \HubSpot\Factory::createWithAccessToken($this->accessToken);
}
/**
* Secondly and daily limits for Hubspot API
*
* Product Tier: Free & Starter | Professional & Enterprise | API add-on (any tier)
* Burst: 100/10 seconds | 150/10 seconds | 200/10 seconds
* Daily: 250,000 | 500,000 | 1,000,000
*
* Official documentation states: The search endpoints are rate limited to five requests per second.
* Since with 5 RPS were still hitting secondly rate limits we lowered it to 4
*/
public function getPaginatedData(array $payload, string $type, int $offset = 0): array
{
$total = 0;
$lastId = null;
$rows = [];
foreach ($this->getPaginatedDataGenerator($payload, $type, $offset, $total, $lastId) as $row) {
$rows[] = $row;
}
return ['results' => $rows, 'total' => $total, 'last_record' => $lastId];
}
/**
* @throws HubspotException
* @throws SocialAccountTokenInvalidException
* @throws BadRequest
*/
public function getPaginatedDataGenerator(
array $payload,
string $type,
int $offset = 0,
int &$total = 0,
?string &$lastRecordId = null
): \Generator {
return $this->paginationService->getPaginatedDataGenerator(
$this,
$payload,
$type,
$offset,
$total,
$lastRecordId
);
}
/**
* @throws DealApiException
* @throws CrmException
*/
public function getOpportunityById(string $crmId, array $fields): array
{
try {
$deal = $this->executeRequest(fn () => $this->getNewInstance()->crm()->deals()->basicApi()->getById(
$crmId,
implode(',', $fields),
'companies,contacts'
));
} catch (DealApiException $e) {
$this->log->info('[Hubspot] Failed to fetch opportunity', [
'crm_id' => $crmId,
'reason' => $e->getMessage(),
]);
throw $e;
}
if (! $deal instanceof DealWithAssociations) {
throw new CrmException('Deal not found');
}
return [
'id' => $deal->getId(),
'properties' => $deal->getProperties(),
'associations' => $deal->getAssociations(),
];
}
/**
* Generic batch read method for HubSpot objects
*
* @param string $objectType The object type ('deals', 'companies', 'contacts')
* @param array<string> $crmIds Array of HubSpot object IDs (max 100)
* @param array<string> $fields Array of property names to fetch
*
* @return array<string, array> Array keyed by CRM ID with object data
*/
private function batchReadObjects(string $objectType, array $crmIds, array $fields): array
{
if (empty($crmIds)) {
return [];
}
$this->validateBatchSize($objectType, $crmIds);
$this->ensureValidToken();
try {
$batchConfig = $this->createBatchConfiguration($objectType);
$batchReadRequest = $this->prepareBatchRequest($batchConfig, $crmIds, $fields);
$response = $batchConfig['api']->read($batchReadRequest);
$this->validateApiResponse($response, $objectType);
$results = $this->processApiResults($response);
$this->logBatchResults($objectType, $crmIds, $results);
return $results;
} catch (\Throwable $e) {
$this->handleBatchError($e, $objectType, $crmIds);
}
}
private function validateBatchSize(string $objectType, array $crmIds): void
{
if (count($crmIds) > 100) {
throw new \InvalidArgumentException("Batch size cannot exceed 100 {$objectType}");
}
}
private function createBatchConfiguration(string $objectType): array
{
$configurations = [
'deals' => [
'batchReadRequest' => new \HubSpot\Client\Crm\Deals\Model\BatchReadInputSimplePublicObjectId(),
'inputClass' => \HubSpot\Client\Crm\Deals\Model\SimplePublicObjectId::class,
'api' => $this->getNewInstance()->crm()->deals()->batchApi(),
],
'companies' => [
'batchReadRequest' => new \HubSpot\Client\Crm\Companies\Model\BatchReadInputSimplePublicObjectId(),
'inputClass' => \HubSpot\Client\Crm\Companies\Model\SimplePublicObjectId::class,
'api' => $this->getNewInstance()->crm()->companies()->batchApi(),
],
'contacts' => [
'batchReadRequest' => new \HubSpot\Client\Crm\Contacts\Model\BatchReadInputSimplePublicObjectId(),
'inputClass' => \HubSpot\Client\Crm\Contacts\Model\SimplePublicObjectId::class,
'api' => $this->getNewInstance()->crm()->contacts()->batchApi(),
],
];
if (! isset($configurations[$objectType])) {
throw new \InvalidArgumentException("Unsupported object type: {$objectType}");
}
return $configurations[$objectType];
}
private function prepareBatchRequest(array $batchConfig, array $crmIds, array $fields): object
{
$batchReadRequest = $batchConfig['batchReadRequest'];
$inputClass = $batchConfig['inputClass'];
$inputs = array_map(function ($crmId) use ($inputClass) {
$input = new $inputClass();
$input->setId($crmId);
return $input;
}, $crmIds);
$batchReadRequest->setInputs($inputs);
$batchReadRequest->setProperties($fields);
return $batchReadRequest;
}
private function validateApiResponse($response, string $objectType): void
{
if (! $response) {
throw new CrmException("HubSpot API returned null response for {$objectType} batch read");
}
}
private function processApiResults($response): array
{
$results = [];
$responseResults = $response->getResults();
if ($responseResults) {
foreach ($responseResults as $object) {
if ($object && $object->getId()) {
$results[$object->getId()] = [
'id' => $object->getId(),
'properties' => $object->getProperties() ?: [],
];
}
}
}
return $results;
}
private function logBatchResults(string $objectType, array $crmIds, array $results): void
{
$this->log->info("[HubSpot] Batch fetched {$objectType}", [
'requested_count' => count($crmIds),
'returned_count' => count($results),
'crm_ids' => $crmIds,
]);
}
private function handleBatchError(\Throwable $e, string $objectType, array $crmIds): void
{
$errorMessage = $e->getMessage() ?: 'Unknown error';
$errorTrace = $e->getTraceAsString() ?: 'No trace available';
$this->log->error("[HubSpot] Failed to batch fetch {$objectType}", [
'crm_ids' => $crmIds,
'error' => $errorMessage,
'trace' => $errorTrace,
]);
throw new CrmException("Failed to batch fetch {$objectType}: " . $errorMessage);
}
/**
* Batch read multiple opportunities by their CRM IDs
*
* @param array<string> $crmIds Array of HubSpot deal IDs (max 100)
* @param array<string> $fields Array of property names to fetch
*
* @return array<string, array> Array keyed by CRM ID with opportunity data
*/
public function getOpportunitiesByIds(array $crmIds, array $fields): array
{
return $this->batchReadObjects('deals', $crmIds, $fields);
}
/**
* Batch read multiple companies by their CRM IDs
*
* @param array<string> $crmIds Array of HubSpot company IDs (max 100)
* @param array<string> $fields Array of property names to fetch
*
* @return array<string, array> Array keyed by CRM ID with company data
*/
public function getCompaniesByIds(array $crmIds, array $fields): array
{
return $this->batchReadObjects('companies', $crmIds, $fields);
}
/**
* Batch read multiple contacts by their CRM IDs
*
* @param array<string> $crmIds Array of HubSpot contact IDs (max 100)
* @param array<string> $fields Array of property names to fetch
*
* @return array<string, array> Array keyed by CRM ID with contact data
*/
public function getContactsByIds(array $crmIds, array $fields): array
{
return $this->batchReadObjects('contacts', $crmIds, $fields);
}
/**
* @throws CompanyApiException
* @throws CrmException
*/
public function getAccountById(string $crmId, array $fields): array
{
try {
$company = $this->getNewInstance()->crm()->companies()->basicApi()->getById(
$crmId,
implode(',', $fields),
);
} catch (CompanyApiException $e) {
$this->log->info('[Hubspot] Failed to fetch account', [
'crm_id' => $crmId,
'reason' => $e->getMessage(),
]);
throw $e;
}
if (! $company instanceof CompaniesWithAssociations) {
throw new CrmException('Account not found');
}
return [
'id' => $company->getId(),
'properties' => $company->getProperties(),
];
}
/**
* @throws ContactApiException
* @throws CrmException
*/
public function getContactById(string $crmId, array $fields): array
{
try {
$contact = $this->getNewInstance()->crm()->contacts()->basicApi()->getById(
$crmId,
implode(',', $fields)
);
} catch (ContactApiException $e) {
$this->log->info('[Hubspot] Failed to fetch contact', [
'crm_id' => $crmId,
'reason' => $e->getMessage(),
]);
throw $e;
}
if (! $contact instanceof ContactsWithAssociations) {
throw new CrmException('Contact not found');
}
return [
'id' => $contact->getId(),
'properties' => $contact->getProperties(),
];
}
/**
* This is email search request that Hubspot offers as GET (more generous quota)
*/
public function getContactByEmail(string $email, array $fields = []): array
{
try {
$contact = $this->getNewInstance()->crm()->contacts()->basicApi()->getById(
$email,
implode(',', $fields),
null,
false,
'email'
);
return [
'id' => $contact->getId(),
'properties' => $contact->getProperties(),
];
} catch (ContactApiException $e) {
$this->log->info('[Hubspot] Failed to fetch contact', [
'email' => $email,
'reason' => $e->getMessage(),
]);
return [];
}
}
/**
* @throws CrmException
*/
public function fetchProperty(string $objectType, string $propertyId): Property
{
$result = $this->getNewInstance()->crm()->properties()->coreApi()->getByName($objectType, $propertyId);
if (! $result instanceof Property) {
$this->log->error('[Hubspot] Failed to fetch property', [
'object_type' => $objectType,
'property_id' => $propertyId,
'reason' => $result->getMessage(),
]);
throw new CrmException('Failed to fetch property');
}
return $result;
}
/**
* @return array<CrmFieldOption>
*/
public function fetchPropertyOptions(string $objectType, string $propertyId): array
{
/** @var array<CrmFieldOption> */
return $this->fetchProperty($objectType, $propertyId)->getOptions();
}
/**
* @return array<array{id:string, label:string, deleted:bool}>
*/
public function fetchCallDispositions(): array
{
/** @var Response $response */
$response = $this->getInstance()->engagements()->getCallDispositions();
/**
* @var array<array{
* id:string,
* label:string,
* deleted: bool
* }>
*/
return $response->toArray();
}
/**
* @return array<CrmFieldOption>
*/
public function fetchOpportunityPipelineStages(): array
{
$stages = [];
$apiResponse = $this->getNewInstance()->crm()->pipelines()->pipelinesApi()->getAll('deals');
if ($apiResponse instanceof Error) {
$this->log->error('[Hubspot] Failed to fetch opportunity pipelines', [
'reason' => $apiResponse->getMessage(),
]);
return [];
}
foreach ($apiResponse->getResults() as $pipeline) {
$pipelineStages = array_map(
static function (PipelineStage $stage) {
return [
'id' => $stage->getId(),
'label' => $stage->getLabel(),
];
},
$pipeline->getStages()
);
$stages = array_merge($stages, $pipelineStages);
}
return $stages;
}
public function fetchOpportunityPipelines(): array
{
$pipelines = [];
try {
$apiResponse = $this->makeRequest('/crm/v3/pipelines/deals');
} catch (\Exception $e) {
$this->log->info('[Hubspot] Failed to fetch opportunity pipelines', [
'reason' => $e->getMessage(),
]);
return [];
}
$response = $apiResponse->toArray();
foreach ($response['results'] as $pipeline) {
$pipelines[] = [
'id' => $pipeline['id'],
'label' => $pipeline['label'],
];
}
return $pipelines;
}
/**
* @return array<CrmFieldOption>
*/
public function fetchMeetingOutcomeFieldOptions(Field $field): array
{
return $field->getCrmProviderId() === 'meetingOutcome'
? $this->fetchMeetingOutcomeTypes()
: $this->fetchCallActivityTypes();
}
public function fetchMeetingOutcomeTypes(): array
{
return $this->extractMeetingTypeOptions(
'[URL_WITH_CREDENTIALS] Response $response */
$response = $this->getInstance()
->getClient()
->request('GET', $endpoint);
/**
* @var array<array{
* value: string,
* label: string,
* displayOrder: int
* }> $optionData
*/
$optionData = $response->toArray()['options'] ?? [];
$options = [];
foreach ($optionData as $item) {
$options[] = [
'id' => $item['value'],
'value' => $item['value'],
'label' => $item['label'],
'display_order' => $item['displayOrder'],
];
}
return $options;
}
/**
* @return array<CrmFieldOption>
*/
public function fetchDispositionFieldOptions(): array
{
$options = [];
$dispositions = $this->fetchCallDispositions();
foreach ($dispositions as $disposition) {
if ($disposition['deleted'] !== false) {
continue;
}
$option['value'] = $disposition['id'];
$option['id'] = $disposition['id'];
$option['label'] = $disposition['label'];
$options[] = $option;
}
return $options;
}
/**
* @return array<CrmFieldOption>
*/
public function fetchOpportunityFieldOptions(Field $field): array
{
if ($field->isStageField()) {
return $this->fetchOpportunityPipelineStages();
}
if ($field->isPipelineField()) {
return $this->fetchOpportunityPipelines();
}
return $this->fetchPropertyOptions('deals', $field->getCrmProviderId());
}
/**
* @throws BadRequest
* @throws HubspotException
*/
public function makeRequest(string $endpoint, $method = 'GET', $payload = [], ?string $queryString = null)
{
$endpoint = self::BASE_URL . $endpoint;
if ($method === 'GET') {
$response = $this->getInstance()->getClient()?->request(
method: $method,
endpoint: $endpoint,
query_string: $queryString
);
} else {
$response = $this->getInstance()->getClient()->request($method, $endpoint, [
'json' => ($payload),
]);
}
$max = $response->getHeaderLine('X-HubSpot-RateLimit-Max'); // "110"
$remaining = $response->getHeaderLine('X-HubSpot-RateLimit-Remaining'); // "109"
$interval = $response->getHeaderLine('X-HubSpot-RateLimit-Interval-Milliseconds'); // "10000"
$body = json_decode((string) $response->getBody(), true);
\Illuminate\Support\Facades\Log::channel('custom_channel')->info('$max ' . PHP_EOL . print_r($max, true));
\Illuminate\Support\Facades\Log::channel('custom_channel')->info('$remaining ' . PHP_EOL . print_r($remaining, true));
\Illuminate\Support\Facades\Log::channel('custom_channel')->info('$interval ' . PHP_EOL . print_r($interval, true));
\Illuminate\Support\Facades\Log::channel('custom_channel')->info('$body ' . PHP_EOL . print_r($body, true));
return $response;
}
/**
* @throws BadRequest
* @throws HubspotException
*/
public function createMeeting(array $payload): Response
{
$endpoint = '/crm/v3/objects/meetings';
return $this->makeRequest($endpoint, 'POST', $payload);
}
/**
* @throws BadRequest
* @throws HubspotException
*/
public function updateMeeting(string $meetingId, array $payload): Response
{
$endpoint = '/crm/v3/objects/meetings/' . $meetingId;
return $this->makeRequest($endpoint, 'PATCH', $payload);
}
/**
* @throws \Exception
*/
public function createNote(
string $body,
string $ownerId,
int $timestamp,
string $objectId,
NoteObject $noteObject
): ?string {
try {
$noteInput = new SimplePublicObjectInput([
'properties' => [
'hs_note_body' => $body,
'hubspot_owner_id' => $ownerId,
'hs_timestamp' => $timestamp,
],
]);
// Create note
$note = $this->getNewInstance()->crm()->objects()->basicApi()->create('note', $noteInput);
$this->getNewInstance()->crm()->objects()->associationsApi()->create(
'note',
$note->getId(),
$this->getNoteObject($noteObject),
$objectId,
$this->getNoteAssociationType($noteObject),
);
return $note->getId();
} catch (\Exception $e) {
$this->log->error('[Hubspot] Failed to create note', [
'objectId' => $objectId,
'noteObject' => $noteObject->getObjectType(),
'reason' => $e->getMessage(),
]);
\Sentry::captureException($e);
}
return null;
}
public function updateEngagement(string $objectId, array $engagement, array $metadata): void
{
$this->getInstance()->engagements()->update($objectId, $engagement, $metadata);
}
public function getEngagementData(string $engagementId): array
{
$engagement = $this->getInstance()->engagements()->get($engagementId);
return $engagement->toArray();
}
public function createEngagement(array $engagement, array $associations, array $metadata): Response
{
return $this->getInstance()
->engagements()
->create($engagement, $associations, $metadata);
}
public function isUnauthorizedException(\Exception $e): bool
{
// Check for specific HubSpot API exception types first
if ($e instanceof BadRequest) {
// BadRequest can contain 401 status codes
return $e->getCode() === 401;
}
// Check for HTTP client exceptions with status codes
if ($e instanceof \GuzzleHttp\Exception\RequestException && $e->hasResponse()) {
$response = $e->getResponse();
if ($response !== null) {
return $response->getStatusCode() === 401;
}
}
// Check for Guzzle HTTP exceptions
if ($e instanceof \GuzzleHttp\Exception\ClientException) {
return $e->getCode() === 401;
}
// Fallback to string matching as last resort, but be more specific
$message = strtolower($e->getMessage());
return str_contains($message, '401 unauthorized') ||
str_contains($message, 'http 401') ||
str_contains($message, 'status code 401') ||
(preg_match('/\b401\b/', $message) && str_contains($message, 'unauthorized'));
}
/**
* Validates and refreshes the access token if needed before API requests.
* This ensures long-running processes don't fail due to token expiration.
*
* @throws SocialAccountTokenInvalidException
*/
public function ensureValidToken(): void
{
if ($this->oauthAccount === null) {
return;
}
$newToken = $this->tokenManager->ensureValidToken($this->oauthAccount);
if ($newToken !== null) {
$this->accessToken = $newToken;
}
}
public function getConfig()
{
return $this->config;
}
// returns only active (archived=false)
public function getOwners(): array
{
return $this->getNewInstance()->crm()->owners()->getAll();
}
/**
* @param bool $archived
*
* @return array<Owner>|[]
*/
public function getOwnersArchived(bool $archived = true): array
{
$endpoint = '/crm/v3/owners';
$queryParams = [
'archived' => $archived ? 'true' : 'false',
];
$queryString = http_build_query($queryParams);
$owners = [];
try {
$response = $this->makeRequest(endpoint: $endpoint, queryString: $queryString);
$responseData = $response?->toArray();
foreach ($responseData['results'] as $result) {
try {
$owners[] = Owner::create($result);
} catch (Throwable $e) {
$this->log->error('[HubSpot] Failed to process owner data', [
'result' => $result,
'error' => $e->getMessage(),
]);
continue;
}
}
} catch (Throwable $e) {
$this->log->error('HubSpot] Failed to fetch owners', [
'archived' => $archived,
'error' => $e->getMessage(),
]);
return [];
}
return $owners;
}
public function getMeeting(string $engagementId): ObjectWithAssociations
{
return $this->getNewInstance()->crm()->objects()->basicApi()
->getById('meeting', $engagementId, null, 'contact,company,deal');
}
public function deleteEngagement(string $engagementId): void
{
$this->getInstance()->engagements()->delete((int) $engagementId);
}
public function getAssociationsData(array $ids, string $fromObject, string $toObject): array
{
$associationData = [];
$idChunks = array_chunk($ids, self::ASSOCIATIONS_BATCH_SIZE_LIMIT);
foreach ($idChunks as $idChunk) {
try {
$batchInput = new \HubSpot\Client\Crm\Associations\Model\BatchInputPublicObjectId();
$batchInput->setInputs(array_map(function ($id) {
$publicObjectId = new \HubSpot\Client\Crm\Associations\Model\PublicObjectId();
$publicObjectId->setId($id);
return $publicObjectId;
}, $idChunk));
$associatedObjectsData = $this
->getNewInstance()
->crm()
->associations()
->batchApi()
->read($fromObject, $toObject, $batchInput);
if ($associatedObjectsData instanceof \HubSpot\Client\Crm\Associations\Model\BatchResponsePublicAssociationMulti) {
foreach ($associatedObjectsData->getResults() as $association) {
$from = $association->getFrom()->getId();
$toAssociations = $association->getTo();
if (! empty($toAssociations)) {
$associationData[$from] = array_map(function ($item) {
return $item->getId();
}, $toAssociations);
}
}
}
} catch (\Exception $e) {
$this->log->error('[Hubspot] Failed to fetch associations', [
'from_object' => $fromObject,
'to_object' => $toObject,
'reason' => $e->getMessage(),
]);
}
}
return $associationData;
}
/**
* @throws \Exception
*/
private function getNoteAssociationType(NoteObject $noteObject): string
{
return match($noteObject) {
NoteObject::Opportunity => 'note_to_deal',
NoteObject::Lead, NoteObject::Contact => 'note_to_contact', // or 'note_to_lead' if your portal supports it
NoteObject::Account => 'note_to_company',
NoteObject::Call, NoteObject::Event => throw new \Exception('Not supported'),
};
}
/**
* @throws \Exception
*/
private function getNoteObject(NoteObject $noteObject): string
{
return match($noteObject) {
NoteObject::Opportunity => 'deal',
NoteObject::Lead, NoteObject::Contact => 'contact',
NoteObject::Account => 'company',
NoteObject::Call, NoteObject::Event => throw new \Exception('Not supported'),
};
}
public function addAssociations(string $objectType, string $associationType, array $payload): Response
{
$endpoint = "/crm/v4/associations/$objectType/$associationType/batch/create";
return $this->makeRequest($endpoint, 'POST', $payload);
}
public function removeAssociations(string $objectType, string $associationType, array $payload): Response
{
$endpoint = "/crm/v4/associations/$objectType/$associationType/batch/archive";
return $this->makeRequest($endpoint, 'POST', $payload);
}
}
Project
Project
New File or Directory…
Expand Selected
Collapse All
Options
Hide...
|
2716
|
NULL
|
NULL
|
NULL
|
|
2719
|
112
|
31
|
2026-05-07T11:36:49.845456+00:00
|
/Users/lukas/.screenpipe/data/data/2026-05-07/1778 /Users/lukas/.screenpipe/data/data/2026-05-07/1778153809845_m2.jpg...
|
PhpStorm
|
faVsco.js – custom.log
|
True
|
NULL
|
monitor_2
|
NULL
|
NULL
|
NULL
|
NULL
|
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
15
Previous Highlighted Error
Next Highlighted Error
lukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/.screenpipe/data/data $ npx screenpipe@latest record --help
Start recording screen, audio, and serve the API
Usage: screenpipe record [OPTIONS]
Options:
-d, --audio-chunk-duration <AUDIO_CHUNK_DURATION>
Audio chunk duration in seconds
[default: 30]
-p, --port <PORT>
Port to run the server on
[default: 3030]
--disable-audio
Disable audio recording
-i, --audio-device <AUDIO_DEVICE>
Audio devices to use (can be specified multiple times)
--use-system-default-audio
Follow system default audio devices
--experimental-coreaudio-system-audio
[experimental, macOS 14.4+] Capture System Audio via CoreAudio Process Tap instead of ScreenCaptureKit. Off by default; ignored on older macOS and non-macOS
--data-dir <DATA_DIR>
Data directory. Default to $HOME/.screenpipe
--debug
Enable debug logging for screenpipe modules
-a, --audio-transcription-engine <AUDIO_TRANSCRIPTION_ENGINE>
Audio transcription engine to use
Possible values:
- deepgram
- whisper-tiny
- whisper-tiny-quantized
- whisper-large
- whisper-large-quantized
- whisper-large-v3-turbo
- whisper-large-v3-turbo-quantized
- openai-compatible
- qwen3-asr
- parakeet
- disabled: Disable transcription (audio capture only, no speech-to-text)
[default: parakeet]
-m, --monitor-id <MONITOR_ID>
Monitor IDs to record. May be specified multiple times. When set, only the listed monitors are recorded (implies `--use-all-monitors=false`)
--use-all-monitors
Automatically record all monitors. Ignored when `--monitor-id` is passed
-l, --language <LANGUAGE>
Languages for OCR/transcription
[possible values: english, chinese, german, spanish, russian, korean, french, japanese, portuguese, turkish, polish, catalan, dutch, arabic, swedish, italian, indonesian, hindi, vietnamese, finnish, hebrew, ukrainian, greek, malay, czech, romanian, danish, hungarian, norwegian, thai, urdu, croatian, bulgarian, lithuanian, latin, malayalam, welsh, slovak, persian, latvian, bengali, serbian, azerbaijani, slovenian, estonian, macedonian, nepali, mongolian, bosnian, kazakh, albanian, swahili, galician, marathi, punjabi, sinhala, khmer, afrikaans, belarusian, gujarati, amharic, yiddish, lao, uzbek, faroese, pashto, maltese, sanskrit, luxembourgish, myanmar, tibetan, tagalog, assamese, tatar, hausa, javanese]
--use-pii-removal
Enable PII removal
--filter-music
Filter music-dominant audio before transcription (reduces Spotify/YouTube music noise)
--disable-vision
Disable vision recording
--ignored-windows <IGNORED_WINDOWS>
Windows to ignore (by title, uses contains matching)
--included-windows <INCLUDED_WINDOWS>
Windows to include (by title, uses contains matching)
--ignored-urls <IGNORED_URLS>
URLs to ignore for browser privacy filtering
--deepgram-api-key <DEEPGRAM_API_KEY>
Deepgram API Key for audio transcription
--transcription-mode <TRANSCRIPTION_MODE>
Audio transcription scheduling mode: batch (default, longer chunks for quality) or realtime
Possible values:
- realtime: Transcribe immediately as audio is captured
- batch: Accumulate longer audio batches for better transcription quality (default)
[default: batch]
--disable-telemetry
Disable telemetry
--video-quality <VIDEO_QUALITY>
Video quality preset: low, balanced, high, max
[default: balanced]
--enable-sync
Enable cloud sync
--sync-token <SYNC_TOKEN>
API token for cloud sync
[env: SCREENPIPE_SYNC_TOKEN=]
--sync-password <SYNC_PASSWORD>
Password for encrypting synced data
[env: SCREENPIPE_SYNC_PASSWORD=[PASSWORD]
--sync-interval-secs <SYNC_INTERVAL_SECS>
Interval between sync cycles in seconds
[default: 300]
--sync-machine-id <SYNC_MACHINE_ID>
Override the machine ID for this device
--pause-on-drm-content
Pause screen and audio capture when a DRM-protected streaming app (Netflix, Disney+, etc.) or a remote-desktop client (Omnissa/VMware Horizon) is focused — these blank their windows while any app is recording the screen
--api-auth
Require authentication for remote API access. When enabled, non-localhost requests must include Authorization: Bearer <SCREENPIPE_API_KEY>. Localhost requests are always allowed
--listen-on-lan
Bind the HTTP server to [IP_ADDRESS] so other devices on the LAN can reach it. Off by default — the server binds [IP_ADDRESS] only. `--api-auth` is forced on whenever this flag is used; you can't accidentally expose an unauthenticated API on your network
--encrypt-secrets
Encrypt secrets (API keys, OAuth tokens) at rest using the OS keychain. Creates a keychain key if one doesn't exist. Without this flag, the CLI will use an existing key (created by the desktop app) but won't create one
--retention-days <RETENTION_DAYS>
Local data retention in days. Old screen/audio data is auto-deleted after this period. Set to 0 to disable retention (keep data forever)
[default: 14]
-h, --help
Print help (see a summary with '-h')
Sync Changes
Hide This Notification
Code changed:
Hide
2
60
Previous Highlighted Error
Next Highlighted Error
<?php
declare(strict_types=1);
namespace Jiminny\Services\Crm\Hubspot;
use HubSpot\Client\Crm\Deals\ApiException as DealApiException;
use HubSpot\Client\Crm\Contacts\ApiException as ContactApiException;
use HubSpot\Client\Crm\Companies\ApiException as CompanyApiException;
use HubSpot\Client\Crm\Contacts\Model\SimplePublicObjectWithAssociations as ContactsWithAssociations;
use HubSpot\Client\Crm\Companies\Model\SimplePublicObjectWithAssociations as CompaniesWithAssociations;
use HubSpot\Client\Crm\Deals\Model\SimplePublicObjectWithAssociations as DealWithAssociations;
use HubSpot\Client\Crm\Objects\Model\SimplePublicObjectInput;
use HubSpot\Client\Crm\Objects\Model\SimplePublicObjectWithAssociations as ObjectWithAssociations;
use HubSpot\Client\Crm\Pipelines\Model\Error;
use HubSpot\Client\Crm\Pipelines\Model\PipelineStage;
use HubSpot\Client\Crm\Properties\Model\Property;
use HubSpot\Discovery\Discovery;
use Jiminny\Component\Utility\Service\ProviderRateLimiter;
use Jiminny\Exceptions\CrmException;
use Jiminny\Exceptions\RateLimitException;
use Jiminny\Exceptions\SocialAccountTokenInvalidException;
use Jiminny\Jobs\Crm\NoteObject;
use Jiminny\Models\Crm\Field;
use Jiminny\Services\Crm\BaseClient;
use Jiminny\Services\Crm\Hubspot\DTO\Response\Owner;
use Jiminny\Services\SocialAccountService;
use SevenShores\Hubspot\Exceptions\BadRequest;
use SevenShores\Hubspot\Exceptions\HubspotException;
use SevenShores\Hubspot\Factory;
use SevenShores\Hubspot\Http\Response;
use Jiminny\Services\Crm\Hubspot\Pagination\HubspotPaginationService;
use Throwable;
/**
* @phpstan-type CrmFieldOption array{id:string, label:string, value?:string}
*/
class Client extends BaseClient implements HubspotClientInterface
{
public const string MIN_API_VERSION = '2';
public const string BASE_URL = '[URL_WITH_CREDENTIALS] T
* @param callable(): T $apiCall
* @return T
*
* @throws RateLimitException
*/
private function executeRequest(callable $apiCall)
{
if (! $this->rateLimiter->canMakeRequest($this->config)) {
$retryAfter = $this->rateLimiter->requestAvailableIn($this->config);
$this->log->warning('[Hubspot] Rate limit exceeded, deferring request', [
'team_id' => $this->config->team_id,
'config_id' => $this->config->getId(),
'retry_after' => $retryAfter,
]);
throw new RateLimitException(
'Hubspot rate limit reached for configuration ' . $this->config->getId(),
$retryAfter,
);
}
$this->rateLimiter->incrementRequestCount($this->config);
try {
return $apiCall();
} catch (Throwable $e) {
if ($this->isHubspotRateLimit($e)) {
$retryAfter = $this->parseRetryAfter($e);
$this->log->warning('[Hubspot] Received 429 from API', [
'team_id' => $this->config->team_id,
'config_id' => $this->config->getId(),
'retry_after' => $retryAfter,
'reason' => $e->getMessage(),
]);
throw new RateLimitException('Hubspot returned 429', $retryAfter, $e);
}
throw $e;
}
}
private function isHubspotRateLimit(Throwable $e): bool
{
return method_exists($e, 'getCode') && (int) $e->getCode() === 429;
}
private function parseRetryAfter(Throwable $e): int
{
if (method_exists($e, 'getResponseHeaders')) {
$headers = $e->getResponseHeaders() ?: [];
$value = $headers['Retry-After'] ?? $headers['retry-after'] ?? null;
if (is_array($value)) {
$value = $value[0] ?? null;
}
if (is_numeric($value)) {
return (int) $value;
}
}
return 10;
}
public function getMinimumApiVersion(): string
{
return self::MIN_API_VERSION;
}
public function getInstance(): Factory
{
return new Factory([
'key' => $this->accessToken,
'oauth2' => true,
'base_url' => $this->baseUrl,
]);
}
public function getNewInstance(): Discovery
{
return \HubSpot\Factory::createWithAccessToken($this->accessToken);
}
/**
* Secondly and daily limits for Hubspot API
*
* Product Tier: Free & Starter | Professional & Enterprise | API add-on (any tier)
* Burst: 100/10 seconds | 150/10 seconds | 200/10 seconds
* Daily: 250,000 | 500,000 | 1,000,000
*
* Official documentation states: The search endpoints are rate limited to five requests per second.
* Since with 5 RPS were still hitting secondly rate limits we lowered it to 4
*/
public function getPaginatedData(array $payload, string $type, int $offset = 0): array
{
$total = 0;
$lastId = null;
$rows = [];
foreach ($this->getPaginatedDataGenerator($payload, $type, $offset, $total, $lastId) as $row) {
$rows[] = $row;
}
return ['results' => $rows, 'total' => $total, 'last_record' => $lastId];
}
/**
* @throws HubspotException
* @throws SocialAccountTokenInvalidException
* @throws BadRequest
*/
public function getPaginatedDataGenerator(
array $payload,
string $type,
int $offset = 0,
int &$total = 0,
?string &$lastRecordId = null
): \Generator {
return $this->paginationService->getPaginatedDataGenerator(
$this,
$payload,
$type,
$offset,
$total,
$lastRecordId
);
}
/**
* @throws DealApiException
* @throws CrmException
*/
public function getOpportunityById(string $crmId, array $fields): array
{
try {
$deal = $this->executeRequest(fn () => $this->getNewInstance()->crm()->deals()->basicApi()->getById(
$crmId,
implode(',', $fields),
'companies,contacts'
));
} catch (DealApiException $e) {
$this->log->info('[Hubspot] Failed to fetch opportunity', [
'crm_id' => $crmId,
'reason' => $e->getMessage(),
]);
throw $e;
}
if (! $deal instanceof DealWithAssociations) {
throw new CrmException('Deal not found');
}
return [
'id' => $deal->getId(),
'properties' => $deal->getProperties(),
'associations' => $deal->getAssociations(),
];
}
/**
* Generic batch read method for HubSpot objects
*
* @param string $objectType The object type ('deals', 'companies', 'contacts')
* @param array<string> $crmIds Array of HubSpot object IDs (max 100)
* @param array<string> $fields Array of property names to fetch
*
* @return array<string, array> Array keyed by CRM ID with object data
*/
private function batchReadObjects(string $objectType, array $crmIds, array $fields): array
{
if (empty($crmIds)) {
return [];
}
$this->validateBatchSize($objectType, $crmIds);
$this->ensureValidToken();
try {
$batchConfig = $this->createBatchConfiguration($objectType);
$batchReadRequest = $this->prepareBatchRequest($batchConfig, $crmIds, $fields);
$response = $batchConfig['api']->read($batchReadRequest);
$this->validateApiResponse($response, $objectType);
$results = $this->processApiResults($response);
$this->logBatchResults($objectType, $crmIds, $results);
return $results;
} catch (\Throwable $e) {
$this->handleBatchError($e, $objectType, $crmIds);
}
}
private function validateBatchSize(string $objectType, array $crmIds): void
{
if (count($crmIds) > 100) {
throw new \InvalidArgumentException("Batch size cannot exceed 100 {$objectType}");
}
}
private function createBatchConfiguration(string $objectType): array
{
$configurations = [
'deals' => [
'batchReadRequest' => new \HubSpot\Client\Crm\Deals\Model\BatchReadInputSimplePublicObjectId(),
'inputClass' => \HubSpot\Client\Crm\Deals\Model\SimplePublicObjectId::class,
'api' => $this->getNewInstance()->crm()->deals()->batchApi(),
],
'companies' => [
'batchReadRequest' => new \HubSpot\Client\Crm\Companies\Model\BatchReadInputSimplePublicObjectId(),
'inputClass' => \HubSpot\Client\Crm\Companies\Model\SimplePublicObjectId::class,
'api' => $this->getNewInstance()->crm()->companies()->batchApi(),
],
'contacts' => [
'batchReadRequest' => new \HubSpot\Client\Crm\Contacts\Model\BatchReadInputSimplePublicObjectId(),
'inputClass' => \HubSpot\Client\Crm\Contacts\Model\SimplePublicObjectId::class,
'api' => $this->getNewInstance()->crm()->contacts()->batchApi(),
],
];
if (! isset($configurations[$objectType])) {
throw new \InvalidArgumentException("Unsupported object type: {$objectType}");
}
return $configurations[$objectType];
}
private function prepareBatchRequest(array $batchConfig, array $crmIds, array $fields): object
{
$batchReadRequest = $batchConfig['batchReadRequest'];
$inputClass = $batchConfig['inputClass'];
$inputs = array_map(function ($crmId) use ($inputClass) {
$input = new $inputClass();
$input->setId($crmId);
return $input;
}, $crmIds);
$batchReadRequest->setInputs($inputs);
$batchReadRequest->setProperties($fields);
return $batchReadRequest;
}
private function validateApiResponse($response, string $objectType): void
{
if (! $response) {
throw new CrmException("HubSpot API returned null response for {$objectType} batch read");
}
}
private function processApiResults($response): array
{
$results = [];
$responseResults = $response->getResults();
if ($responseResults) {
foreach ($responseResults as $object) {
if ($object && $object->getId()) {
$results[$object->getId()] = [
'id' => $object->getId(),
'properties' => $object->getProperties() ?: [],
];
}
}
}
return $results;
}
private function logBatchResults(string $objectType, array $crmIds, array $results): void
{
$this->log->info("[HubSpot] Batch fetched {$objectType}", [
'requested_count' => count($crmIds),
'returned_count' => count($results),
'crm_ids' => $crmIds,
]);
}
private function handleBatchError(\Throwable $e, string $objectType, array $crmIds): void
{
$errorMessage = $e->getMessage() ?: 'Unknown error';
$errorTrace = $e->getTraceAsString() ?: 'No trace available';
$this->log->error("[HubSpot] Failed to batch fetch {$objectType}", [
'crm_ids' => $crmIds,
'error' => $errorMessage,
'trace' => $errorTrace,
]);
throw new CrmException("Failed to batch fetch {$objectType}: " . $errorMessage);
}
/**
* Batch read multiple opportunities by their CRM IDs
*
* @param array<string> $crmIds Array of HubSpot deal IDs (max 100)
* @param array<string> $fields Array of property names to fetch
*
* @return array<string, array> Array keyed by CRM ID with opportunity data
*/
public function getOpportunitiesByIds(array $crmIds, array $fields): array
{
return $this->batchReadObjects('deals', $crmIds, $fields);
}
/**
* Batch read multiple companies by their CRM IDs
*
* @param array<string> $crmIds Array of HubSpot company IDs (max 100)
* @param array<string> $fields Array of property names to fetch
*
* @return array<string, array> Array keyed by CRM ID with company data
*/
public function getCompaniesByIds(array $crmIds, array $fields): array
{
return $this->batchReadObjects('companies', $crmIds, $fields);
}
/**
* Batch read multiple contacts by their CRM IDs
*
* @param array<string> $crmIds Array of HubSpot contact IDs (max 100)
* @param array<string> $fields Array of property names to fetch
*
* @return array<string, array> Array keyed by CRM ID with contact data
*/
public function getContactsByIds(array $crmIds, array $fields): array
{
return $this->batchReadObjects('contacts', $crmIds, $fields);
}
/**
* @throws CompanyApiException
* @throws CrmException
*/
public function getAccountById(string $crmId, array $fields): array
{
try {
$company = $this->getNewInstance()->crm()->companies()->basicApi()->getById(
$crmId,
implode(',', $fields),
);
} catch (CompanyApiException $e) {
$this->log->info('[Hubspot] Failed to fetch account', [
'crm_id' => $crmId,
'reason' => $e->getMessage(),
]);
throw $e;
}
if (! $company instanceof CompaniesWithAssociations) {
throw new CrmException('Account not found');
}
return [
'id' => $company->getId(),
'properties' => $company->getProperties(),
];
}
/**
* @throws ContactApiException
* @throws CrmException
*/
public function getContactById(string $crmId, array $fields): array
{
try {
$contact = $this->getNewInstance()->crm()->contacts()->basicApi()->getById(
$crmId,
implode(',', $fields)
);
} catch (ContactApiException $e) {
$this->log->info('[Hubspot] Failed to fetch contact', [
'crm_id' => $crmId,
'reason' => $e->getMessage(),
]);
throw $e;
}
if (! $contact instanceof ContactsWithAssociations) {
throw new CrmException('Contact not found');
}
return [
'id' => $contact->getId(),
'properties' => $contact->getProperties(),
];
}
/**
* This is email search request that Hubspot offers as GET (more generous quota)
*/
public function getContactByEmail(string $email, array $fields = []): array
{
try {
$contact = $this->getNewInstance()->crm()->contacts()->basicApi()->getById(
$email,
implode(',', $fields),
null,
false,
'email'
);
return [
'id' => $contact->getId(),
'properties' => $contact->getProperties(),
];
} catch (ContactApiException $e) {
$this->log->info('[Hubspot] Failed to fetch contact', [
'email' => $email,
'reason' => $e->getMessage(),
]);
return [];
}
}
/**
* @throws CrmException
*/
public function fetchProperty(string $objectType, string $propertyId): Property
{
$result = $this->getNewInstance()->crm()->properties()->coreApi()->getByName($objectType, $propertyId);
if (! $result instanceof Property) {
$this->log->error('[Hubspot] Failed to fetch property', [
'object_type' => $objectType,
'property_id' => $propertyId,
'reason' => $result->getMessage(),
]);
throw new CrmException('Failed to fetch property');
}
return $result;
}
/**
* @return array<CrmFieldOption>
*/
public function fetchPropertyOptions(string $objectType, string $propertyId): array
{
/** @var array<CrmFieldOption> */
return $this->fetchProperty($objectType, $propertyId)->getOptions();
}
/**
* @return array<array{id:string, label:string, deleted:bool}>
*/
public function fetchCallDispositions(): array
{
/** @var Response $response */
$response = $this->getInstance()->engagements()->getCallDispositions();
/**
* @var array<array{
* id:string,
* label:string,
* deleted: bool
* }>
*/
return $response->toArray();
}
/**
* @return array<CrmFieldOption>
*/
public function fetchOpportunityPipelineStages(): array
{
$stages = [];
$apiResponse = $this->getNewInstance()->crm()->pipelines()->pipelinesApi()->getAll('deals');
if ($apiResponse instanceof Error) {
$this->log->error('[Hubspot] Failed to fetch opportunity pipelines', [
'reason' => $apiResponse->getMessage(),
]);
return [];
}
foreach ($apiResponse->getResults() as $pipeline) {
$pipelineStages = array_map(
static function (PipelineStage $stage) {
return [
'id' => $stage->getId(),
'label' => $stage->getLabel(),
];
},
$pipeline->getStages()
);
$stages = array_merge($stages, $pipelineStages);
}
return $stages;
}
public function fetchOpportunityPipelines(): array
{
$pipelines = [];
try {
$apiResponse = $this->makeRequest('/crm/v3/pipelines/deals');
} catch (\Exception $e) {
$this->log->info('[Hubspot] Failed to fetch opportunity pipelines', [
'reason' => $e->getMessage(),
]);
return [];
}
$response = $apiResponse->toArray();
foreach ($response['results'] as $pipeline) {
$pipelines[] = [
'id' => $pipeline['id'],
'label' => $pipeline['label'],
];
}
return $pipelines;
}
/**
* @return array<CrmFieldOption>
*/
public function fetchMeetingOutcomeFieldOptions(Field $field): array
{
return $field->getCrmProviderId() === 'meetingOutcome'
? $this->fetchMeetingOutcomeTypes()
: $this->fetchCallActivityTypes();
}
public function fetchMeetingOutcomeTypes(): array
{
return $this->extractMeetingTypeOptions(
'[URL_WITH_CREDENTIALS] Response $response */
$response = $this->getInstance()
->getClient()
->request('GET', $endpoint);
/**
* @var array<array{
* value: string,
* label: string,
* displayOrder: int
* }> $optionData
*/
$optionData = $response->toArray()['options'] ?? [];
$options = [];
foreach ($optionData as $item) {
$options[] = [
'id' => $item['value'],
'value' => $item['value'],
'label' => $item['label'],
'display_order' => $item['displayOrder'],
];
}
return $options;
}
/**
* @return array<CrmFieldOption>
*/
public function fetchDispositionFieldOptions(): array
{
$options = [];
$dispositions = $this->fetchCallDispositions();
foreach ($dispositions as $disposition) {
if ($disposition['deleted'] !== false) {
continue;
}
$option['value'] = $disposition['id'];
$option['id'] = $disposition['id'];
$option['label'] = $disposition['label'];
$options[] = $option;
}
return $options;
}
/**
* @return array<CrmFieldOption>
*/
public function fetchOpportunityFieldOptions(Field $field): array
{
if ($field->isStageField()) {
return $this->fetchOpportunityPipelineStages();
}
if ($field->isPipelineField()) {
return $this->fetchOpportunityPipelines();
}
return $this->fetchPropertyOptions('deals', $field->getCrmProviderId());
}
/**
* @throws BadRequest
* @throws HubspotException
*/
public function makeRequest(string $endpoint, $method = 'GET', $payload = [], ?string $queryString = null)
{
$endpoint = self::BASE_URL . $endpoint;
if ($method === 'GET') {
$response = $this->getInstance()->getClient()?->request(
method: $method,
endpoint: $endpoint,
query_string: $queryString
);
} else {
$response = $this->getInstance()->getClient()->request($method, $endpoint, [
'json' => ($payload),
]);
}
$max = $response->getHeaderLine('X-HubSpot-RateLimit-Max'); // "110"
$remaining = $response->getHeaderLine('X-HubSpot-RateLimit-Remaining'); // "109"
$interval = $response->getHeaderLine('X-HubSpot-RateLimit-Interval-Milliseconds'); // "10000"
$body = json_decode((string) $response->getBody(), true);
\Illuminate\Support\Facades\Log::channel('custom_channel')->info('$max ' . PHP_EOL . print_r($max, true));
\Illuminate\Support\Facades\Log::channel('custom_channel')->info('$remaining ' . PHP_EOL . print_r($remaining, true));
\Illuminate\Support\Facades\Log::channel('custom_channel')->info('$interval ' . PHP_EOL . print_r($interval, true));
\Illuminate\Support\Facades\Log::channel('custom_channel')->info('$body ' . PHP_EOL . print_r($body, true));
return $response;
}
/**
* @throws BadRequest
* @throws HubspotException
*/
public function createMeeting(array $payload): Response
{
$endpoint = '/crm/v3/objects/meetings';
return $this->makeRequest($endpoint, 'POST', $payload);
}
/**
* @throws BadRequest
* @throws HubspotException
*/
public function updateMeeting(string $meetingId, array $payload): Response
{
$endpoint = '/crm/v3/objects/meetings/' . $meetingId;
return $this->makeRequest($endpoint, 'PATCH', $payload);
}
/**
* @throws \Exception
*/
public function createNote(
string $body,
string $ownerId,
int $timestamp,
string $objectId,
NoteObject $noteObject
): ?string {
try {
$noteInput = new SimplePublicObjectInput([
'properties' => [
'hs_note_body' => $body,
'hubspot_owner_id' => $ownerId,
'hs_timestamp' => $timestamp,
],
]);
// Create note
$note = $this->getNewInstance()->crm()->objects()->basicApi()->create('note', $noteInput);
$this->getNewInstance()->crm()->objects()->associationsApi()->create(
'note',
$note->getId(),
$this->getNoteObject($noteObject),
$objectId,
$this->getNoteAssociationType($noteObject),
);
return $note->getId();
} catch (\Exception $e) {
$this->log->error('[Hubspot] Failed to create note', [
'objectId' => $objectId,
'noteObject' => $noteObject->getObjectType(),
'reason' => $e->getMessage(),
]);
\Sentry::captureException($e);
}
return null;
}
public function updateEngagement(string $objectId, array $engagement, array $metadata): void
{
$this->getInstance()->engagements()->update($objectId, $engagement, $metadata);
}
public function getEngagementData(string $engagementId): array
{
$engagement = $this->getInstance()->engagements()->get($engagementId);
return $engagement->toArray();
}
public function createEngagement(array $engagement, array $associations, array $metadata): Response
{
return $this->getInstance()
->engagements()
->create($engagement, $associations, $metadata);
}
public function isUnauthorizedException(\Exception $e): bool
{
// Check for specific HubSpot API exception types first
if ($e instanceof BadRequest) {
// BadRequest can contain 401 status codes
return $e->getCode() === 401;
}
// Check for HTTP client exceptions with status codes
if ($e instanceof \GuzzleHttp\Exception\RequestException && $e->hasResponse()) {
$response = $e->getResponse();
if ($response !== null) {
return $response->getStatusCode() === 401;
}
}
// Check for Guzzle HTTP exceptions
if ($e instanceof \GuzzleHttp\Exception\ClientException) {
return $e->getCode() === 401;
}
// Fallback to string matching as last resort, but be more specific
$message = strtolower($e->getMessage());
return str_contains($message, '401 unauthorized') ||
str_contains($message, 'http 401') ||
str_contains($message, 'status code 401') ||
(preg_match('/\b401\b/', $message) && str_contains($message, 'unauthorized'));
}
/**
* Validates and refreshes the access token if needed before API requests.
* This ensures long-running processes don't fail due to token expiration.
*
* @throws SocialAccountTokenInvalidException
*/
public function ensureValidToken(): void
{
if ($this->oauthAccount === null) {
return;
}
$newToken = $this->tokenManager->ensureValidToken($this->oauthAccount);
if ($newToken !== null) {
$this->accessToken = $newToken;
}
}
public function getConfig()
{
return $this->config;
}
// returns only active (archived=false)
public function getOwners(): array
{
return $this->getNewInstance()->crm()->owners()->getAll();
}
/**
* @param bool $archived
*
* @return array<Owner>|[]
*/
public function getOwnersArchived(bool $archived = true): array
{
$endpoint = '/crm/v3/owners';
$queryParams = [
'archived' => $archived ? 'true' : 'false',
];
$queryString = http_build_query($queryParams);
$owners = [];
try {
$response = $this->makeRequest(endpoint: $endpoint, queryString: $queryString);
$responseData = $response?->toArray();
foreach ($responseData['results'] as $result) {
try {
$owners[] = Owner::create($result);
} catch (Throwable $e) {
$this->log->error('[HubSpot] Failed to process owner data', [
'result' => $result,
'error' => $e->getMessage(),
]);
continue;
}
}
} catch (Throwable $e) {
$this->log->error('HubSpot] Failed to fetch owners', [
'archived' => $archived,
'error' => $e->getMessage(),
]);
return [];
}
return $owners;
}
public function getMeeting(string $engagementId): ObjectWithAssociations
{
return $this->getNewInstance()->crm()->objects()->basicApi()
->getById('meeting', $engagementId, null, 'contact,company,deal');
}
public function deleteEngagement(string $engagementId): void
{
$this->getInstance()->engagements()->delete((int) $engagementId);
}
public function getAssociationsData(array $ids, string $fromObject, string $toObject): array
{
$associationData = [];
$idChunks = array_chunk($ids, self::ASSOCIATIONS_BATCH_SIZE_LIMIT);
foreach ($idChunks as $idChunk) {
try {
$batchInput = new \HubSpot\Client\Crm\Associations\Model\BatchInputPublicObjectId();
$batchInput->setInputs(array_map(function ($id) {
$publicObjectId = new \HubSpot\Client\Crm\Associations\Model\PublicObjectId();
$publicObjectId->setId($id);
return $publicObjectId;
}, $idChunk));
$associatedObjectsData = $this
->getNewInstance()
->crm()
->associations()
->batchApi()
->read($fromObject, $toObject, $batchInput);
if ($associatedObjectsData instanceof \HubSpot\Client\Crm\Associations\Model\BatchResponsePublicAssociationMulti) {
foreach ($associatedObjectsData->getResults() as $association) {
$from = $association->getFrom()->getId();
$toAssociations = $association->getTo();
if (! empty($toAssociations)) {
$associationData[$from] = array_map(function ($item) {
return $item->getId();
}, $toAssociations);
}
}
}
} catch (\Exception $e) {
$this->log->error('[Hubspot] Failed to fetch associations', [
'from_object' => $fromObject,
'to_object' => $toObject,
'reason' => $e->getMessage(),
]);
}
}
return $associationData;
}
/**
* @throws \Exception
*/
private function getNoteAssociationType(NoteObject $noteObject): string
{
return match($noteObject) {
NoteObject::Opportunity => 'note_to_deal',
NoteObject::Lead, NoteObject::Contact => 'note_to_contact', // or 'note_to_lead' if your portal supports it
NoteObject::Account => 'note_to_company',
NoteObject::Call, NoteObject::Event => throw new \Exception('Not supported'),
};
}
/**
* @throws \Exception
*/
private function getNoteObject(NoteObject $noteObject): string
{
return match($noteObject) {
NoteObject::Opportunity => 'deal',
NoteObject::Lead, NoteObject::Contact => 'contact',
NoteObject::Account => 'company',
NoteObject::Call, NoteObject::Event => throw new \Exception('Not supported'),
};
}
public function addAssociations(string $objectType, string $associationType, array $payload): Response
{
$endpoint = "/crm/v4/associations/$objectType/$associationType/batch/create";
return $this->makeRequest($endpoint, 'POST', $payload);
}
public function removeAssociations(string $objectType, string $associationType, array $payload): Response
{
$endpoint = "/crm/v4/associations/$objectType/$associationType/batch/archive";
return $this->makeRequest($endpoint, 'POST', $payload);
}
}
Project
Project
New File or Directory…
Expand Selected
Collapse All
Options
Hide...
|
[{"role":"AXButton","text" [{"role":"AXButton","text":"Project: faVsco.js, menu","depth":5,"bounds":{"left":0.025930852,"top":0.019952115,"width":0.03856383,"height":0.025538707},"on_screen":true,"help_text":"~/jiminny/app","role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"master, menu","depth":5,"bounds":{"left":0.064494684,"top":0.019952115,"width":0.034242023,"height":0.025538707},"on_screen":true,"help_text":"Git Branch: master","role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Start Listening for PHP Debug Connections","depth":5,"bounds":{"left":0.8081782,"top":0.019952115,"width":0.011303191,"height":0.025538707},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"AskJiminnyReportActivityServiceTest","depth":6,"bounds":{"left":0.8234708,"top":0.019952115,"width":0.09208777,"height":0.025538707},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Run 'AskJiminnyReportActivityServiceTest'","depth":6,"bounds":{"left":0.9155585,"top":0.019952115,"width":0.011303191,"height":0.025538707},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Debug 'AskJiminnyReportActivityServiceTest'","depth":6,"bounds":{"left":0.9268617,"top":0.019952115,"width":0.011303191,"height":0.025538707},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"More Actions","depth":6,"bounds":{"left":0.9381649,"top":0.019952115,"width":0.011303191,"height":0.025538707},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"JetBrains AI","depth":5,"bounds":{"left":0.96609044,"top":0.019952115,"width":0.011303191,"height":0.025538707},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Search Everywhere","depth":5,"bounds":{"left":0.9773936,"top":0.019952115,"width":0.011303191,"height":0.025538707},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"IDE and Project Settings","depth":5,"bounds":{"left":0.9886968,"top":0.019952115,"width":0.011303186,"height":0.025538707},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Sync Changes","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.008643617,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Hide This Notification","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.008643617,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Code changed:","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.042220745,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Hide","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.008643617,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"15","depth":4,"bounds":{"left":0.96276593,"top":0.07581804,"width":0.009640957,"height":0.015163607},"on_screen":true,"role_description":"text"},{"role":"AXButton","text":"Previous Highlighted Error","depth":4,"bounds":{"left":0.9740692,"top":0.074221864,"width":0.00731383,"height":0.018355945},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Next Highlighted Error","depth":4,"bounds":{"left":0.98138297,"top":0.074221864,"width":0.006981383,"height":0.018355945},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXTextArea","text":"lukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/.screenpipe/data/data $ npx screenpipe@latest record --help\nStart recording screen, audio, and serve the API\n\nUsage: screenpipe record [OPTIONS]\n\nOptions:\n -d, --audio-chunk-duration <AUDIO_CHUNK_DURATION>\n Audio chunk duration in seconds\n\n [default: 30]\n\n -p, --port <PORT>\n Port to run the server on\n\n [default: 3030]\n\n --disable-audio\n Disable audio recording\n\n -i, --audio-device <AUDIO_DEVICE>\n Audio devices to use (can be specified multiple times)\n\n --use-system-default-audio\n Follow system default audio devices\n\n --experimental-coreaudio-system-audio\n [experimental, macOS 14.4+] Capture System Audio via CoreAudio Process Tap instead of ScreenCaptureKit. Off by default; ignored on older macOS and non-macOS\n\n --data-dir <DATA_DIR>\n Data directory. Default to $HOME/.screenpipe\n\n --debug\n Enable debug logging for screenpipe modules\n\n -a, --audio-transcription-engine <AUDIO_TRANSCRIPTION_ENGINE>\n Audio transcription engine to use\n\n Possible values:\n - deepgram\n - whisper-tiny\n - whisper-tiny-quantized\n - whisper-large\n - whisper-large-quantized\n - whisper-large-v3-turbo\n - whisper-large-v3-turbo-quantized\n - openai-compatible\n - qwen3-asr\n - parakeet\n - disabled: Disable transcription (audio capture only, no speech-to-text)\n\n [default: parakeet]\n\n -m, --monitor-id <MONITOR_ID>\n Monitor IDs to record. May be specified multiple times. When set, only the listed monitors are recorded (implies `--use-all-monitors=false`)\n\n --use-all-monitors\n Automatically record all monitors. Ignored when `--monitor-id` is passed\n\n -l, --language <LANGUAGE>\n Languages for OCR/transcription\n\n [possible values: english, chinese, german, spanish, russian, korean, french, japanese, portuguese, turkish, polish, catalan, dutch, arabic, swedish, italian, indonesian, hindi, vietnamese, finnish, hebrew, ukrainian, greek, malay, czech, romanian, danish, hungarian, norwegian, thai, urdu, croatian, bulgarian, lithuanian, latin, malayalam, welsh, slovak, persian, latvian, bengali, serbian, azerbaijani, slovenian, estonian, macedonian, nepali, mongolian, bosnian, kazakh, albanian, swahili, galician, marathi, punjabi, sinhala, khmer, afrikaans, belarusian, gujarati, amharic, yiddish, lao, uzbek, faroese, pashto, maltese, sanskrit, luxembourgish, myanmar, tibetan, tagalog, assamese, tatar, hausa, javanese]\n\n --use-pii-removal\n Enable PII removal\n\n --filter-music\n Filter music-dominant audio before transcription (reduces Spotify/YouTube music noise)\n\n --disable-vision\n Disable vision recording\n\n --ignored-windows <IGNORED_WINDOWS>\n Windows to ignore (by title, uses contains matching)\n\n --included-windows <INCLUDED_WINDOWS>\n Windows to include (by title, uses contains matching)\n\n --ignored-urls <IGNORED_URLS>\n URLs to ignore for browser privacy filtering\n\n --deepgram-api-key <DEEPGRAM_API_KEY>\n Deepgram API Key for audio transcription\n\n --transcription-mode <TRANSCRIPTION_MODE>\n Audio transcription scheduling mode: batch (default, longer chunks for quality) or realtime\n\n Possible values:\n - realtime: Transcribe immediately as audio is captured\n - batch: Accumulate longer audio batches for better transcription quality (default)\n\n [default: batch]\n\n --disable-telemetry\n Disable telemetry\n\n --video-quality <VIDEO_QUALITY>\n Video quality preset: low, balanced, high, max\n\n [default: balanced]\n\n --enable-sync\n Enable cloud sync\n\n --sync-token <SYNC_TOKEN>\n API token for cloud sync\n\n [env: SCREENPIPE_SYNC_TOKEN=]\n\n --sync-password <SYNC_PASSWORD>\n Password for encrypting synced data\n\n [env: SCREENPIPE_SYNC_PASSWORD=]\n\n --sync-interval-secs <SYNC_INTERVAL_SECS>\n Interval between sync cycles in seconds\n\n [default: 300]\n\n --sync-machine-id <SYNC_MACHINE_ID>\n Override the machine ID for this device\n\n --pause-on-drm-content\n Pause screen and audio capture when a DRM-protected streaming app (Netflix, Disney+, etc.) or a remote-desktop client (Omnissa/VMware Horizon) is focused — these blank their windows while any app is recording the screen\n\n --api-auth\n Require authentication for remote API access. When enabled, non-localhost requests must include Authorization: Bearer <SCREENPIPE_API_KEY>. Localhost requests are always allowed\n\n --listen-on-lan\n Bind the HTTP server to 0.0.0.0 so other devices on the LAN can reach it. Off by default — the server binds 127.0.0.1 only. `--api-auth` is forced on whenever this flag is used; you can't accidentally expose an unauthenticated API on your network\n\n --encrypt-secrets\n Encrypt secrets (API keys, OAuth tokens) at rest using the OS keychain. Creates a keychain key if one doesn't exist. Without this flag, the CLI will use an existing key (created by the desktop app) but won't create one\n\n --retention-days <RETENTION_DAYS>\n Local data retention in days. Old screen/audio data is auto-deleted after this period. Set to 0 to disable retention (keep data forever)\n\n [default: 14]\n\n -h, --help\n Print help (see a summary with '-h')","depth":4,"bounds":{"left":0.55019945,"top":0.0726257,"width":0.44980055,"height":0.9273743},"on_screen":true,"value":"lukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/.screenpipe/data/data $ npx screenpipe@latest record --help\nStart recording screen, audio, and serve the API\n\nUsage: screenpipe record [OPTIONS]\n\nOptions:\n -d, --audio-chunk-duration <AUDIO_CHUNK_DURATION>\n Audio chunk duration in seconds\n\n [default: 30]\n\n -p, --port <PORT>\n Port to run the server on\n\n [default: 3030]\n\n --disable-audio\n Disable audio recording\n\n -i, --audio-device <AUDIO_DEVICE>\n Audio devices to use (can be specified multiple times)\n\n --use-system-default-audio\n Follow system default audio devices\n\n --experimental-coreaudio-system-audio\n [experimental, macOS 14.4+] Capture System Audio via CoreAudio Process Tap instead of ScreenCaptureKit. Off by default; ignored on older macOS and non-macOS\n\n --data-dir <DATA_DIR>\n Data directory. Default to $HOME/.screenpipe\n\n --debug\n Enable debug logging for screenpipe modules\n\n -a, --audio-transcription-engine <AUDIO_TRANSCRIPTION_ENGINE>\n Audio transcription engine to use\n\n Possible values:\n - deepgram\n - whisper-tiny\n - whisper-tiny-quantized\n - whisper-large\n - whisper-large-quantized\n - whisper-large-v3-turbo\n - whisper-large-v3-turbo-quantized\n - openai-compatible\n - qwen3-asr\n - parakeet\n - disabled: Disable transcription (audio capture only, no speech-to-text)\n\n [default: parakeet]\n\n -m, --monitor-id <MONITOR_ID>\n Monitor IDs to record. May be specified multiple times. When set, only the listed monitors are recorded (implies `--use-all-monitors=false`)\n\n --use-all-monitors\n Automatically record all monitors. Ignored when `--monitor-id` is passed\n\n -l, --language <LANGUAGE>\n Languages for OCR/transcription\n\n [possible values: english, chinese, german, spanish, russian, korean, french, japanese, portuguese, turkish, polish, catalan, dutch, arabic, swedish, italian, indonesian, hindi, vietnamese, finnish, hebrew, ukrainian, greek, malay, czech, romanian, danish, hungarian, norwegian, thai, urdu, croatian, bulgarian, lithuanian, latin, malayalam, welsh, slovak, persian, latvian, bengali, serbian, azerbaijani, slovenian, estonian, macedonian, nepali, mongolian, bosnian, kazakh, albanian, swahili, galician, marathi, punjabi, sinhala, khmer, afrikaans, belarusian, gujarati, amharic, yiddish, lao, uzbek, faroese, pashto, maltese, sanskrit, luxembourgish, myanmar, tibetan, tagalog, assamese, tatar, hausa, javanese]\n\n --use-pii-removal\n Enable PII removal\n\n --filter-music\n Filter music-dominant audio before transcription (reduces Spotify/YouTube music noise)\n\n --disable-vision\n Disable vision recording\n\n --ignored-windows <IGNORED_WINDOWS>\n Windows to ignore (by title, uses contains matching)\n\n --included-windows <INCLUDED_WINDOWS>\n Windows to include (by title, uses contains matching)\n\n --ignored-urls <IGNORED_URLS>\n URLs to ignore for browser privacy filtering\n\n --deepgram-api-key <DEEPGRAM_API_KEY>\n Deepgram API Key for audio transcription\n\n --transcription-mode <TRANSCRIPTION_MODE>\n Audio transcription scheduling mode: batch (default, longer chunks for quality) or realtime\n\n Possible values:\n - realtime: Transcribe immediately as audio is captured\n - batch: Accumulate longer audio batches for better transcription quality (default)\n\n [default: batch]\n\n --disable-telemetry\n Disable telemetry\n\n --video-quality <VIDEO_QUALITY>\n Video quality preset: low, balanced, high, max\n\n [default: balanced]\n\n --enable-sync\n Enable cloud sync\n\n --sync-token <SYNC_TOKEN>\n API token for cloud sync\n\n [env: SCREENPIPE_SYNC_TOKEN=]\n\n --sync-password <SYNC_PASSWORD>\n Password for encrypting synced data\n\n [env: SCREENPIPE_SYNC_PASSWORD=]\n\n --sync-interval-secs <SYNC_INTERVAL_SECS>\n Interval between sync cycles in seconds\n\n [default: 300]\n\n --sync-machine-id <SYNC_MACHINE_ID>\n Override the machine ID for this device\n\n --pause-on-drm-content\n Pause screen and audio capture when a DRM-protected streaming app (Netflix, Disney+, etc.) or a remote-desktop client (Omnissa/VMware Horizon) is focused — these blank their windows while any app is recording the screen\n\n --api-auth\n Require authentication for remote API access. When enabled, non-localhost requests must include Authorization: Bearer <SCREENPIPE_API_KEY>. Localhost requests are always allowed\n\n --listen-on-lan\n Bind the HTTP server to 0.0.0.0 so other devices on the LAN can reach it. Off by default — the server binds 127.0.0.1 only. `--api-auth` is forced on whenever this flag is used; you can't accidentally expose an unauthenticated API on your network\n\n --encrypt-secrets\n Encrypt secrets (API keys, OAuth tokens) at rest using the OS keychain. Creates a keychain key if one doesn't exist. Without this flag, the CLI will use an existing key (created by the desktop app) but won't create one\n\n --retention-days <RETENTION_DAYS>\n Local data retention in days. Old screen/audio data is auto-deleted after this period. Set to 0 to disable retention (keep data forever)\n\n [default: 14]\n\n -h, --help\n Print help (see a summary with '-h')","role_description":"text entry area","is_enabled":true,"is_focused":true,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Sync Changes","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.008643617,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Hide This Notification","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.008643617,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Code changed:","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.042220745,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Hide","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.008643617,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"2","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.007978723,"height":0.0},"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"60","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.010305851,"height":0.0},"on_screen":false,"role_description":"text"},{"role":"AXButton","text":"Previous Highlighted Error","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.00731383,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Next Highlighted Error","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.006981383,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXTextArea","text":"<?php\n\ndeclare(strict_types=1);\n\nnamespace Jiminny\\Services\\Crm\\Hubspot;\n\nuse HubSpot\\Client\\Crm\\Deals\\ApiException as DealApiException;\nuse HubSpot\\Client\\Crm\\Contacts\\ApiException as ContactApiException;\nuse HubSpot\\Client\\Crm\\Companies\\ApiException as CompanyApiException;\nuse HubSpot\\Client\\Crm\\Contacts\\Model\\SimplePublicObjectWithAssociations as ContactsWithAssociations;\nuse HubSpot\\Client\\Crm\\Companies\\Model\\SimplePublicObjectWithAssociations as CompaniesWithAssociations;\nuse HubSpot\\Client\\Crm\\Deals\\Model\\SimplePublicObjectWithAssociations as DealWithAssociations;\nuse HubSpot\\Client\\Crm\\Objects\\Model\\SimplePublicObjectInput;\nuse HubSpot\\Client\\Crm\\Objects\\Model\\SimplePublicObjectWithAssociations as ObjectWithAssociations;\nuse HubSpot\\Client\\Crm\\Pipelines\\Model\\Error;\nuse HubSpot\\Client\\Crm\\Pipelines\\Model\\PipelineStage;\nuse HubSpot\\Client\\Crm\\Properties\\Model\\Property;\nuse HubSpot\\Discovery\\Discovery;\nuse Jiminny\\Component\\Utility\\Service\\ProviderRateLimiter;\nuse Jiminny\\Exceptions\\CrmException;\nuse Jiminny\\Exceptions\\RateLimitException;\nuse Jiminny\\Exceptions\\SocialAccountTokenInvalidException;\nuse Jiminny\\Jobs\\Crm\\NoteObject;\nuse Jiminny\\Models\\Crm\\Field;\nuse Jiminny\\Services\\Crm\\BaseClient;\nuse Jiminny\\Services\\Crm\\Hubspot\\DTO\\Response\\Owner;\nuse Jiminny\\Services\\SocialAccountService;\nuse SevenShores\\Hubspot\\Exceptions\\BadRequest;\nuse SevenShores\\Hubspot\\Exceptions\\HubspotException;\nuse SevenShores\\Hubspot\\Factory;\nuse SevenShores\\Hubspot\\Http\\Response;\nuse Jiminny\\Services\\Crm\\Hubspot\\Pagination\\HubspotPaginationService;\nuse Throwable;\n\n/**\n * @phpstan-type CrmFieldOption array{id:string, label:string, value?:string}\n */\nclass Client extends BaseClient implements HubspotClientInterface\n{\n public const string MIN_API_VERSION = '2';\n\n public const string BASE_URL = 'https://api.hubapi.com';\n\n public const int ASSOCIATIONS_BATCH_SIZE_LIMIT = 1000;\n\n private HubspotPaginationService $paginationService;\n private HubspotTokenManager $tokenManager;\n private ProviderRateLimiter $rateLimiter;\n\n public function __construct(\n SocialAccountService $socialAccountService,\n HubspotPaginationService $paginationService,\n HubspotTokenManager $tokenManager,\n ProviderRateLimiter $rateLimiter,\n ) {\n parent::__construct($socialAccountService);\n $this->paginationService = $paginationService;\n $this->tokenManager = $tokenManager;\n $this->rateLimiter = $rateLimiter;\n\n $this->setBaseUrl(self::BASE_URL);\n $this->setVersion(self::MIN_API_VERSION);\n }\n\n /**\n * Single entry point for every HubSpot API call. Enforces the per-portal\n * rate limit configured in the rate_limits table (morphed to the current\n * Configuration) and reacts to a real 429 from HubSpot by translating it\n * into a RateLimitException carrying Retry-After.\n *\n * Wrap any outbound HubSpot call (SDK or raw HTTP) like:\n *\n * $this->executeRequest(fn () => $this->getNewInstance()->crm()->...);\n *\n * @template T\n * @param callable(): T $apiCall\n * @return T\n *\n * @throws RateLimitException\n */\n private function executeRequest(callable $apiCall)\n {\n if (! $this->rateLimiter->canMakeRequest($this->config)) {\n $retryAfter = $this->rateLimiter->requestAvailableIn($this->config);\n\n $this->log->warning('[Hubspot] Rate limit exceeded, deferring request', [\n 'team_id' => $this->config->team_id,\n 'config_id' => $this->config->getId(),\n 'retry_after' => $retryAfter,\n ]);\n\n throw new RateLimitException(\n 'Hubspot rate limit reached for configuration ' . $this->config->getId(),\n $retryAfter,\n );\n }\n\n $this->rateLimiter->incrementRequestCount($this->config);\n\n try {\n return $apiCall();\n } catch (Throwable $e) {\n if ($this->isHubspotRateLimit($e)) {\n $retryAfter = $this->parseRetryAfter($e);\n\n $this->log->warning('[Hubspot] Received 429 from API', [\n 'team_id' => $this->config->team_id,\n 'config_id' => $this->config->getId(),\n 'retry_after' => $retryAfter,\n 'reason' => $e->getMessage(),\n ]);\n\n throw new RateLimitException('Hubspot returned 429', $retryAfter, $e);\n }\n\n throw $e;\n }\n }\n\n private function isHubspotRateLimit(Throwable $e): bool\n {\n return method_exists($e, 'getCode') && (int) $e->getCode() === 429;\n }\n\n private function parseRetryAfter(Throwable $e): int\n {\n if (method_exists($e, 'getResponseHeaders')) {\n $headers = $e->getResponseHeaders() ?: [];\n $value = $headers['Retry-After'] ?? $headers['retry-after'] ?? null;\n if (is_array($value)) {\n $value = $value[0] ?? null;\n }\n if (is_numeric($value)) {\n return (int) $value;\n }\n }\n\n return 10;\n }\n\n public function getMinimumApiVersion(): string\n {\n return self::MIN_API_VERSION;\n }\n\n public function getInstance(): Factory\n {\n return new Factory([\n 'key' => $this->accessToken,\n 'oauth2' => true,\n 'base_url' => $this->baseUrl,\n ]);\n }\n\n public function getNewInstance(): Discovery\n {\n return \\HubSpot\\Factory::createWithAccessToken($this->accessToken);\n }\n\n /**\n * Secondly and daily limits for Hubspot API\n *\n * Product Tier: Free & Starter | Professional & Enterprise | API add-on (any tier)\n * Burst: 100/10 seconds | 150/10 seconds | 200/10 seconds\n * Daily: 250,000 | 500,000 | 1,000,000\n *\n * Official documentation states: The search endpoints are rate limited to five requests per second.\n * Since with 5 RPS were still hitting secondly rate limits we lowered it to 4\n */\n public function getPaginatedData(array $payload, string $type, int $offset = 0): array\n {\n $total = 0;\n $lastId = null;\n $rows = [];\n foreach ($this->getPaginatedDataGenerator($payload, $type, $offset, $total, $lastId) as $row) {\n $rows[] = $row;\n }\n\n return ['results' => $rows, 'total' => $total, 'last_record' => $lastId];\n }\n\n /**\n * @throws HubspotException\n * @throws SocialAccountTokenInvalidException\n * @throws BadRequest\n */\n public function getPaginatedDataGenerator(\n array $payload,\n string $type,\n int $offset = 0,\n int &$total = 0,\n ?string &$lastRecordId = null\n ): \\Generator {\n return $this->paginationService->getPaginatedDataGenerator(\n $this,\n $payload,\n $type,\n $offset,\n $total,\n $lastRecordId\n );\n }\n\n /**\n * @throws DealApiException\n * @throws CrmException\n */\n public function getOpportunityById(string $crmId, array $fields): array\n {\n try {\n $deal = $this->executeRequest(fn () => $this->getNewInstance()->crm()->deals()->basicApi()->getById(\n $crmId,\n implode(',', $fields),\n 'companies,contacts'\n ));\n } catch (DealApiException $e) {\n $this->log->info('[Hubspot] Failed to fetch opportunity', [\n 'crm_id' => $crmId,\n 'reason' => $e->getMessage(),\n ]);\n\n throw $e;\n }\n\n if (! $deal instanceof DealWithAssociations) {\n throw new CrmException('Deal not found');\n }\n\n return [\n 'id' => $deal->getId(),\n 'properties' => $deal->getProperties(),\n 'associations' => $deal->getAssociations(),\n ];\n }\n\n /**\n * Generic batch read method for HubSpot objects\n *\n * @param string $objectType The object type ('deals', 'companies', 'contacts')\n * @param array<string> $crmIds Array of HubSpot object IDs (max 100)\n * @param array<string> $fields Array of property names to fetch\n *\n * @return array<string, array> Array keyed by CRM ID with object data\n */\n private function batchReadObjects(string $objectType, array $crmIds, array $fields): array\n {\n if (empty($crmIds)) {\n return [];\n }\n\n $this->validateBatchSize($objectType, $crmIds);\n $this->ensureValidToken();\n\n try {\n $batchConfig = $this->createBatchConfiguration($objectType);\n $batchReadRequest = $this->prepareBatchRequest($batchConfig, $crmIds, $fields);\n $response = $batchConfig['api']->read($batchReadRequest);\n\n $this->validateApiResponse($response, $objectType);\n\n $results = $this->processApiResults($response);\n $this->logBatchResults($objectType, $crmIds, $results);\n\n return $results;\n } catch (\\Throwable $e) {\n $this->handleBatchError($e, $objectType, $crmIds);\n }\n }\n\n private function validateBatchSize(string $objectType, array $crmIds): void\n {\n if (count($crmIds) > 100) {\n throw new \\InvalidArgumentException(\"Batch size cannot exceed 100 {$objectType}\");\n }\n }\n\n private function createBatchConfiguration(string $objectType): array\n {\n $configurations = [\n 'deals' => [\n 'batchReadRequest' => new \\HubSpot\\Client\\Crm\\Deals\\Model\\BatchReadInputSimplePublicObjectId(),\n 'inputClass' => \\HubSpot\\Client\\Crm\\Deals\\Model\\SimplePublicObjectId::class,\n 'api' => $this->getNewInstance()->crm()->deals()->batchApi(),\n ],\n 'companies' => [\n 'batchReadRequest' => new \\HubSpot\\Client\\Crm\\Companies\\Model\\BatchReadInputSimplePublicObjectId(),\n 'inputClass' => \\HubSpot\\Client\\Crm\\Companies\\Model\\SimplePublicObjectId::class,\n 'api' => $this->getNewInstance()->crm()->companies()->batchApi(),\n ],\n 'contacts' => [\n 'batchReadRequest' => new \\HubSpot\\Client\\Crm\\Contacts\\Model\\BatchReadInputSimplePublicObjectId(),\n 'inputClass' => \\HubSpot\\Client\\Crm\\Contacts\\Model\\SimplePublicObjectId::class,\n 'api' => $this->getNewInstance()->crm()->contacts()->batchApi(),\n ],\n ];\n\n if (! isset($configurations[$objectType])) {\n throw new \\InvalidArgumentException(\"Unsupported object type: {$objectType}\");\n }\n\n return $configurations[$objectType];\n }\n\n private function prepareBatchRequest(array $batchConfig, array $crmIds, array $fields): object\n {\n $batchReadRequest = $batchConfig['batchReadRequest'];\n $inputClass = $batchConfig['inputClass'];\n\n $inputs = array_map(function ($crmId) use ($inputClass) {\n $input = new $inputClass();\n $input->setId($crmId);\n\n return $input;\n }, $crmIds);\n\n $batchReadRequest->setInputs($inputs);\n $batchReadRequest->setProperties($fields);\n\n return $batchReadRequest;\n }\n\n private function validateApiResponse($response, string $objectType): void\n {\n if (! $response) {\n throw new CrmException(\"HubSpot API returned null response for {$objectType} batch read\");\n }\n }\n\n private function processApiResults($response): array\n {\n $results = [];\n $responseResults = $response->getResults();\n\n if ($responseResults) {\n foreach ($responseResults as $object) {\n if ($object && $object->getId()) {\n $results[$object->getId()] = [\n 'id' => $object->getId(),\n 'properties' => $object->getProperties() ?: [],\n ];\n }\n }\n }\n\n return $results;\n }\n\n private function logBatchResults(string $objectType, array $crmIds, array $results): void\n {\n $this->log->info(\"[HubSpot] Batch fetched {$objectType}\", [\n 'requested_count' => count($crmIds),\n 'returned_count' => count($results),\n 'crm_ids' => $crmIds,\n ]);\n }\n\n private function handleBatchError(\\Throwable $e, string $objectType, array $crmIds): void\n {\n $errorMessage = $e->getMessage() ?: 'Unknown error';\n $errorTrace = $e->getTraceAsString() ?: 'No trace available';\n\n $this->log->error(\"[HubSpot] Failed to batch fetch {$objectType}\", [\n 'crm_ids' => $crmIds,\n 'error' => $errorMessage,\n 'trace' => $errorTrace,\n ]);\n\n throw new CrmException(\"Failed to batch fetch {$objectType}: \" . $errorMessage);\n }\n\n /**\n * Batch read multiple opportunities by their CRM IDs\n *\n * @param array<string> $crmIds Array of HubSpot deal IDs (max 100)\n * @param array<string> $fields Array of property names to fetch\n *\n * @return array<string, array> Array keyed by CRM ID with opportunity data\n */\n public function getOpportunitiesByIds(array $crmIds, array $fields): array\n {\n return $this->batchReadObjects('deals', $crmIds, $fields);\n }\n\n /**\n * Batch read multiple companies by their CRM IDs\n *\n * @param array<string> $crmIds Array of HubSpot company IDs (max 100)\n * @param array<string> $fields Array of property names to fetch\n *\n * @return array<string, array> Array keyed by CRM ID with company data\n */\n public function getCompaniesByIds(array $crmIds, array $fields): array\n {\n return $this->batchReadObjects('companies', $crmIds, $fields);\n }\n\n /**\n * Batch read multiple contacts by their CRM IDs\n *\n * @param array<string> $crmIds Array of HubSpot contact IDs (max 100)\n * @param array<string> $fields Array of property names to fetch\n *\n * @return array<string, array> Array keyed by CRM ID with contact data\n */\n public function getContactsByIds(array $crmIds, array $fields): array\n {\n return $this->batchReadObjects('contacts', $crmIds, $fields);\n }\n\n /**\n * @throws CompanyApiException\n * @throws CrmException\n */\n public function getAccountById(string $crmId, array $fields): array\n {\n try {\n $company = $this->getNewInstance()->crm()->companies()->basicApi()->getById(\n $crmId,\n implode(',', $fields),\n );\n } catch (CompanyApiException $e) {\n $this->log->info('[Hubspot] Failed to fetch account', [\n 'crm_id' => $crmId,\n 'reason' => $e->getMessage(),\n ]);\n\n throw $e;\n }\n\n if (! $company instanceof CompaniesWithAssociations) {\n throw new CrmException('Account not found');\n }\n\n return [\n 'id' => $company->getId(),\n 'properties' => $company->getProperties(),\n ];\n }\n\n /**\n * @throws ContactApiException\n * @throws CrmException\n */\n public function getContactById(string $crmId, array $fields): array\n {\n try {\n $contact = $this->getNewInstance()->crm()->contacts()->basicApi()->getById(\n $crmId,\n implode(',', $fields)\n );\n } catch (ContactApiException $e) {\n $this->log->info('[Hubspot] Failed to fetch contact', [\n 'crm_id' => $crmId,\n 'reason' => $e->getMessage(),\n ]);\n\n throw $e;\n }\n\n if (! $contact instanceof ContactsWithAssociations) {\n throw new CrmException('Contact not found');\n }\n\n return [\n 'id' => $contact->getId(),\n 'properties' => $contact->getProperties(),\n ];\n }\n\n /**\n * This is email search request that Hubspot offers as GET (more generous quota)\n */\n public function getContactByEmail(string $email, array $fields = []): array\n {\n try {\n $contact = $this->getNewInstance()->crm()->contacts()->basicApi()->getById(\n $email,\n implode(',', $fields),\n null,\n false,\n 'email'\n );\n\n return [\n 'id' => $contact->getId(),\n 'properties' => $contact->getProperties(),\n ];\n } catch (ContactApiException $e) {\n $this->log->info('[Hubspot] Failed to fetch contact', [\n 'email' => $email,\n 'reason' => $e->getMessage(),\n ]);\n\n return [];\n }\n }\n\n /**\n * @throws CrmException\n */\n public function fetchProperty(string $objectType, string $propertyId): Property\n {\n $result = $this->getNewInstance()->crm()->properties()->coreApi()->getByName($objectType, $propertyId);\n\n if (! $result instanceof Property) {\n $this->log->error('[Hubspot] Failed to fetch property', [\n 'object_type' => $objectType,\n 'property_id' => $propertyId,\n 'reason' => $result->getMessage(),\n ]);\n\n throw new CrmException('Failed to fetch property');\n }\n\n return $result;\n }\n\n /**\n * @return array<CrmFieldOption>\n */\n public function fetchPropertyOptions(string $objectType, string $propertyId): array\n {\n /** @var array<CrmFieldOption> */\n return $this->fetchProperty($objectType, $propertyId)->getOptions();\n }\n\n /**\n * @return array<array{id:string, label:string, deleted:bool}>\n */\n public function fetchCallDispositions(): array\n {\n /** @var Response $response */\n $response = $this->getInstance()->engagements()->getCallDispositions();\n\n /**\n * @var array<array{\n * id:string,\n * label:string,\n * deleted: bool\n * }>\n */\n return $response->toArray();\n }\n\n /**\n * @return array<CrmFieldOption>\n */\n public function fetchOpportunityPipelineStages(): array\n {\n $stages = [];\n $apiResponse = $this->getNewInstance()->crm()->pipelines()->pipelinesApi()->getAll('deals');\n\n if ($apiResponse instanceof Error) {\n $this->log->error('[Hubspot] Failed to fetch opportunity pipelines', [\n 'reason' => $apiResponse->getMessage(),\n ]);\n\n return [];\n }\n\n foreach ($apiResponse->getResults() as $pipeline) {\n $pipelineStages = array_map(\n static function (PipelineStage $stage) {\n return [\n 'id' => $stage->getId(),\n 'label' => $stage->getLabel(),\n ];\n },\n $pipeline->getStages()\n );\n\n $stages = array_merge($stages, $pipelineStages);\n }\n\n return $stages;\n }\n\n public function fetchOpportunityPipelines(): array\n {\n $pipelines = [];\n\n try {\n $apiResponse = $this->makeRequest('/crm/v3/pipelines/deals');\n } catch (\\Exception $e) {\n $this->log->info('[Hubspot] Failed to fetch opportunity pipelines', [\n 'reason' => $e->getMessage(),\n ]);\n\n return [];\n }\n\n $response = $apiResponse->toArray();\n\n foreach ($response['results'] as $pipeline) {\n $pipelines[] = [\n 'id' => $pipeline['id'],\n 'label' => $pipeline['label'],\n ];\n }\n\n return $pipelines;\n }\n\n /**\n * @return array<CrmFieldOption>\n */\n public function fetchMeetingOutcomeFieldOptions(Field $field): array\n {\n return $field->getCrmProviderId() === 'meetingOutcome'\n ? $this->fetchMeetingOutcomeTypes()\n : $this->fetchCallActivityTypes();\n }\n\n public function fetchMeetingOutcomeTypes(): array\n {\n return $this->extractMeetingTypeOptions(\n 'https://api.hubapi.com/crm/v3/properties/meeting/hs_meeting_outcome'\n );\n }\n\n public function fetchCallActivityTypes(): array\n {\n return $this->extractMeetingTypeOptions(\n 'https://api.hubapi.com/crm/v3/properties/call/hs_activity_type'\n );\n }\n\n private function extractMeetingTypeOptions(string $endpoint): array\n {\n /** @var Response $response */\n $response = $this->getInstance()\n ->getClient()\n ->request('GET', $endpoint);\n\n /**\n * @var array<array{\n * value: string,\n * label: string,\n * displayOrder: int\n * }> $optionData\n */\n $optionData = $response->toArray()['options'] ?? [];\n\n $options = [];\n foreach ($optionData as $item) {\n $options[] = [\n 'id' => $item['value'],\n 'value' => $item['value'],\n 'label' => $item['label'],\n 'display_order' => $item['displayOrder'],\n ];\n }\n\n return $options;\n }\n\n /**\n * @return array<CrmFieldOption>\n */\n public function fetchDispositionFieldOptions(): array\n {\n $options = [];\n\n $dispositions = $this->fetchCallDispositions();\n\n foreach ($dispositions as $disposition) {\n if ($disposition['deleted'] !== false) {\n continue;\n }\n\n $option['value'] = $disposition['id'];\n $option['id'] = $disposition['id'];\n $option['label'] = $disposition['label'];\n\n $options[] = $option;\n }\n\n return $options;\n }\n\n /**\n * @return array<CrmFieldOption>\n */\n public function fetchOpportunityFieldOptions(Field $field): array\n {\n if ($field->isStageField()) {\n return $this->fetchOpportunityPipelineStages();\n }\n\n if ($field->isPipelineField()) {\n return $this->fetchOpportunityPipelines();\n }\n\n return $this->fetchPropertyOptions('deals', $field->getCrmProviderId());\n }\n\n /**\n * @throws BadRequest\n * @throws HubspotException\n */\n public function makeRequest(string $endpoint, $method = 'GET', $payload = [], ?string $queryString = null)\n {\n $endpoint = self::BASE_URL . $endpoint;\n\n if ($method === 'GET') {\n $response = $this->getInstance()->getClient()?->request(\n method: $method,\n endpoint: $endpoint,\n query_string: $queryString\n );\n } else {\n $response = $this->getInstance()->getClient()->request($method, $endpoint, [\n 'json' => ($payload),\n ]);\n }\n\n $max = $response->getHeaderLine('X-HubSpot-RateLimit-Max'); // \"110\"\n $remaining = $response->getHeaderLine('X-HubSpot-RateLimit-Remaining'); // \"109\"\n $interval = $response->getHeaderLine('X-HubSpot-RateLimit-Interval-Milliseconds'); // \"10000\"\n $body = json_decode((string) $response->getBody(), true);\n\n \\Illuminate\\Support\\Facades\\Log::channel('custom_channel')->info('$max ' . PHP_EOL . print_r($max, true));\n \\Illuminate\\Support\\Facades\\Log::channel('custom_channel')->info('$remaining ' . PHP_EOL . print_r($remaining, true));\n \\Illuminate\\Support\\Facades\\Log::channel('custom_channel')->info('$interval ' . PHP_EOL . print_r($interval, true));\n \\Illuminate\\Support\\Facades\\Log::channel('custom_channel')->info('$body ' . PHP_EOL . print_r($body, true));\n\n return $response;\n }\n\n /**\n * @throws BadRequest\n * @throws HubspotException\n */\n public function createMeeting(array $payload): Response\n {\n $endpoint = '/crm/v3/objects/meetings';\n\n return $this->makeRequest($endpoint, 'POST', $payload);\n }\n\n /**\n * @throws BadRequest\n * @throws HubspotException\n */\n public function updateMeeting(string $meetingId, array $payload): Response\n {\n $endpoint = '/crm/v3/objects/meetings/' . $meetingId;\n\n return $this->makeRequest($endpoint, 'PATCH', $payload);\n }\n\n /**\n * @throws \\Exception\n */\n public function createNote(\n string $body,\n string $ownerId,\n int $timestamp,\n string $objectId,\n NoteObject $noteObject\n ): ?string {\n try {\n $noteInput = new SimplePublicObjectInput([\n 'properties' => [\n 'hs_note_body' => $body,\n 'hubspot_owner_id' => $ownerId,\n 'hs_timestamp' => $timestamp,\n ],\n ]);\n\n // Create note\n $note = $this->getNewInstance()->crm()->objects()->basicApi()->create('note', $noteInput);\n\n $this->getNewInstance()->crm()->objects()->associationsApi()->create(\n 'note',\n $note->getId(),\n $this->getNoteObject($noteObject),\n $objectId,\n $this->getNoteAssociationType($noteObject),\n );\n\n return $note->getId();\n } catch (\\Exception $e) {\n $this->log->error('[Hubspot] Failed to create note', [\n 'objectId' => $objectId,\n 'noteObject' => $noteObject->getObjectType(),\n 'reason' => $e->getMessage(),\n ]);\n\n \\Sentry::captureException($e);\n }\n\n return null;\n }\n\n public function updateEngagement(string $objectId, array $engagement, array $metadata): void\n {\n $this->getInstance()->engagements()->update($objectId, $engagement, $metadata);\n }\n\n public function getEngagementData(string $engagementId): array\n {\n $engagement = $this->getInstance()->engagements()->get($engagementId);\n\n return $engagement->toArray();\n }\n\n public function createEngagement(array $engagement, array $associations, array $metadata): Response\n {\n return $this->getInstance()\n ->engagements()\n ->create($engagement, $associations, $metadata);\n }\n\n public function isUnauthorizedException(\\Exception $e): bool\n {\n // Check for specific HubSpot API exception types first\n if ($e instanceof BadRequest) {\n // BadRequest can contain 401 status codes\n return $e->getCode() === 401;\n }\n\n // Check for HTTP client exceptions with status codes\n if ($e instanceof \\GuzzleHttp\\Exception\\RequestException && $e->hasResponse()) {\n $response = $e->getResponse();\n if ($response !== null) {\n return $response->getStatusCode() === 401;\n }\n }\n\n // Check for Guzzle HTTP exceptions\n if ($e instanceof \\GuzzleHttp\\Exception\\ClientException) {\n return $e->getCode() === 401;\n }\n\n // Fallback to string matching as last resort, but be more specific\n $message = strtolower($e->getMessage());\n\n return str_contains($message, '401 unauthorized') ||\n str_contains($message, 'http 401') ||\n str_contains($message, 'status code 401') ||\n (preg_match('/\\b401\\b/', $message) && str_contains($message, 'unauthorized'));\n }\n\n /**\n * Validates and refreshes the access token if needed before API requests.\n * This ensures long-running processes don't fail due to token expiration.\n *\n * @throws SocialAccountTokenInvalidException\n */\n public function ensureValidToken(): void\n {\n if ($this->oauthAccount === null) {\n return;\n }\n\n $newToken = $this->tokenManager->ensureValidToken($this->oauthAccount);\n if ($newToken !== null) {\n $this->accessToken = $newToken;\n }\n }\n\n public function getConfig()\n {\n return $this->config;\n }\n\n // returns only active (archived=false)\n public function getOwners(): array\n {\n return $this->getNewInstance()->crm()->owners()->getAll();\n }\n\n /**\n * @param bool $archived\n *\n * @return array<Owner>|[]\n */\n public function getOwnersArchived(bool $archived = true): array\n {\n $endpoint = '/crm/v3/owners';\n $queryParams = [\n 'archived' => $archived ? 'true' : 'false',\n ];\n $queryString = http_build_query($queryParams);\n\n $owners = [];\n\n try {\n $response = $this->makeRequest(endpoint: $endpoint, queryString: $queryString);\n $responseData = $response?->toArray();\n\n foreach ($responseData['results'] as $result) {\n try {\n $owners[] = Owner::create($result);\n } catch (Throwable $e) {\n $this->log->error('[HubSpot] Failed to process owner data', [\n 'result' => $result,\n 'error' => $e->getMessage(),\n ]);\n\n continue;\n }\n }\n } catch (Throwable $e) {\n $this->log->error('HubSpot] Failed to fetch owners', [\n 'archived' => $archived,\n 'error' => $e->getMessage(),\n ]);\n\n return [];\n }\n\n return $owners;\n }\n\n public function getMeeting(string $engagementId): ObjectWithAssociations\n {\n return $this->getNewInstance()->crm()->objects()->basicApi()\n ->getById('meeting', $engagementId, null, 'contact,company,deal');\n }\n\n public function deleteEngagement(string $engagementId): void\n {\n $this->getInstance()->engagements()->delete((int) $engagementId);\n }\n\n public function getAssociationsData(array $ids, string $fromObject, string $toObject): array\n {\n $associationData = [];\n $idChunks = array_chunk($ids, self::ASSOCIATIONS_BATCH_SIZE_LIMIT);\n\n foreach ($idChunks as $idChunk) {\n try {\n $batchInput = new \\HubSpot\\Client\\Crm\\Associations\\Model\\BatchInputPublicObjectId();\n $batchInput->setInputs(array_map(function ($id) {\n $publicObjectId = new \\HubSpot\\Client\\Crm\\Associations\\Model\\PublicObjectId();\n $publicObjectId->setId($id);\n\n return $publicObjectId;\n }, $idChunk));\n\n $associatedObjectsData = $this\n ->getNewInstance()\n ->crm()\n ->associations()\n ->batchApi()\n ->read($fromObject, $toObject, $batchInput);\n\n if ($associatedObjectsData instanceof \\HubSpot\\Client\\Crm\\Associations\\Model\\BatchResponsePublicAssociationMulti) {\n foreach ($associatedObjectsData->getResults() as $association) {\n $from = $association->getFrom()->getId();\n $toAssociations = $association->getTo();\n\n if (! empty($toAssociations)) {\n $associationData[$from] = array_map(function ($item) {\n return $item->getId();\n }, $toAssociations);\n }\n }\n }\n } catch (\\Exception $e) {\n $this->log->error('[Hubspot] Failed to fetch associations', [\n 'from_object' => $fromObject,\n 'to_object' => $toObject,\n 'reason' => $e->getMessage(),\n ]);\n }\n }\n\n return $associationData;\n }\n\n /**\n * @throws \\Exception\n */\n private function getNoteAssociationType(NoteObject $noteObject): string\n {\n return match($noteObject) {\n NoteObject::Opportunity => 'note_to_deal',\n NoteObject::Lead, NoteObject::Contact => 'note_to_contact', // or 'note_to_lead' if your portal supports it\n NoteObject::Account => 'note_to_company',\n NoteObject::Call, NoteObject::Event => throw new \\Exception('Not supported'),\n };\n }\n\n /**\n * @throws \\Exception\n */\n private function getNoteObject(NoteObject $noteObject): string\n {\n return match($noteObject) {\n NoteObject::Opportunity => 'deal',\n NoteObject::Lead, NoteObject::Contact => 'contact',\n NoteObject::Account => 'company',\n NoteObject::Call, NoteObject::Event => throw new \\Exception('Not supported'),\n };\n }\n\n public function addAssociations(string $objectType, string $associationType, array $payload): Response\n {\n $endpoint = \"/crm/v4/associations/$objectType/$associationType/batch/create\";\n\n return $this->makeRequest($endpoint, 'POST', $payload);\n }\n\n public function removeAssociations(string $objectType, string $associationType, array $payload): Response\n {\n $endpoint = \"/crm/v4/associations/$objectType/$associationType/batch/archive\";\n\n return $this->makeRequest($endpoint, 'POST', $payload);\n }\n}","depth":4,"on_screen":true,"value":"<?php\n\ndeclare(strict_types=1);\n\nnamespace Jiminny\\Services\\Crm\\Hubspot;\n\nuse HubSpot\\Client\\Crm\\Deals\\ApiException as DealApiException;\nuse HubSpot\\Client\\Crm\\Contacts\\ApiException as ContactApiException;\nuse HubSpot\\Client\\Crm\\Companies\\ApiException as CompanyApiException;\nuse HubSpot\\Client\\Crm\\Contacts\\Model\\SimplePublicObjectWithAssociations as ContactsWithAssociations;\nuse HubSpot\\Client\\Crm\\Companies\\Model\\SimplePublicObjectWithAssociations as CompaniesWithAssociations;\nuse HubSpot\\Client\\Crm\\Deals\\Model\\SimplePublicObjectWithAssociations as DealWithAssociations;\nuse HubSpot\\Client\\Crm\\Objects\\Model\\SimplePublicObjectInput;\nuse HubSpot\\Client\\Crm\\Objects\\Model\\SimplePublicObjectWithAssociations as ObjectWithAssociations;\nuse HubSpot\\Client\\Crm\\Pipelines\\Model\\Error;\nuse HubSpot\\Client\\Crm\\Pipelines\\Model\\PipelineStage;\nuse HubSpot\\Client\\Crm\\Properties\\Model\\Property;\nuse HubSpot\\Discovery\\Discovery;\nuse Jiminny\\Component\\Utility\\Service\\ProviderRateLimiter;\nuse Jiminny\\Exceptions\\CrmException;\nuse Jiminny\\Exceptions\\RateLimitException;\nuse Jiminny\\Exceptions\\SocialAccountTokenInvalidException;\nuse Jiminny\\Jobs\\Crm\\NoteObject;\nuse Jiminny\\Models\\Crm\\Field;\nuse Jiminny\\Services\\Crm\\BaseClient;\nuse Jiminny\\Services\\Crm\\Hubspot\\DTO\\Response\\Owner;\nuse Jiminny\\Services\\SocialAccountService;\nuse SevenShores\\Hubspot\\Exceptions\\BadRequest;\nuse SevenShores\\Hubspot\\Exceptions\\HubspotException;\nuse SevenShores\\Hubspot\\Factory;\nuse SevenShores\\Hubspot\\Http\\Response;\nuse Jiminny\\Services\\Crm\\Hubspot\\Pagination\\HubspotPaginationService;\nuse Throwable;\n\n/**\n * @phpstan-type CrmFieldOption array{id:string, label:string, value?:string}\n */\nclass Client extends BaseClient implements HubspotClientInterface\n{\n public const string MIN_API_VERSION = '2';\n\n public const string BASE_URL = 'https://api.hubapi.com';\n\n public const int ASSOCIATIONS_BATCH_SIZE_LIMIT = 1000;\n\n private HubspotPaginationService $paginationService;\n private HubspotTokenManager $tokenManager;\n private ProviderRateLimiter $rateLimiter;\n\n public function __construct(\n SocialAccountService $socialAccountService,\n HubspotPaginationService $paginationService,\n HubspotTokenManager $tokenManager,\n ProviderRateLimiter $rateLimiter,\n ) {\n parent::__construct($socialAccountService);\n $this->paginationService = $paginationService;\n $this->tokenManager = $tokenManager;\n $this->rateLimiter = $rateLimiter;\n\n $this->setBaseUrl(self::BASE_URL);\n $this->setVersion(self::MIN_API_VERSION);\n }\n\n /**\n * Single entry point for every HubSpot API call. Enforces the per-portal\n * rate limit configured in the rate_limits table (morphed to the current\n * Configuration) and reacts to a real 429 from HubSpot by translating it\n * into a RateLimitException carrying Retry-After.\n *\n * Wrap any outbound HubSpot call (SDK or raw HTTP) like:\n *\n * $this->executeRequest(fn () => $this->getNewInstance()->crm()->...);\n *\n * @template T\n * @param callable(): T $apiCall\n * @return T\n *\n * @throws RateLimitException\n */\n private function executeRequest(callable $apiCall)\n {\n if (! $this->rateLimiter->canMakeRequest($this->config)) {\n $retryAfter = $this->rateLimiter->requestAvailableIn($this->config);\n\n $this->log->warning('[Hubspot] Rate limit exceeded, deferring request', [\n 'team_id' => $this->config->team_id,\n 'config_id' => $this->config->getId(),\n 'retry_after' => $retryAfter,\n ]);\n\n throw new RateLimitException(\n 'Hubspot rate limit reached for configuration ' . $this->config->getId(),\n $retryAfter,\n );\n }\n\n $this->rateLimiter->incrementRequestCount($this->config);\n\n try {\n return $apiCall();\n } catch (Throwable $e) {\n if ($this->isHubspotRateLimit($e)) {\n $retryAfter = $this->parseRetryAfter($e);\n\n $this->log->warning('[Hubspot] Received 429 from API', [\n 'team_id' => $this->config->team_id,\n 'config_id' => $this->config->getId(),\n 'retry_after' => $retryAfter,\n 'reason' => $e->getMessage(),\n ]);\n\n throw new RateLimitException('Hubspot returned 429', $retryAfter, $e);\n }\n\n throw $e;\n }\n }\n\n private function isHubspotRateLimit(Throwable $e): bool\n {\n return method_exists($e, 'getCode') && (int) $e->getCode() === 429;\n }\n\n private function parseRetryAfter(Throwable $e): int\n {\n if (method_exists($e, 'getResponseHeaders')) {\n $headers = $e->getResponseHeaders() ?: [];\n $value = $headers['Retry-After'] ?? $headers['retry-after'] ?? null;\n if (is_array($value)) {\n $value = $value[0] ?? null;\n }\n if (is_numeric($value)) {\n return (int) $value;\n }\n }\n\n return 10;\n }\n\n public function getMinimumApiVersion(): string\n {\n return self::MIN_API_VERSION;\n }\n\n public function getInstance(): Factory\n {\n return new Factory([\n 'key' => $this->accessToken,\n 'oauth2' => true,\n 'base_url' => $this->baseUrl,\n ]);\n }\n\n public function getNewInstance(): Discovery\n {\n return \\HubSpot\\Factory::createWithAccessToken($this->accessToken);\n }\n\n /**\n * Secondly and daily limits for Hubspot API\n *\n * Product Tier: Free & Starter | Professional & Enterprise | API add-on (any tier)\n * Burst: 100/10 seconds | 150/10 seconds | 200/10 seconds\n * Daily: 250,000 | 500,000 | 1,000,000\n *\n * Official documentation states: The search endpoints are rate limited to five requests per second.\n * Since with 5 RPS were still hitting secondly rate limits we lowered it to 4\n */\n public function getPaginatedData(array $payload, string $type, int $offset = 0): array\n {\n $total = 0;\n $lastId = null;\n $rows = [];\n foreach ($this->getPaginatedDataGenerator($payload, $type, $offset, $total, $lastId) as $row) {\n $rows[] = $row;\n }\n\n return ['results' => $rows, 'total' => $total, 'last_record' => $lastId];\n }\n\n /**\n * @throws HubspotException\n * @throws SocialAccountTokenInvalidException\n * @throws BadRequest\n */\n public function getPaginatedDataGenerator(\n array $payload,\n string $type,\n int $offset = 0,\n int &$total = 0,\n ?string &$lastRecordId = null\n ): \\Generator {\n return $this->paginationService->getPaginatedDataGenerator(\n $this,\n $payload,\n $type,\n $offset,\n $total,\n $lastRecordId\n );\n }\n\n /**\n * @throws DealApiException\n * @throws CrmException\n */\n public function getOpportunityById(string $crmId, array $fields): array\n {\n try {\n $deal = $this->executeRequest(fn () => $this->getNewInstance()->crm()->deals()->basicApi()->getById(\n $crmId,\n implode(',', $fields),\n 'companies,contacts'\n ));\n } catch (DealApiException $e) {\n $this->log->info('[Hubspot] Failed to fetch opportunity', [\n 'crm_id' => $crmId,\n 'reason' => $e->getMessage(),\n ]);\n\n throw $e;\n }\n\n if (! $deal instanceof DealWithAssociations) {\n throw new CrmException('Deal not found');\n }\n\n return [\n 'id' => $deal->getId(),\n 'properties' => $deal->getProperties(),\n 'associations' => $deal->getAssociations(),\n ];\n }\n\n /**\n * Generic batch read method for HubSpot objects\n *\n * @param string $objectType The object type ('deals', 'companies', 'contacts')\n * @param array<string> $crmIds Array of HubSpot object IDs (max 100)\n * @param array<string> $fields Array of property names to fetch\n *\n * @return array<string, array> Array keyed by CRM ID with object data\n */\n private function batchReadObjects(string $objectType, array $crmIds, array $fields): array\n {\n if (empty($crmIds)) {\n return [];\n }\n\n $this->validateBatchSize($objectType, $crmIds);\n $this->ensureValidToken();\n\n try {\n $batchConfig = $this->createBatchConfiguration($objectType);\n $batchReadRequest = $this->prepareBatchRequest($batchConfig, $crmIds, $fields);\n $response = $batchConfig['api']->read($batchReadRequest);\n\n $this->validateApiResponse($response, $objectType);\n\n $results = $this->processApiResults($response);\n $this->logBatchResults($objectType, $crmIds, $results);\n\n return $results;\n } catch (\\Throwable $e) {\n $this->handleBatchError($e, $objectType, $crmIds);\n }\n }\n\n private function validateBatchSize(string $objectType, array $crmIds): void\n {\n if (count($crmIds) > 100) {\n throw new \\InvalidArgumentException(\"Batch size cannot exceed 100 {$objectType}\");\n }\n }\n\n private function createBatchConfiguration(string $objectType): array\n {\n $configurations = [\n 'deals' => [\n 'batchReadRequest' => new \\HubSpot\\Client\\Crm\\Deals\\Model\\BatchReadInputSimplePublicObjectId(),\n 'inputClass' => \\HubSpot\\Client\\Crm\\Deals\\Model\\SimplePublicObjectId::class,\n 'api' => $this->getNewInstance()->crm()->deals()->batchApi(),\n ],\n 'companies' => [\n 'batchReadRequest' => new \\HubSpot\\Client\\Crm\\Companies\\Model\\BatchReadInputSimplePublicObjectId(),\n 'inputClass' => \\HubSpot\\Client\\Crm\\Companies\\Model\\SimplePublicObjectId::class,\n 'api' => $this->getNewInstance()->crm()->companies()->batchApi(),\n ],\n 'contacts' => [\n 'batchReadRequest' => new \\HubSpot\\Client\\Crm\\Contacts\\Model\\BatchReadInputSimplePublicObjectId(),\n 'inputClass' => \\HubSpot\\Client\\Crm\\Contacts\\Model\\SimplePublicObjectId::class,\n 'api' => $this->getNewInstance()->crm()->contacts()->batchApi(),\n ],\n ];\n\n if (! isset($configurations[$objectType])) {\n throw new \\InvalidArgumentException(\"Unsupported object type: {$objectType}\");\n }\n\n return $configurations[$objectType];\n }\n\n private function prepareBatchRequest(array $batchConfig, array $crmIds, array $fields): object\n {\n $batchReadRequest = $batchConfig['batchReadRequest'];\n $inputClass = $batchConfig['inputClass'];\n\n $inputs = array_map(function ($crmId) use ($inputClass) {\n $input = new $inputClass();\n $input->setId($crmId);\n\n return $input;\n }, $crmIds);\n\n $batchReadRequest->setInputs($inputs);\n $batchReadRequest->setProperties($fields);\n\n return $batchReadRequest;\n }\n\n private function validateApiResponse($response, string $objectType): void\n {\n if (! $response) {\n throw new CrmException(\"HubSpot API returned null response for {$objectType} batch read\");\n }\n }\n\n private function processApiResults($response): array\n {\n $results = [];\n $responseResults = $response->getResults();\n\n if ($responseResults) {\n foreach ($responseResults as $object) {\n if ($object && $object->getId()) {\n $results[$object->getId()] = [\n 'id' => $object->getId(),\n 'properties' => $object->getProperties() ?: [],\n ];\n }\n }\n }\n\n return $results;\n }\n\n private function logBatchResults(string $objectType, array $crmIds, array $results): void\n {\n $this->log->info(\"[HubSpot] Batch fetched {$objectType}\", [\n 'requested_count' => count($crmIds),\n 'returned_count' => count($results),\n 'crm_ids' => $crmIds,\n ]);\n }\n\n private function handleBatchError(\\Throwable $e, string $objectType, array $crmIds): void\n {\n $errorMessage = $e->getMessage() ?: 'Unknown error';\n $errorTrace = $e->getTraceAsString() ?: 'No trace available';\n\n $this->log->error(\"[HubSpot] Failed to batch fetch {$objectType}\", [\n 'crm_ids' => $crmIds,\n 'error' => $errorMessage,\n 'trace' => $errorTrace,\n ]);\n\n throw new CrmException(\"Failed to batch fetch {$objectType}: \" . $errorMessage);\n }\n\n /**\n * Batch read multiple opportunities by their CRM IDs\n *\n * @param array<string> $crmIds Array of HubSpot deal IDs (max 100)\n * @param array<string> $fields Array of property names to fetch\n *\n * @return array<string, array> Array keyed by CRM ID with opportunity data\n */\n public function getOpportunitiesByIds(array $crmIds, array $fields): array\n {\n return $this->batchReadObjects('deals', $crmIds, $fields);\n }\n\n /**\n * Batch read multiple companies by their CRM IDs\n *\n * @param array<string> $crmIds Array of HubSpot company IDs (max 100)\n * @param array<string> $fields Array of property names to fetch\n *\n * @return array<string, array> Array keyed by CRM ID with company data\n */\n public function getCompaniesByIds(array $crmIds, array $fields): array\n {\n return $this->batchReadObjects('companies', $crmIds, $fields);\n }\n\n /**\n * Batch read multiple contacts by their CRM IDs\n *\n * @param array<string> $crmIds Array of HubSpot contact IDs (max 100)\n * @param array<string> $fields Array of property names to fetch\n *\n * @return array<string, array> Array keyed by CRM ID with contact data\n */\n public function getContactsByIds(array $crmIds, array $fields): array\n {\n return $this->batchReadObjects('contacts', $crmIds, $fields);\n }\n\n /**\n * @throws CompanyApiException\n * @throws CrmException\n */\n public function getAccountById(string $crmId, array $fields): array\n {\n try {\n $company = $this->getNewInstance()->crm()->companies()->basicApi()->getById(\n $crmId,\n implode(',', $fields),\n );\n } catch (CompanyApiException $e) {\n $this->log->info('[Hubspot] Failed to fetch account', [\n 'crm_id' => $crmId,\n 'reason' => $e->getMessage(),\n ]);\n\n throw $e;\n }\n\n if (! $company instanceof CompaniesWithAssociations) {\n throw new CrmException('Account not found');\n }\n\n return [\n 'id' => $company->getId(),\n 'properties' => $company->getProperties(),\n ];\n }\n\n /**\n * @throws ContactApiException\n * @throws CrmException\n */\n public function getContactById(string $crmId, array $fields): array\n {\n try {\n $contact = $this->getNewInstance()->crm()->contacts()->basicApi()->getById(\n $crmId,\n implode(',', $fields)\n );\n } catch (ContactApiException $e) {\n $this->log->info('[Hubspot] Failed to fetch contact', [\n 'crm_id' => $crmId,\n 'reason' => $e->getMessage(),\n ]);\n\n throw $e;\n }\n\n if (! $contact instanceof ContactsWithAssociations) {\n throw new CrmException('Contact not found');\n }\n\n return [\n 'id' => $contact->getId(),\n 'properties' => $contact->getProperties(),\n ];\n }\n\n /**\n * This is email search request that Hubspot offers as GET (more generous quota)\n */\n public function getContactByEmail(string $email, array $fields = []): array\n {\n try {\n $contact = $this->getNewInstance()->crm()->contacts()->basicApi()->getById(\n $email,\n implode(',', $fields),\n null,\n false,\n 'email'\n );\n\n return [\n 'id' => $contact->getId(),\n 'properties' => $contact->getProperties(),\n ];\n } catch (ContactApiException $e) {\n $this->log->info('[Hubspot] Failed to fetch contact', [\n 'email' => $email,\n 'reason' => $e->getMessage(),\n ]);\n\n return [];\n }\n }\n\n /**\n * @throws CrmException\n */\n public function fetchProperty(string $objectType, string $propertyId): Property\n {\n $result = $this->getNewInstance()->crm()->properties()->coreApi()->getByName($objectType, $propertyId);\n\n if (! $result instanceof Property) {\n $this->log->error('[Hubspot] Failed to fetch property', [\n 'object_type' => $objectType,\n 'property_id' => $propertyId,\n 'reason' => $result->getMessage(),\n ]);\n\n throw new CrmException('Failed to fetch property');\n }\n\n return $result;\n }\n\n /**\n * @return array<CrmFieldOption>\n */\n public function fetchPropertyOptions(string $objectType, string $propertyId): array\n {\n /** @var array<CrmFieldOption> */\n return $this->fetchProperty($objectType, $propertyId)->getOptions();\n }\n\n /**\n * @return array<array{id:string, label:string, deleted:bool}>\n */\n public function fetchCallDispositions(): array\n {\n /** @var Response $response */\n $response = $this->getInstance()->engagements()->getCallDispositions();\n\n /**\n * @var array<array{\n * id:string,\n * label:string,\n * deleted: bool\n * }>\n */\n return $response->toArray();\n }\n\n /**\n * @return array<CrmFieldOption>\n */\n public function fetchOpportunityPipelineStages(): array\n {\n $stages = [];\n $apiResponse = $this->getNewInstance()->crm()->pipelines()->pipelinesApi()->getAll('deals');\n\n if ($apiResponse instanceof Error) {\n $this->log->error('[Hubspot] Failed to fetch opportunity pipelines', [\n 'reason' => $apiResponse->getMessage(),\n ]);\n\n return [];\n }\n\n foreach ($apiResponse->getResults() as $pipeline) {\n $pipelineStages = array_map(\n static function (PipelineStage $stage) {\n return [\n 'id' => $stage->getId(),\n 'label' => $stage->getLabel(),\n ];\n },\n $pipeline->getStages()\n );\n\n $stages = array_merge($stages, $pipelineStages);\n }\n\n return $stages;\n }\n\n public function fetchOpportunityPipelines(): array\n {\n $pipelines = [];\n\n try {\n $apiResponse = $this->makeRequest('/crm/v3/pipelines/deals');\n } catch (\\Exception $e) {\n $this->log->info('[Hubspot] Failed to fetch opportunity pipelines', [\n 'reason' => $e->getMessage(),\n ]);\n\n return [];\n }\n\n $response = $apiResponse->toArray();\n\n foreach ($response['results'] as $pipeline) {\n $pipelines[] = [\n 'id' => $pipeline['id'],\n 'label' => $pipeline['label'],\n ];\n }\n\n return $pipelines;\n }\n\n /**\n * @return array<CrmFieldOption>\n */\n public function fetchMeetingOutcomeFieldOptions(Field $field): array\n {\n return $field->getCrmProviderId() === 'meetingOutcome'\n ? $this->fetchMeetingOutcomeTypes()\n : $this->fetchCallActivityTypes();\n }\n\n public function fetchMeetingOutcomeTypes(): array\n {\n return $this->extractMeetingTypeOptions(\n 'https://api.hubapi.com/crm/v3/properties/meeting/hs_meeting_outcome'\n );\n }\n\n public function fetchCallActivityTypes(): array\n {\n return $this->extractMeetingTypeOptions(\n 'https://api.hubapi.com/crm/v3/properties/call/hs_activity_type'\n );\n }\n\n private function extractMeetingTypeOptions(string $endpoint): array\n {\n /** @var Response $response */\n $response = $this->getInstance()\n ->getClient()\n ->request('GET', $endpoint);\n\n /**\n * @var array<array{\n * value: string,\n * label: string,\n * displayOrder: int\n * }> $optionData\n */\n $optionData = $response->toArray()['options'] ?? [];\n\n $options = [];\n foreach ($optionData as $item) {\n $options[] = [\n 'id' => $item['value'],\n 'value' => $item['value'],\n 'label' => $item['label'],\n 'display_order' => $item['displayOrder'],\n ];\n }\n\n return $options;\n }\n\n /**\n * @return array<CrmFieldOption>\n */\n public function fetchDispositionFieldOptions(): array\n {\n $options = [];\n\n $dispositions = $this->fetchCallDispositions();\n\n foreach ($dispositions as $disposition) {\n if ($disposition['deleted'] !== false) {\n continue;\n }\n\n $option['value'] = $disposition['id'];\n $option['id'] = $disposition['id'];\n $option['label'] = $disposition['label'];\n\n $options[] = $option;\n }\n\n return $options;\n }\n\n /**\n * @return array<CrmFieldOption>\n */\n public function fetchOpportunityFieldOptions(Field $field): array\n {\n if ($field->isStageField()) {\n return $this->fetchOpportunityPipelineStages();\n }\n\n if ($field->isPipelineField()) {\n return $this->fetchOpportunityPipelines();\n }\n\n return $this->fetchPropertyOptions('deals', $field->getCrmProviderId());\n }\n\n /**\n * @throws BadRequest\n * @throws HubspotException\n */\n public function makeRequest(string $endpoint, $method = 'GET', $payload = [], ?string $queryString = null)\n {\n $endpoint = self::BASE_URL . $endpoint;\n\n if ($method === 'GET') {\n $response = $this->getInstance()->getClient()?->request(\n method: $method,\n endpoint: $endpoint,\n query_string: $queryString\n );\n } else {\n $response = $this->getInstance()->getClient()->request($method, $endpoint, [\n 'json' => ($payload),\n ]);\n }\n\n $max = $response->getHeaderLine('X-HubSpot-RateLimit-Max'); // \"110\"\n $remaining = $response->getHeaderLine('X-HubSpot-RateLimit-Remaining'); // \"109\"\n $interval = $response->getHeaderLine('X-HubSpot-RateLimit-Interval-Milliseconds'); // \"10000\"\n $body = json_decode((string) $response->getBody(), true);\n\n \\Illuminate\\Support\\Facades\\Log::channel('custom_channel')->info('$max ' . PHP_EOL . print_r($max, true));\n \\Illuminate\\Support\\Facades\\Log::channel('custom_channel')->info('$remaining ' . PHP_EOL . print_r($remaining, true));\n \\Illuminate\\Support\\Facades\\Log::channel('custom_channel')->info('$interval ' . PHP_EOL . print_r($interval, true));\n \\Illuminate\\Support\\Facades\\Log::channel('custom_channel')->info('$body ' . PHP_EOL . print_r($body, true));\n\n return $response;\n }\n\n /**\n * @throws BadRequest\n * @throws HubspotException\n */\n public function createMeeting(array $payload): Response\n {\n $endpoint = '/crm/v3/objects/meetings';\n\n return $this->makeRequest($endpoint, 'POST', $payload);\n }\n\n /**\n * @throws BadRequest\n * @throws HubspotException\n */\n public function updateMeeting(string $meetingId, array $payload): Response\n {\n $endpoint = '/crm/v3/objects/meetings/' . $meetingId;\n\n return $this->makeRequest($endpoint, 'PATCH', $payload);\n }\n\n /**\n * @throws \\Exception\n */\n public function createNote(\n string $body,\n string $ownerId,\n int $timestamp,\n string $objectId,\n NoteObject $noteObject\n ): ?string {\n try {\n $noteInput = new SimplePublicObjectInput([\n 'properties' => [\n 'hs_note_body' => $body,\n 'hubspot_owner_id' => $ownerId,\n 'hs_timestamp' => $timestamp,\n ],\n ]);\n\n // Create note\n $note = $this->getNewInstance()->crm()->objects()->basicApi()->create('note', $noteInput);\n\n $this->getNewInstance()->crm()->objects()->associationsApi()->create(\n 'note',\n $note->getId(),\n $this->getNoteObject($noteObject),\n $objectId,\n $this->getNoteAssociationType($noteObject),\n );\n\n return $note->getId();\n } catch (\\Exception $e) {\n $this->log->error('[Hubspot] Failed to create note', [\n 'objectId' => $objectId,\n 'noteObject' => $noteObject->getObjectType(),\n 'reason' => $e->getMessage(),\n ]);\n\n \\Sentry::captureException($e);\n }\n\n return null;\n }\n\n public function updateEngagement(string $objectId, array $engagement, array $metadata): void\n {\n $this->getInstance()->engagements()->update($objectId, $engagement, $metadata);\n }\n\n public function getEngagementData(string $engagementId): array\n {\n $engagement = $this->getInstance()->engagements()->get($engagementId);\n\n return $engagement->toArray();\n }\n\n public function createEngagement(array $engagement, array $associations, array $metadata): Response\n {\n return $this->getInstance()\n ->engagements()\n ->create($engagement, $associations, $metadata);\n }\n\n public function isUnauthorizedException(\\Exception $e): bool\n {\n // Check for specific HubSpot API exception types first\n if ($e instanceof BadRequest) {\n // BadRequest can contain 401 status codes\n return $e->getCode() === 401;\n }\n\n // Check for HTTP client exceptions with status codes\n if ($e instanceof \\GuzzleHttp\\Exception\\RequestException && $e->hasResponse()) {\n $response = $e->getResponse();\n if ($response !== null) {\n return $response->getStatusCode() === 401;\n }\n }\n\n // Check for Guzzle HTTP exceptions\n if ($e instanceof \\GuzzleHttp\\Exception\\ClientException) {\n return $e->getCode() === 401;\n }\n\n // Fallback to string matching as last resort, but be more specific\n $message = strtolower($e->getMessage());\n\n return str_contains($message, '401 unauthorized') ||\n str_contains($message, 'http 401') ||\n str_contains($message, 'status code 401') ||\n (preg_match('/\\b401\\b/', $message) && str_contains($message, 'unauthorized'));\n }\n\n /**\n * Validates and refreshes the access token if needed before API requests.\n * This ensures long-running processes don't fail due to token expiration.\n *\n * @throws SocialAccountTokenInvalidException\n */\n public function ensureValidToken(): void\n {\n if ($this->oauthAccount === null) {\n return;\n }\n\n $newToken = $this->tokenManager->ensureValidToken($this->oauthAccount);\n if ($newToken !== null) {\n $this->accessToken = $newToken;\n }\n }\n\n public function getConfig()\n {\n return $this->config;\n }\n\n // returns only active (archived=false)\n public function getOwners(): array\n {\n return $this->getNewInstance()->crm()->owners()->getAll();\n }\n\n /**\n * @param bool $archived\n *\n * @return array<Owner>|[]\n */\n public function getOwnersArchived(bool $archived = true): array\n {\n $endpoint = '/crm/v3/owners';\n $queryParams = [\n 'archived' => $archived ? 'true' : 'false',\n ];\n $queryString = http_build_query($queryParams);\n\n $owners = [];\n\n try {\n $response = $this->makeRequest(endpoint: $endpoint, queryString: $queryString);\n $responseData = $response?->toArray();\n\n foreach ($responseData['results'] as $result) {\n try {\n $owners[] = Owner::create($result);\n } catch (Throwable $e) {\n $this->log->error('[HubSpot] Failed to process owner data', [\n 'result' => $result,\n 'error' => $e->getMessage(),\n ]);\n\n continue;\n }\n }\n } catch (Throwable $e) {\n $this->log->error('HubSpot] Failed to fetch owners', [\n 'archived' => $archived,\n 'error' => $e->getMessage(),\n ]);\n\n return [];\n }\n\n return $owners;\n }\n\n public function getMeeting(string $engagementId): ObjectWithAssociations\n {\n return $this->getNewInstance()->crm()->objects()->basicApi()\n ->getById('meeting', $engagementId, null, 'contact,company,deal');\n }\n\n public function deleteEngagement(string $engagementId): void\n {\n $this->getInstance()->engagements()->delete((int) $engagementId);\n }\n\n public function getAssociationsData(array $ids, string $fromObject, string $toObject): array\n {\n $associationData = [];\n $idChunks = array_chunk($ids, self::ASSOCIATIONS_BATCH_SIZE_LIMIT);\n\n foreach ($idChunks as $idChunk) {\n try {\n $batchInput = new \\HubSpot\\Client\\Crm\\Associations\\Model\\BatchInputPublicObjectId();\n $batchInput->setInputs(array_map(function ($id) {\n $publicObjectId = new \\HubSpot\\Client\\Crm\\Associations\\Model\\PublicObjectId();\n $publicObjectId->setId($id);\n\n return $publicObjectId;\n }, $idChunk));\n\n $associatedObjectsData = $this\n ->getNewInstance()\n ->crm()\n ->associations()\n ->batchApi()\n ->read($fromObject, $toObject, $batchInput);\n\n if ($associatedObjectsData instanceof \\HubSpot\\Client\\Crm\\Associations\\Model\\BatchResponsePublicAssociationMulti) {\n foreach ($associatedObjectsData->getResults() as $association) {\n $from = $association->getFrom()->getId();\n $toAssociations = $association->getTo();\n\n if (! empty($toAssociations)) {\n $associationData[$from] = array_map(function ($item) {\n return $item->getId();\n }, $toAssociations);\n }\n }\n }\n } catch (\\Exception $e) {\n $this->log->error('[Hubspot] Failed to fetch associations', [\n 'from_object' => $fromObject,\n 'to_object' => $toObject,\n 'reason' => $e->getMessage(),\n ]);\n }\n }\n\n return $associationData;\n }\n\n /**\n * @throws \\Exception\n */\n private function getNoteAssociationType(NoteObject $noteObject): string\n {\n return match($noteObject) {\n NoteObject::Opportunity => 'note_to_deal',\n NoteObject::Lead, NoteObject::Contact => 'note_to_contact', // or 'note_to_lead' if your portal supports it\n NoteObject::Account => 'note_to_company',\n NoteObject::Call, NoteObject::Event => throw new \\Exception('Not supported'),\n };\n }\n\n /**\n * @throws \\Exception\n */\n private function getNoteObject(NoteObject $noteObject): string\n {\n return match($noteObject) {\n NoteObject::Opportunity => 'deal',\n NoteObject::Lead, NoteObject::Contact => 'contact',\n NoteObject::Account => 'company',\n NoteObject::Call, NoteObject::Event => throw new \\Exception('Not supported'),\n };\n }\n\n public function addAssociations(string $objectType, string $associationType, array $payload): Response\n {\n $endpoint = \"/crm/v4/associations/$objectType/$associationType/batch/create\";\n\n return $this->makeRequest($endpoint, 'POST', $payload);\n }\n\n public function removeAssociations(string $objectType, string $associationType, array $payload): Response\n {\n $endpoint = \"/crm/v4/associations/$objectType/$associationType/batch/archive\";\n\n return $this->makeRequest($endpoint, 'POST', $payload);\n }\n}","role_description":"text entry area","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"Project","depth":3,"on_screen":false,"role_description":"text"},{"role":"AXButton","text":"Project","depth":3,"bounds":{"left":0.011968086,"top":0.047885075,"width":0.024268618,"height":0.024740623},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"New File or Directory…","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.008643617,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Expand Selected","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.008643617,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Collapse All","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.008643617,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Options","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.008643617,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Hide","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.008643617,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false}]...
|
3216904061287467739
|
7531555552090524004
|
visual_change
|
accessibility
|
NULL
|
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
15
Previous Highlighted Error
Next Highlighted Error
lukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/.screenpipe/data/data $ npx screenpipe@latest record --help
Start recording screen, audio, and serve the API
Usage: screenpipe record [OPTIONS]
Options:
-d, --audio-chunk-duration <AUDIO_CHUNK_DURATION>
Audio chunk duration in seconds
[default: 30]
-p, --port <PORT>
Port to run the server on
[default: 3030]
--disable-audio
Disable audio recording
-i, --audio-device <AUDIO_DEVICE>
Audio devices to use (can be specified multiple times)
--use-system-default-audio
Follow system default audio devices
--experimental-coreaudio-system-audio
[experimental, macOS 14.4+] Capture System Audio via CoreAudio Process Tap instead of ScreenCaptureKit. Off by default; ignored on older macOS and non-macOS
--data-dir <DATA_DIR>
Data directory. Default to $HOME/.screenpipe
--debug
Enable debug logging for screenpipe modules
-a, --audio-transcription-engine <AUDIO_TRANSCRIPTION_ENGINE>
Audio transcription engine to use
Possible values:
- deepgram
- whisper-tiny
- whisper-tiny-quantized
- whisper-large
- whisper-large-quantized
- whisper-large-v3-turbo
- whisper-large-v3-turbo-quantized
- openai-compatible
- qwen3-asr
- parakeet
- disabled: Disable transcription (audio capture only, no speech-to-text)
[default: parakeet]
-m, --monitor-id <MONITOR_ID>
Monitor IDs to record. May be specified multiple times. When set, only the listed monitors are recorded (implies `--use-all-monitors=false`)
--use-all-monitors
Automatically record all monitors. Ignored when `--monitor-id` is passed
-l, --language <LANGUAGE>
Languages for OCR/transcription
[possible values: english, chinese, german, spanish, russian, korean, french, japanese, portuguese, turkish, polish, catalan, dutch, arabic, swedish, italian, indonesian, hindi, vietnamese, finnish, hebrew, ukrainian, greek, malay, czech, romanian, danish, hungarian, norwegian, thai, urdu, croatian, bulgarian, lithuanian, latin, malayalam, welsh, slovak, persian, latvian, bengali, serbian, azerbaijani, slovenian, estonian, macedonian, nepali, mongolian, bosnian, kazakh, albanian, swahili, galician, marathi, punjabi, sinhala, khmer, afrikaans, belarusian, gujarati, amharic, yiddish, lao, uzbek, faroese, pashto, maltese, sanskrit, luxembourgish, myanmar, tibetan, tagalog, assamese, tatar, hausa, javanese]
--use-pii-removal
Enable PII removal
--filter-music
Filter music-dominant audio before transcription (reduces Spotify/YouTube music noise)
--disable-vision
Disable vision recording
--ignored-windows <IGNORED_WINDOWS>
Windows to ignore (by title, uses contains matching)
--included-windows <INCLUDED_WINDOWS>
Windows to include (by title, uses contains matching)
--ignored-urls <IGNORED_URLS>
URLs to ignore for browser privacy filtering
--deepgram-api-key <DEEPGRAM_API_KEY>
Deepgram API Key for audio transcription
--transcription-mode <TRANSCRIPTION_MODE>
Audio transcription scheduling mode: batch (default, longer chunks for quality) or realtime
Possible values:
- realtime: Transcribe immediately as audio is captured
- batch: Accumulate longer audio batches for better transcription quality (default)
[default: batch]
--disable-telemetry
Disable telemetry
--video-quality <VIDEO_QUALITY>
Video quality preset: low, balanced, high, max
[default: balanced]
--enable-sync
Enable cloud sync
--sync-token <SYNC_TOKEN>
API token for cloud sync
[env: SCREENPIPE_SYNC_TOKEN=]
--sync-password <SYNC_PASSWORD>
Password for encrypting synced data
[env: SCREENPIPE_SYNC_PASSWORD=[PASSWORD]
--sync-interval-secs <SYNC_INTERVAL_SECS>
Interval between sync cycles in seconds
[default: 300]
--sync-machine-id <SYNC_MACHINE_ID>
Override the machine ID for this device
--pause-on-drm-content
Pause screen and audio capture when a DRM-protected streaming app (Netflix, Disney+, etc.) or a remote-desktop client (Omnissa/VMware Horizon) is focused — these blank their windows while any app is recording the screen
--api-auth
Require authentication for remote API access. When enabled, non-localhost requests must include Authorization: Bearer <SCREENPIPE_API_KEY>. Localhost requests are always allowed
--listen-on-lan
Bind the HTTP server to [IP_ADDRESS] so other devices on the LAN can reach it. Off by default — the server binds [IP_ADDRESS] only. `--api-auth` is forced on whenever this flag is used; you can't accidentally expose an unauthenticated API on your network
--encrypt-secrets
Encrypt secrets (API keys, OAuth tokens) at rest using the OS keychain. Creates a keychain key if one doesn't exist. Without this flag, the CLI will use an existing key (created by the desktop app) but won't create one
--retention-days <RETENTION_DAYS>
Local data retention in days. Old screen/audio data is auto-deleted after this period. Set to 0 to disable retention (keep data forever)
[default: 14]
-h, --help
Print help (see a summary with '-h')
Sync Changes
Hide This Notification
Code changed:
Hide
2
60
Previous Highlighted Error
Next Highlighted Error
<?php
declare(strict_types=1);
namespace Jiminny\Services\Crm\Hubspot;
use HubSpot\Client\Crm\Deals\ApiException as DealApiException;
use HubSpot\Client\Crm\Contacts\ApiException as ContactApiException;
use HubSpot\Client\Crm\Companies\ApiException as CompanyApiException;
use HubSpot\Client\Crm\Contacts\Model\SimplePublicObjectWithAssociations as ContactsWithAssociations;
use HubSpot\Client\Crm\Companies\Model\SimplePublicObjectWithAssociations as CompaniesWithAssociations;
use HubSpot\Client\Crm\Deals\Model\SimplePublicObjectWithAssociations as DealWithAssociations;
use HubSpot\Client\Crm\Objects\Model\SimplePublicObjectInput;
use HubSpot\Client\Crm\Objects\Model\SimplePublicObjectWithAssociations as ObjectWithAssociations;
use HubSpot\Client\Crm\Pipelines\Model\Error;
use HubSpot\Client\Crm\Pipelines\Model\PipelineStage;
use HubSpot\Client\Crm\Properties\Model\Property;
use HubSpot\Discovery\Discovery;
use Jiminny\Component\Utility\Service\ProviderRateLimiter;
use Jiminny\Exceptions\CrmException;
use Jiminny\Exceptions\RateLimitException;
use Jiminny\Exceptions\SocialAccountTokenInvalidException;
use Jiminny\Jobs\Crm\NoteObject;
use Jiminny\Models\Crm\Field;
use Jiminny\Services\Crm\BaseClient;
use Jiminny\Services\Crm\Hubspot\DTO\Response\Owner;
use Jiminny\Services\SocialAccountService;
use SevenShores\Hubspot\Exceptions\BadRequest;
use SevenShores\Hubspot\Exceptions\HubspotException;
use SevenShores\Hubspot\Factory;
use SevenShores\Hubspot\Http\Response;
use Jiminny\Services\Crm\Hubspot\Pagination\HubspotPaginationService;
use Throwable;
/**
* @phpstan-type CrmFieldOption array{id:string, label:string, value?:string}
*/
class Client extends BaseClient implements HubspotClientInterface
{
public const string MIN_API_VERSION = '2';
public const string BASE_URL = '[URL_WITH_CREDENTIALS] T
* @param callable(): T $apiCall
* @return T
*
* @throws RateLimitException
*/
private function executeRequest(callable $apiCall)
{
if (! $this->rateLimiter->canMakeRequest($this->config)) {
$retryAfter = $this->rateLimiter->requestAvailableIn($this->config);
$this->log->warning('[Hubspot] Rate limit exceeded, deferring request', [
'team_id' => $this->config->team_id,
'config_id' => $this->config->getId(),
'retry_after' => $retryAfter,
]);
throw new RateLimitException(
'Hubspot rate limit reached for configuration ' . $this->config->getId(),
$retryAfter,
);
}
$this->rateLimiter->incrementRequestCount($this->config);
try {
return $apiCall();
} catch (Throwable $e) {
if ($this->isHubspotRateLimit($e)) {
$retryAfter = $this->parseRetryAfter($e);
$this->log->warning('[Hubspot] Received 429 from API', [
'team_id' => $this->config->team_id,
'config_id' => $this->config->getId(),
'retry_after' => $retryAfter,
'reason' => $e->getMessage(),
]);
throw new RateLimitException('Hubspot returned 429', $retryAfter, $e);
}
throw $e;
}
}
private function isHubspotRateLimit(Throwable $e): bool
{
return method_exists($e, 'getCode') && (int) $e->getCode() === 429;
}
private function parseRetryAfter(Throwable $e): int
{
if (method_exists($e, 'getResponseHeaders')) {
$headers = $e->getResponseHeaders() ?: [];
$value = $headers['Retry-After'] ?? $headers['retry-after'] ?? null;
if (is_array($value)) {
$value = $value[0] ?? null;
}
if (is_numeric($value)) {
return (int) $value;
}
}
return 10;
}
public function getMinimumApiVersion(): string
{
return self::MIN_API_VERSION;
}
public function getInstance(): Factory
{
return new Factory([
'key' => $this->accessToken,
'oauth2' => true,
'base_url' => $this->baseUrl,
]);
}
public function getNewInstance(): Discovery
{
return \HubSpot\Factory::createWithAccessToken($this->accessToken);
}
/**
* Secondly and daily limits for Hubspot API
*
* Product Tier: Free & Starter | Professional & Enterprise | API add-on (any tier)
* Burst: 100/10 seconds | 150/10 seconds | 200/10 seconds
* Daily: 250,000 | 500,000 | 1,000,000
*
* Official documentation states: The search endpoints are rate limited to five requests per second.
* Since with 5 RPS were still hitting secondly rate limits we lowered it to 4
*/
public function getPaginatedData(array $payload, string $type, int $offset = 0): array
{
$total = 0;
$lastId = null;
$rows = [];
foreach ($this->getPaginatedDataGenerator($payload, $type, $offset, $total, $lastId) as $row) {
$rows[] = $row;
}
return ['results' => $rows, 'total' => $total, 'last_record' => $lastId];
}
/**
* @throws HubspotException
* @throws SocialAccountTokenInvalidException
* @throws BadRequest
*/
public function getPaginatedDataGenerator(
array $payload,
string $type,
int $offset = 0,
int &$total = 0,
?string &$lastRecordId = null
): \Generator {
return $this->paginationService->getPaginatedDataGenerator(
$this,
$payload,
$type,
$offset,
$total,
$lastRecordId
);
}
/**
* @throws DealApiException
* @throws CrmException
*/
public function getOpportunityById(string $crmId, array $fields): array
{
try {
$deal = $this->executeRequest(fn () => $this->getNewInstance()->crm()->deals()->basicApi()->getById(
$crmId,
implode(',', $fields),
'companies,contacts'
));
} catch (DealApiException $e) {
$this->log->info('[Hubspot] Failed to fetch opportunity', [
'crm_id' => $crmId,
'reason' => $e->getMessage(),
]);
throw $e;
}
if (! $deal instanceof DealWithAssociations) {
throw new CrmException('Deal not found');
}
return [
'id' => $deal->getId(),
'properties' => $deal->getProperties(),
'associations' => $deal->getAssociations(),
];
}
/**
* Generic batch read method for HubSpot objects
*
* @param string $objectType The object type ('deals', 'companies', 'contacts')
* @param array<string> $crmIds Array of HubSpot object IDs (max 100)
* @param array<string> $fields Array of property names to fetch
*
* @return array<string, array> Array keyed by CRM ID with object data
*/
private function batchReadObjects(string $objectType, array $crmIds, array $fields): array
{
if (empty($crmIds)) {
return [];
}
$this->validateBatchSize($objectType, $crmIds);
$this->ensureValidToken();
try {
$batchConfig = $this->createBatchConfiguration($objectType);
$batchReadRequest = $this->prepareBatchRequest($batchConfig, $crmIds, $fields);
$response = $batchConfig['api']->read($batchReadRequest);
$this->validateApiResponse($response, $objectType);
$results = $this->processApiResults($response);
$this->logBatchResults($objectType, $crmIds, $results);
return $results;
} catch (\Throwable $e) {
$this->handleBatchError($e, $objectType, $crmIds);
}
}
private function validateBatchSize(string $objectType, array $crmIds): void
{
if (count($crmIds) > 100) {
throw new \InvalidArgumentException("Batch size cannot exceed 100 {$objectType}");
}
}
private function createBatchConfiguration(string $objectType): array
{
$configurations = [
'deals' => [
'batchReadRequest' => new \HubSpot\Client\Crm\Deals\Model\BatchReadInputSimplePublicObjectId(),
'inputClass' => \HubSpot\Client\Crm\Deals\Model\SimplePublicObjectId::class,
'api' => $this->getNewInstance()->crm()->deals()->batchApi(),
],
'companies' => [
'batchReadRequest' => new \HubSpot\Client\Crm\Companies\Model\BatchReadInputSimplePublicObjectId(),
'inputClass' => \HubSpot\Client\Crm\Companies\Model\SimplePublicObjectId::class,
'api' => $this->getNewInstance()->crm()->companies()->batchApi(),
],
'contacts' => [
'batchReadRequest' => new \HubSpot\Client\Crm\Contacts\Model\BatchReadInputSimplePublicObjectId(),
'inputClass' => \HubSpot\Client\Crm\Contacts\Model\SimplePublicObjectId::class,
'api' => $this->getNewInstance()->crm()->contacts()->batchApi(),
],
];
if (! isset($configurations[$objectType])) {
throw new \InvalidArgumentException("Unsupported object type: {$objectType}");
}
return $configurations[$objectType];
}
private function prepareBatchRequest(array $batchConfig, array $crmIds, array $fields): object
{
$batchReadRequest = $batchConfig['batchReadRequest'];
$inputClass = $batchConfig['inputClass'];
$inputs = array_map(function ($crmId) use ($inputClass) {
$input = new $inputClass();
$input->setId($crmId);
return $input;
}, $crmIds);
$batchReadRequest->setInputs($inputs);
$batchReadRequest->setProperties($fields);
return $batchReadRequest;
}
private function validateApiResponse($response, string $objectType): void
{
if (! $response) {
throw new CrmException("HubSpot API returned null response for {$objectType} batch read");
}
}
private function processApiResults($response): array
{
$results = [];
$responseResults = $response->getResults();
if ($responseResults) {
foreach ($responseResults as $object) {
if ($object && $object->getId()) {
$results[$object->getId()] = [
'id' => $object->getId(),
'properties' => $object->getProperties() ?: [],
];
}
}
}
return $results;
}
private function logBatchResults(string $objectType, array $crmIds, array $results): void
{
$this->log->info("[HubSpot] Batch fetched {$objectType}", [
'requested_count' => count($crmIds),
'returned_count' => count($results),
'crm_ids' => $crmIds,
]);
}
private function handleBatchError(\Throwable $e, string $objectType, array $crmIds): void
{
$errorMessage = $e->getMessage() ?: 'Unknown error';
$errorTrace = $e->getTraceAsString() ?: 'No trace available';
$this->log->error("[HubSpot] Failed to batch fetch {$objectType}", [
'crm_ids' => $crmIds,
'error' => $errorMessage,
'trace' => $errorTrace,
]);
throw new CrmException("Failed to batch fetch {$objectType}: " . $errorMessage);
}
/**
* Batch read multiple opportunities by their CRM IDs
*
* @param array<string> $crmIds Array of HubSpot deal IDs (max 100)
* @param array<string> $fields Array of property names to fetch
*
* @return array<string, array> Array keyed by CRM ID with opportunity data
*/
public function getOpportunitiesByIds(array $crmIds, array $fields): array
{
return $this->batchReadObjects('deals', $crmIds, $fields);
}
/**
* Batch read multiple companies by their CRM IDs
*
* @param array<string> $crmIds Array of HubSpot company IDs (max 100)
* @param array<string> $fields Array of property names to fetch
*
* @return array<string, array> Array keyed by CRM ID with company data
*/
public function getCompaniesByIds(array $crmIds, array $fields): array
{
return $this->batchReadObjects('companies', $crmIds, $fields);
}
/**
* Batch read multiple contacts by their CRM IDs
*
* @param array<string> $crmIds Array of HubSpot contact IDs (max 100)
* @param array<string> $fields Array of property names to fetch
*
* @return array<string, array> Array keyed by CRM ID with contact data
*/
public function getContactsByIds(array $crmIds, array $fields): array
{
return $this->batchReadObjects('contacts', $crmIds, $fields);
}
/**
* @throws CompanyApiException
* @throws CrmException
*/
public function getAccountById(string $crmId, array $fields): array
{
try {
$company = $this->getNewInstance()->crm()->companies()->basicApi()->getById(
$crmId,
implode(',', $fields),
);
} catch (CompanyApiException $e) {
$this->log->info('[Hubspot] Failed to fetch account', [
'crm_id' => $crmId,
'reason' => $e->getMessage(),
]);
throw $e;
}
if (! $company instanceof CompaniesWithAssociations) {
throw new CrmException('Account not found');
}
return [
'id' => $company->getId(),
'properties' => $company->getProperties(),
];
}
/**
* @throws ContactApiException
* @throws CrmException
*/
public function getContactById(string $crmId, array $fields): array
{
try {
$contact = $this->getNewInstance()->crm()->contacts()->basicApi()->getById(
$crmId,
implode(',', $fields)
);
} catch (ContactApiException $e) {
$this->log->info('[Hubspot] Failed to fetch contact', [
'crm_id' => $crmId,
'reason' => $e->getMessage(),
]);
throw $e;
}
if (! $contact instanceof ContactsWithAssociations) {
throw new CrmException('Contact not found');
}
return [
'id' => $contact->getId(),
'properties' => $contact->getProperties(),
];
}
/**
* This is email search request that Hubspot offers as GET (more generous quota)
*/
public function getContactByEmail(string $email, array $fields = []): array
{
try {
$contact = $this->getNewInstance()->crm()->contacts()->basicApi()->getById(
$email,
implode(',', $fields),
null,
false,
'email'
);
return [
'id' => $contact->getId(),
'properties' => $contact->getProperties(),
];
} catch (ContactApiException $e) {
$this->log->info('[Hubspot] Failed to fetch contact', [
'email' => $email,
'reason' => $e->getMessage(),
]);
return [];
}
}
/**
* @throws CrmException
*/
public function fetchProperty(string $objectType, string $propertyId): Property
{
$result = $this->getNewInstance()->crm()->properties()->coreApi()->getByName($objectType, $propertyId);
if (! $result instanceof Property) {
$this->log->error('[Hubspot] Failed to fetch property', [
'object_type' => $objectType,
'property_id' => $propertyId,
'reason' => $result->getMessage(),
]);
throw new CrmException('Failed to fetch property');
}
return $result;
}
/**
* @return array<CrmFieldOption>
*/
public function fetchPropertyOptions(string $objectType, string $propertyId): array
{
/** @var array<CrmFieldOption> */
return $this->fetchProperty($objectType, $propertyId)->getOptions();
}
/**
* @return array<array{id:string, label:string, deleted:bool}>
*/
public function fetchCallDispositions(): array
{
/** @var Response $response */
$response = $this->getInstance()->engagements()->getCallDispositions();
/**
* @var array<array{
* id:string,
* label:string,
* deleted: bool
* }>
*/
return $response->toArray();
}
/**
* @return array<CrmFieldOption>
*/
public function fetchOpportunityPipelineStages(): array
{
$stages = [];
$apiResponse = $this->getNewInstance()->crm()->pipelines()->pipelinesApi()->getAll('deals');
if ($apiResponse instanceof Error) {
$this->log->error('[Hubspot] Failed to fetch opportunity pipelines', [
'reason' => $apiResponse->getMessage(),
]);
return [];
}
foreach ($apiResponse->getResults() as $pipeline) {
$pipelineStages = array_map(
static function (PipelineStage $stage) {
return [
'id' => $stage->getId(),
'label' => $stage->getLabel(),
];
},
$pipeline->getStages()
);
$stages = array_merge($stages, $pipelineStages);
}
return $stages;
}
public function fetchOpportunityPipelines(): array
{
$pipelines = [];
try {
$apiResponse = $this->makeRequest('/crm/v3/pipelines/deals');
} catch (\Exception $e) {
$this->log->info('[Hubspot] Failed to fetch opportunity pipelines', [
'reason' => $e->getMessage(),
]);
return [];
}
$response = $apiResponse->toArray();
foreach ($response['results'] as $pipeline) {
$pipelines[] = [
'id' => $pipeline['id'],
'label' => $pipeline['label'],
];
}
return $pipelines;
}
/**
* @return array<CrmFieldOption>
*/
public function fetchMeetingOutcomeFieldOptions(Field $field): array
{
return $field->getCrmProviderId() === 'meetingOutcome'
? $this->fetchMeetingOutcomeTypes()
: $this->fetchCallActivityTypes();
}
public function fetchMeetingOutcomeTypes(): array
{
return $this->extractMeetingTypeOptions(
'[URL_WITH_CREDENTIALS] Response $response */
$response = $this->getInstance()
->getClient()
->request('GET', $endpoint);
/**
* @var array<array{
* value: string,
* label: string,
* displayOrder: int
* }> $optionData
*/
$optionData = $response->toArray()['options'] ?? [];
$options = [];
foreach ($optionData as $item) {
$options[] = [
'id' => $item['value'],
'value' => $item['value'],
'label' => $item['label'],
'display_order' => $item['displayOrder'],
];
}
return $options;
}
/**
* @return array<CrmFieldOption>
*/
public function fetchDispositionFieldOptions(): array
{
$options = [];
$dispositions = $this->fetchCallDispositions();
foreach ($dispositions as $disposition) {
if ($disposition['deleted'] !== false) {
continue;
}
$option['value'] = $disposition['id'];
$option['id'] = $disposition['id'];
$option['label'] = $disposition['label'];
$options[] = $option;
}
return $options;
}
/**
* @return array<CrmFieldOption>
*/
public function fetchOpportunityFieldOptions(Field $field): array
{
if ($field->isStageField()) {
return $this->fetchOpportunityPipelineStages();
}
if ($field->isPipelineField()) {
return $this->fetchOpportunityPipelines();
}
return $this->fetchPropertyOptions('deals', $field->getCrmProviderId());
}
/**
* @throws BadRequest
* @throws HubspotException
*/
public function makeRequest(string $endpoint, $method = 'GET', $payload = [], ?string $queryString = null)
{
$endpoint = self::BASE_URL . $endpoint;
if ($method === 'GET') {
$response = $this->getInstance()->getClient()?->request(
method: $method,
endpoint: $endpoint,
query_string: $queryString
);
} else {
$response = $this->getInstance()->getClient()->request($method, $endpoint, [
'json' => ($payload),
]);
}
$max = $response->getHeaderLine('X-HubSpot-RateLimit-Max'); // "110"
$remaining = $response->getHeaderLine('X-HubSpot-RateLimit-Remaining'); // "109"
$interval = $response->getHeaderLine('X-HubSpot-RateLimit-Interval-Milliseconds'); // "10000"
$body = json_decode((string) $response->getBody(), true);
\Illuminate\Support\Facades\Log::channel('custom_channel')->info('$max ' . PHP_EOL . print_r($max, true));
\Illuminate\Support\Facades\Log::channel('custom_channel')->info('$remaining ' . PHP_EOL . print_r($remaining, true));
\Illuminate\Support\Facades\Log::channel('custom_channel')->info('$interval ' . PHP_EOL . print_r($interval, true));
\Illuminate\Support\Facades\Log::channel('custom_channel')->info('$body ' . PHP_EOL . print_r($body, true));
return $response;
}
/**
* @throws BadRequest
* @throws HubspotException
*/
public function createMeeting(array $payload): Response
{
$endpoint = '/crm/v3/objects/meetings';
return $this->makeRequest($endpoint, 'POST', $payload);
}
/**
* @throws BadRequest
* @throws HubspotException
*/
public function updateMeeting(string $meetingId, array $payload): Response
{
$endpoint = '/crm/v3/objects/meetings/' . $meetingId;
return $this->makeRequest($endpoint, 'PATCH', $payload);
}
/**
* @throws \Exception
*/
public function createNote(
string $body,
string $ownerId,
int $timestamp,
string $objectId,
NoteObject $noteObject
): ?string {
try {
$noteInput = new SimplePublicObjectInput([
'properties' => [
'hs_note_body' => $body,
'hubspot_owner_id' => $ownerId,
'hs_timestamp' => $timestamp,
],
]);
// Create note
$note = $this->getNewInstance()->crm()->objects()->basicApi()->create('note', $noteInput);
$this->getNewInstance()->crm()->objects()->associationsApi()->create(
'note',
$note->getId(),
$this->getNoteObject($noteObject),
$objectId,
$this->getNoteAssociationType($noteObject),
);
return $note->getId();
} catch (\Exception $e) {
$this->log->error('[Hubspot] Failed to create note', [
'objectId' => $objectId,
'noteObject' => $noteObject->getObjectType(),
'reason' => $e->getMessage(),
]);
\Sentry::captureException($e);
}
return null;
}
public function updateEngagement(string $objectId, array $engagement, array $metadata): void
{
$this->getInstance()->engagements()->update($objectId, $engagement, $metadata);
}
public function getEngagementData(string $engagementId): array
{
$engagement = $this->getInstance()->engagements()->get($engagementId);
return $engagement->toArray();
}
public function createEngagement(array $engagement, array $associations, array $metadata): Response
{
return $this->getInstance()
->engagements()
->create($engagement, $associations, $metadata);
}
public function isUnauthorizedException(\Exception $e): bool
{
// Check for specific HubSpot API exception types first
if ($e instanceof BadRequest) {
// BadRequest can contain 401 status codes
return $e->getCode() === 401;
}
// Check for HTTP client exceptions with status codes
if ($e instanceof \GuzzleHttp\Exception\RequestException && $e->hasResponse()) {
$response = $e->getResponse();
if ($response !== null) {
return $response->getStatusCode() === 401;
}
}
// Check for Guzzle HTTP exceptions
if ($e instanceof \GuzzleHttp\Exception\ClientException) {
return $e->getCode() === 401;
}
// Fallback to string matching as last resort, but be more specific
$message = strtolower($e->getMessage());
return str_contains($message, '401 unauthorized') ||
str_contains($message, 'http 401') ||
str_contains($message, 'status code 401') ||
(preg_match('/\b401\b/', $message) && str_contains($message, 'unauthorized'));
}
/**
* Validates and refreshes the access token if needed before API requests.
* This ensures long-running processes don't fail due to token expiration.
*
* @throws SocialAccountTokenInvalidException
*/
public function ensureValidToken(): void
{
if ($this->oauthAccount === null) {
return;
}
$newToken = $this->tokenManager->ensureValidToken($this->oauthAccount);
if ($newToken !== null) {
$this->accessToken = $newToken;
}
}
public function getConfig()
{
return $this->config;
}
// returns only active (archived=false)
public function getOwners(): array
{
return $this->getNewInstance()->crm()->owners()->getAll();
}
/**
* @param bool $archived
*
* @return array<Owner>|[]
*/
public function getOwnersArchived(bool $archived = true): array
{
$endpoint = '/crm/v3/owners';
$queryParams = [
'archived' => $archived ? 'true' : 'false',
];
$queryString = http_build_query($queryParams);
$owners = [];
try {
$response = $this->makeRequest(endpoint: $endpoint, queryString: $queryString);
$responseData = $response?->toArray();
foreach ($responseData['results'] as $result) {
try {
$owners[] = Owner::create($result);
} catch (Throwable $e) {
$this->log->error('[HubSpot] Failed to process owner data', [
'result' => $result,
'error' => $e->getMessage(),
]);
continue;
}
}
} catch (Throwable $e) {
$this->log->error('HubSpot] Failed to fetch owners', [
'archived' => $archived,
'error' => $e->getMessage(),
]);
return [];
}
return $owners;
}
public function getMeeting(string $engagementId): ObjectWithAssociations
{
return $this->getNewInstance()->crm()->objects()->basicApi()
->getById('meeting', $engagementId, null, 'contact,company,deal');
}
public function deleteEngagement(string $engagementId): void
{
$this->getInstance()->engagements()->delete((int) $engagementId);
}
public function getAssociationsData(array $ids, string $fromObject, string $toObject): array
{
$associationData = [];
$idChunks = array_chunk($ids, self::ASSOCIATIONS_BATCH_SIZE_LIMIT);
foreach ($idChunks as $idChunk) {
try {
$batchInput = new \HubSpot\Client\Crm\Associations\Model\BatchInputPublicObjectId();
$batchInput->setInputs(array_map(function ($id) {
$publicObjectId = new \HubSpot\Client\Crm\Associations\Model\PublicObjectId();
$publicObjectId->setId($id);
return $publicObjectId;
}, $idChunk));
$associatedObjectsData = $this
->getNewInstance()
->crm()
->associations()
->batchApi()
->read($fromObject, $toObject, $batchInput);
if ($associatedObjectsData instanceof \HubSpot\Client\Crm\Associations\Model\BatchResponsePublicAssociationMulti) {
foreach ($associatedObjectsData->getResults() as $association) {
$from = $association->getFrom()->getId();
$toAssociations = $association->getTo();
if (! empty($toAssociations)) {
$associationData[$from] = array_map(function ($item) {
return $item->getId();
}, $toAssociations);
}
}
}
} catch (\Exception $e) {
$this->log->error('[Hubspot] Failed to fetch associations', [
'from_object' => $fromObject,
'to_object' => $toObject,
'reason' => $e->getMessage(),
]);
}
}
return $associationData;
}
/**
* @throws \Exception
*/
private function getNoteAssociationType(NoteObject $noteObject): string
{
return match($noteObject) {
NoteObject::Opportunity => 'note_to_deal',
NoteObject::Lead, NoteObject::Contact => 'note_to_contact', // or 'note_to_lead' if your portal supports it
NoteObject::Account => 'note_to_company',
NoteObject::Call, NoteObject::Event => throw new \Exception('Not supported'),
};
}
/**
* @throws \Exception
*/
private function getNoteObject(NoteObject $noteObject): string
{
return match($noteObject) {
NoteObject::Opportunity => 'deal',
NoteObject::Lead, NoteObject::Contact => 'contact',
NoteObject::Account => 'company',
NoteObject::Call, NoteObject::Event => throw new \Exception('Not supported'),
};
}
public function addAssociations(string $objectType, string $associationType, array $payload): Response
{
$endpoint = "/crm/v4/associations/$objectType/$associationType/batch/create";
return $this->makeRequest($endpoint, 'POST', $payload);
}
public function removeAssociations(string $objectType, string $associationType, array $payload): Response
{
$endpoint = "/crm/v4/associations/$objectType/$associationType/batch/archive";
return $this->makeRequest($endpoint, 'POST', $payload);
}
}
Project
Project
New File or Directory…
Expand Selected
Collapse All
Options
Hide...
|
NULL
|
NULL
|
NULL
|
NULL
|
|
2720
|
112
|
32
|
2026-05-07T11:36:55.402825+00:00
|
/Users/lukas/.screenpipe/data/data/2026-05-07/1778 /Users/lukas/.screenpipe/data/data/2026-05-07/1778153815402_m2.jpg...
|
iTerm2
|
DEV (docker)
|
True
|
NULL
|
monitor_2
|
NULL
|
NULL
|
NULL
|
NULL
|
Last login: Thu May 7 09:44:56 on ttys006
Poetry Last login: Thu May 7 09:44:56 on ttys006
Poetry could not find a pyproject.toml file in /Users/lukas/jiminny/app or its parents
Poetry could not find a pyproject.toml file in /Users/lukas/jiminny/app or its parents
lukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/jiminny/app (JY-20773-fix-automated-reports-user-pilot-tracking) $ dev
root@docker_lamp_1:/home/jiminny# php artisan crm:sync-opportunity --teamId=2 --from='2026-05-01 00:00:00'
Syncing opportunity for Hubspot
Your HubSpot account has become disconnected. Please login to Jiminny to reconnect. skipping...
root@docker_lamp_1:/home/jiminny# php artisan jiminny:token-info -A 1499 -R
----------------------------------------------------------------------------------------------------
access_token => CNeR-JHgMxIZQlNQMl8kQEwrAgwACAkUAhIJBB4BAQEDBxiCiYwCIN7Y_Qwo0qwCMhTnG549n-YtNuc1jgj-2AsLPSmw3DoyQlNQMl8kQEwrAiUACBkGawEFThwBARIBAQEEATEEAQEBAQEBAQEBAQUBEggBAQEBAYlCFPAsBNZxoDp5kAcRyeBlQoE5SM7DSgNuYTFSAFoAYABo3tj9DHAAeAA
----------------------------------------------------------------------------------------------------
access_token_expires_at => 2026-05-07 11:41:20
----------------------------------------------------------------------------------------------------
refresh_token => d5ab04e2-2109-4c0b-b513-8cba1dd54371
----------------------------------------------------------------------------------------------------
refresh_token_expires_at =>
----------------------------------------------------------------------------------------------------
root@docker_lamp_1:/home/jiminny# php artisan crm:sync-opportunity --teamId=2 --from='2026-05-01 00:00:00'
Syncing opportunity for Hubspot
Syncing opportunities modified since 2026-05-01 00:00:00...
Synced 6 opportunities.
root@docker_lamp_1:/home/jiminny#
DOCKER
Close Tab
DEV (docker)
Close Tab
APP (-zsh)
Close Tab
-zsh
Close Tab
screenpipe"
Close Tab
-zsh
Close Tab
⌥⌘1
DEV (docker)...
|
[{"role":"AXTextArea","text [{"role":"AXTextArea","text":"Last login: Thu May 7 09:44:56 on ttys006\n\nPoetry could not find a pyproject.toml file in /Users/lukas/jiminny/app or its parents\n\nPoetry could not find a pyproject.toml file in /Users/lukas/jiminny/app or its parents\nlukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/jiminny/app (JY-20773-fix-automated-reports-user-pilot-tracking) $ dev\nroot@docker_lamp_1:/home/jiminny# php artisan crm:sync-opportunity --teamId=2 --from='2026-05-01 00:00:00'\nSyncing opportunity for Hubspot\nYour HubSpot account has become disconnected. Please login to Jiminny to reconnect. skipping...\nroot@docker_lamp_1:/home/jiminny# php artisan jiminny:token-info -A 1499 -R\n----------------------------------------------------------------------------------------------------\naccess_token => CNeR-JHgMxIZQlNQMl8kQEwrAgwACAkUAhIJBB4BAQEDBxiCiYwCIN7Y_Qwo0qwCMhTnG549n-YtNuc1jgj-2AsLPSmw3DoyQlNQMl8kQEwrAiUACBkGawEFThwBARIBAQEEATEEAQEBAQEBAQEBAQUBEggBAQEBAYlCFPAsBNZxoDp5kAcRyeBlQoE5SM7DSgNuYTFSAFoAYABo3tj9DHAAeAA\n----------------------------------------------------------------------------------------------------\naccess_token_expires_at => 2026-05-07 11:41:20\n----------------------------------------------------------------------------------------------------\nrefresh_token => d5ab04e2-2109-4c0b-b513-8cba1dd54371\n----------------------------------------------------------------------------------------------------\nrefresh_token_expires_at => \n----------------------------------------------------------------------------------------------------\nroot@docker_lamp_1:/home/jiminny# php artisan crm:sync-opportunity --teamId=2 --from='2026-05-01 00:00:00'\nSyncing opportunity for Hubspot\nSyncing opportunities modified since 2026-05-01 00:00:00...\nSynced 6 opportunities.\nroot@docker_lamp_1:/home/jiminny#","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.4800532,"height":-0.06304872},"on_screen":true,"value":"Last login: Thu May 7 09:44:56 on ttys006\n\nPoetry could not find a pyproject.toml file in /Users/lukas/jiminny/app or its parents\n\nPoetry could not find a pyproject.toml file in /Users/lukas/jiminny/app or its parents\nlukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/jiminny/app (JY-20773-fix-automated-reports-user-pilot-tracking) $ dev\nroot@docker_lamp_1:/home/jiminny# php artisan crm:sync-opportunity --teamId=2 --from='2026-05-01 00:00:00'\nSyncing opportunity for Hubspot\nYour HubSpot account has become disconnected. Please login to Jiminny to reconnect. skipping...\nroot@docker_lamp_1:/home/jiminny# php artisan jiminny:token-info -A 1499 -R\n----------------------------------------------------------------------------------------------------\naccess_token => CNeR-JHgMxIZQlNQMl8kQEwrAgwACAkUAhIJBB4BAQEDBxiCiYwCIN7Y_Qwo0qwCMhTnG549n-YtNuc1jgj-2AsLPSmw3DoyQlNQMl8kQEwrAiUACBkGawEFThwBARIBAQEEATEEAQEBAQEBAQEBAQUBEggBAQEBAYlCFPAsBNZxoDp5kAcRyeBlQoE5SM7DSgNuYTFSAFoAYABo3tj9DHAAeAA\n----------------------------------------------------------------------------------------------------\naccess_token_expires_at => 2026-05-07 11:41:20\n----------------------------------------------------------------------------------------------------\nrefresh_token => d5ab04e2-2109-4c0b-b513-8cba1dd54371\n----------------------------------------------------------------------------------------------------\nrefresh_token_expires_at => \n----------------------------------------------------------------------------------------------------\nroot@docker_lamp_1:/home/jiminny# php artisan crm:sync-opportunity --teamId=2 --from='2026-05-01 00:00:00'\nSyncing opportunity for Hubspot\nSyncing opportunities modified since 2026-05-01 00:00:00...\nSynced 6 opportunities.\nroot@docker_lamp_1:/home/jiminny#","is_focused":true},{"role":"AXRadioButton","text":"DOCKER","depth":2,"bounds":{"left":0.27027926,"top":1.0,"width":0.0787899,"height":-0.042298436},"on_screen":true,"role_description":"radio button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Close Tab","depth":3,"bounds":{"left":0.27227393,"top":1.0,"width":0.005319149,"height":-0.04549086},"on_screen":true,"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXRadioButton","text":"DEV (docker)","depth":2,"bounds":{"left":0.34906915,"top":1.0,"width":0.0787899,"height":-0.042298436},"on_screen":true,"role_description":"radio button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Close Tab","depth":3,"bounds":{"left":0.35106382,"top":1.0,"width":0.005319149,"height":-0.04549086},"on_screen":true,"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXRadioButton","text":"APP (-zsh)","depth":2,"bounds":{"left":0.42785904,"top":1.0,"width":0.07862367,"height":-0.042298436},"on_screen":true,"role_description":"radio button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Close Tab","depth":3,"bounds":{"left":0.42985374,"top":1.0,"width":0.005319149,"height":-0.04549086},"on_screen":true,"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXRadioButton","text":"-zsh","depth":2,"bounds":{"left":0.5064827,"top":1.0,"width":0.07862367,"height":-0.042298436},"on_screen":true,"role_description":"radio button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Close Tab","depth":3,"bounds":{"left":0.5084774,"top":1.0,"width":0.005319149,"height":-0.04549086},"on_screen":true,"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXRadioButton","text":"screenpipe\"","depth":2,"bounds":{"left":0.5851064,"top":1.0,"width":0.07862367,"height":-0.042298436},"on_screen":true,"role_description":"radio button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Close Tab","depth":3,"bounds":{"left":0.58710104,"top":1.0,"width":0.005319149,"height":-0.04549086},"on_screen":true,"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXRadioButton","text":"-zsh","depth":2,"bounds":{"left":0.66373,"top":1.0,"width":0.07862367,"height":-0.042298436},"on_screen":true,"role_description":"radio button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Close Tab","depth":3,"bounds":{"left":0.66572475,"top":1.0,"width":0.005319149,"height":-0.04549086},"on_screen":true,"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"⌥⌘1","depth":1,"bounds":{"left":0.7287234,"top":1.0,"width":0.01861702,"height":-0.023144484},"on_screen":true,"automation_id":"_NS:8","role_description":"text"},{"role":"AXStaticText","text":"DEV (docker)","depth":1,"bounds":{"left":0.49534574,"top":1.0,"width":0.029920213,"height":-0.02394259},"on_screen":true,"role_description":"text"}]...
|
-2034367797190539635
|
4792628979174108435
|
click
|
accessibility
|
NULL
|
Last login: Thu May 7 09:44:56 on ttys006
Poetry Last login: Thu May 7 09:44:56 on ttys006
Poetry could not find a pyproject.toml file in /Users/lukas/jiminny/app or its parents
Poetry could not find a pyproject.toml file in /Users/lukas/jiminny/app or its parents
lukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/jiminny/app (JY-20773-fix-automated-reports-user-pilot-tracking) $ dev
root@docker_lamp_1:/home/jiminny# php artisan crm:sync-opportunity --teamId=2 --from='2026-05-01 00:00:00'
Syncing opportunity for Hubspot
Your HubSpot account has become disconnected. Please login to Jiminny to reconnect. skipping...
root@docker_lamp_1:/home/jiminny# php artisan jiminny:token-info -A 1499 -R
----------------------------------------------------------------------------------------------------
access_token => CNeR-JHgMxIZQlNQMl8kQEwrAgwACAkUAhIJBB4BAQEDBxiCiYwCIN7Y_Qwo0qwCMhTnG549n-YtNuc1jgj-2AsLPSmw3DoyQlNQMl8kQEwrAiUACBkGawEFThwBARIBAQEEATEEAQEBAQEBAQEBAQUBEggBAQEBAYlCFPAsBNZxoDp5kAcRyeBlQoE5SM7DSgNuYTFSAFoAYABo3tj9DHAAeAA
----------------------------------------------------------------------------------------------------
access_token_expires_at => 2026-05-07 11:41:20
----------------------------------------------------------------------------------------------------
refresh_token => d5ab04e2-2109-4c0b-b513-8cba1dd54371
----------------------------------------------------------------------------------------------------
refresh_token_expires_at =>
----------------------------------------------------------------------------------------------------
root@docker_lamp_1:/home/jiminny# php artisan crm:sync-opportunity --teamId=2 --from='2026-05-01 00:00:00'
Syncing opportunity for Hubspot
Syncing opportunities modified since 2026-05-01 00:00:00...
Synced 6 opportunities.
root@docker_lamp_1:/home/jiminny#
DOCKER
Close Tab
DEV (docker)
Close Tab
APP (-zsh)
Close Tab
-zsh
Close Tab
screenpipe"
Close Tab
-zsh
Close Tab
⌥⌘1
DEV (docker)...
|
2719
|
NULL
|
NULL
|
NULL
|
|
2721
|
111
|
31
|
2026-05-07T11:36:55.402857+00:00
|
/Users/lukas/.screenpipe/data/data/2026-05-07/1778 /Users/lukas/.screenpipe/data/data/2026-05-07/1778153815402_m1.jpg...
|
iTerm2
|
DEV (docker)
|
True
|
NULL
|
monitor_1
|
NULL
|
NULL
|
NULL
|
NULL
|
Last login: Thu May 7 09:44:56 on ttys006
Poetry Last login: Thu May 7 09:44:56 on ttys006
Poetry could not find a pyproject.toml file in /Users/lukas/jiminny/app or its parents
Poetry could not find a pyproject.toml file in /Users/lukas/jiminny/app or its parents
lukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/jiminny/app (JY-20773-fix-automated-reports-user-pilot-tracking) $ dev
root@docker_lamp_1:/home/jiminny# php artisan crm:sync-opportunity --teamId=2 --from='2026-05-01 00:00:00'
Syncing opportunity for Hubspot
Your HubSpot account has become disconnected. Please login to Jiminny to reconnect. skipping...
root@docker_lamp_1:/home/jiminny# php artisan jiminny:token-info -A 1499 -R
----------------------------------------------------------------------------------------------------
access_token => CNeR-JHgMxIZQlNQMl8kQEwrAgwACAkUAhIJBB4BAQEDBxiCiYwCIN7Y_Qwo0qwCMhTnG549n-YtNuc1jgj-2AsLPSmw3DoyQlNQMl8kQEwrAiUACBkGawEFThwBARIBAQEEATEEAQEBAQEBAQEBAQUBEggBAQEBAYlCFPAsBNZxoDp5kAcRyeBlQoE5SM7DSgNuYTFSAFoAYABo3tj9DHAAeAA
----------------------------------------------------------------------------------------------------
access_token_expires_at => 2026-05-07 11:41:20
----------------------------------------------------------------------------------------------------
refresh_token => d5ab04e2-2109-4c0b-b513-8cba1dd54371
----------------------------------------------------------------------------------------------------
refresh_token_expires_at =>
----------------------------------------------------------------------------------------------------
root@docker_lamp_1:/home/jiminny# php artisan crm:sync-opportunity --teamId=2 --from='2026-05-01 00:00:00'
Syncing opportunity for Hubspot
Syncing opportunities modified since 2026-05-01 00:00:00...
Synced 6 opportunities.
root@docker_lamp_1:/home/jiminny#...
|
[{"role":"AXTextArea","text [{"role":"AXTextArea","text":"Last login: Thu May 7 09:44:56 on ttys006\n\nPoetry could not find a pyproject.toml file in /Users/lukas/jiminny/app or its parents\n\nPoetry could not find a pyproject.toml file in /Users/lukas/jiminny/app or its parents\nlukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/jiminny/app (JY-20773-fix-automated-reports-user-pilot-tracking) $ dev\nroot@docker_lamp_1:/home/jiminny# php artisan crm:sync-opportunity --teamId=2 --from='2026-05-01 00:00:00'\nSyncing opportunity for Hubspot\nYour HubSpot account has become disconnected. Please login to Jiminny to reconnect. skipping...\nroot@docker_lamp_1:/home/jiminny# php artisan jiminny:token-info -A 1499 -R\n----------------------------------------------------------------------------------------------------\naccess_token => CNeR-JHgMxIZQlNQMl8kQEwrAgwACAkUAhIJBB4BAQEDBxiCiYwCIN7Y_Qwo0qwCMhTnG549n-YtNuc1jgj-2AsLPSmw3DoyQlNQMl8kQEwrAiUACBkGawEFThwBARIBAQEEATEEAQEBAQEBAQEBAQUBEggBAQEBAYlCFPAsBNZxoDp5kAcRyeBlQoE5SM7DSgNuYTFSAFoAYABo3tj9DHAAeAA\n----------------------------------------------------------------------------------------------------\naccess_token_expires_at => 2026-05-07 11:41:20\n----------------------------------------------------------------------------------------------------\nrefresh_token => d5ab04e2-2109-4c0b-b513-8cba1dd54371\n----------------------------------------------------------------------------------------------------\nrefresh_token_expires_at => \n----------------------------------------------------------------------------------------------------\nroot@docker_lamp_1:/home/jiminny# php artisan crm:sync-opportunity --teamId=2 --from='2026-05-01 00:00:00'\nSyncing opportunity for Hubspot\nSyncing opportunities modified since 2026-05-01 00:00:00...\nSynced 6 opportunities.\nroot@docker_lamp_1:/home/jiminny#","depth":4,"bounds":{"left":0.0,"top":0.08777778,"width":1.0,"height":0.9122222},"on_screen":true,"lines":[{"char_start":0,"char_count":43,"bounds":{"left":0.0034722222,"top":0.08777778,"width":0.23888889,"height":0.02}},{"char_start":43,"char_count":1,"bounds":{"left":0.0034722222,"top":0.107777774,"width":0.0055555557,"height":0.02}},{"char_start":44,"char_count":87,"bounds":{"left":0.0034722222,"top":0.12777779,"width":0.48333332,"height":0.02}},{"char_start":131,"char_count":1,"bounds":{"left":0.0034722222,"top":0.14777778,"width":0.0055555557,"height":0.02}},{"char_start":132,"char_count":87,"bounds":{"left":0.0034722222,"top":0.16777778,"width":0.48333332,"height":0.02}},{"char_start":219,"char_count":114,"bounds":{"left":0.0034722222,"top":0.18777777,"width":0.6333333,"height":0.02}},{"char_start":333,"char_count":107,"bounds":{"left":0.0034722222,"top":0.20777778,"width":0.59444445,"height":0.02}},{"char_start":440,"char_count":32,"bounds":{"left":0.0034722222,"top":0.22777778,"width":0.17777778,"height":0.02}},{"char_start":472,"char_count":1300,"bounds":{"left":0.0034722222,"top":0.24777777,"width":0.18333334,"height":0.34}}],"value":"Last login: Thu May 7 09:44:56 on ttys006\n\nPoetry could not find a pyproject.toml file in /Users/lukas/jiminny/app or its parents\n\nPoetry could not find a pyproject.toml file in /Users/lukas/jiminny/app or its parents\nlukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/jiminny/app (JY-20773-fix-automated-reports-user-pilot-tracking) $ dev\nroot@docker_lamp_1:/home/jiminny# php artisan crm:sync-opportunity --teamId=2 --from='2026-05-01 00:00:00'\nSyncing opportunity for Hubspot\nYour HubSpot account has become disconnected. Please login to Jiminny to reconnect. skipping...\nroot@docker_lamp_1:/home/jiminny# php artisan jiminny:token-info -A 1499 -R\n----------------------------------------------------------------------------------------------------\naccess_token => CNeR-JHgMxIZQlNQMl8kQEwrAgwACAkUAhIJBB4BAQEDBxiCiYwCIN7Y_Qwo0qwCMhTnG549n-YtNuc1jgj-2AsLPSmw3DoyQlNQMl8kQEwrAiUACBkGawEFThwBARIBAQEEATEEAQEBAQEBAQEBAQUBEggBAQEBAYlCFPAsBNZxoDp5kAcRyeBlQoE5SM7DSgNuYTFSAFoAYABo3tj9DHAAeAA\n----------------------------------------------------------------------------------------------------\naccess_token_expires_at => 2026-05-07 11:41:20\n----------------------------------------------------------------------------------------------------\nrefresh_token => d5ab04e2-2109-4c0b-b513-8cba1dd54371\n----------------------------------------------------------------------------------------------------\nrefresh_token_expires_at => \n----------------------------------------------------------------------------------------------------\nroot@docker_lamp_1:/home/jiminny# php artisan crm:sync-opportunity --teamId=2 --from='2026-05-01 00:00:00'\nSyncing opportunity for Hubspot\nSyncing opportunities modified since 2026-05-01 00:00:00...\nSynced 6 opportunities.\nroot@docker_lamp_1:/home/jiminny#","is_focused":true}]...
|
-4754226629612360853
|
5820575594117490707
|
click
|
accessibility
|
NULL
|
Last login: Thu May 7 09:44:56 on ttys006
Poetry Last login: Thu May 7 09:44:56 on ttys006
Poetry could not find a pyproject.toml file in /Users/lukas/jiminny/app or its parents
Poetry could not find a pyproject.toml file in /Users/lukas/jiminny/app or its parents
lukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/jiminny/app (JY-20773-fix-automated-reports-user-pilot-tracking) $ dev
root@docker_lamp_1:/home/jiminny# php artisan crm:sync-opportunity --teamId=2 --from='2026-05-01 00:00:00'
Syncing opportunity for Hubspot
Your HubSpot account has become disconnected. Please login to Jiminny to reconnect. skipping...
root@docker_lamp_1:/home/jiminny# php artisan jiminny:token-info -A 1499 -R
----------------------------------------------------------------------------------------------------
access_token => CNeR-JHgMxIZQlNQMl8kQEwrAgwACAkUAhIJBB4BAQEDBxiCiYwCIN7Y_Qwo0qwCMhTnG549n-YtNuc1jgj-2AsLPSmw3DoyQlNQMl8kQEwrAiUACBkGawEFThwBARIBAQEEATEEAQEBAQEBAQEBAQUBEggBAQEBAYlCFPAsBNZxoDp5kAcRyeBlQoE5SM7DSgNuYTFSAFoAYABo3tj9DHAAeAA
----------------------------------------------------------------------------------------------------
access_token_expires_at => 2026-05-07 11:41:20
----------------------------------------------------------------------------------------------------
refresh_token => d5ab04e2-2109-4c0b-b513-8cba1dd54371
----------------------------------------------------------------------------------------------------
refresh_token_expires_at =>
----------------------------------------------------------------------------------------------------
root@docker_lamp_1:/home/jiminny# php artisan crm:sync-opportunity --teamId=2 --from='2026-05-01 00:00:00'
Syncing opportunity for Hubspot
Syncing opportunities modified since 2026-05-01 00:00:00...
Synced 6 opportunities.
root@docker_lamp_1:/home/jiminny#...
|
NULL
|
NULL
|
NULL
|
NULL
|
|
2722
|
111
|
32
|
2026-05-07T11:37:19.466719+00:00
|
/Users/lukas/.screenpipe/data/data/2026-05-07/1778 /Users/lukas/.screenpipe/data/data/2026-05-07/1778153839466_m1.jpg...
|
iTerm2
|
DEV (docker)
|
True
|
NULL
|
monitor_1
|
NULL
|
NULL
|
NULL
|
NULL
|
Last login: Thu May 7 09:44:56 on ttys006
Poetry Last login: Thu May 7 09:44:56 on ttys006
Poetry could not find a pyproject.toml file in /Users/lukas/jiminny/app or its parents
Poetry could not find a pyproject.toml file in /Users/lukas/jiminny/app or its parents
lukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/jiminny/app (JY-20773-fix-automated-reports-user-pilot-tracking) $ dev
root@docker_lamp_1:/home/jiminny# php artisan crm:sync-opportunity --teamId=2 --from='2026-05-01 00:00:00'
Syncing opportunity for Hubspot
Your HubSpot account has become disconnected. Please login to Jiminny to reconnect. skipping...
root@docker_lamp_1:/home/jiminny# php artisan jiminny:token-info -A 1499 -R
----------------------------------------------------------------------------------------------------
access_token => CNeR-JHgMxIZQlNQMl8kQEwrAgwACAkUAhIJBB4BAQEDBxiCiYwCIN7Y_Qwo0qwCMhTnG549n-YtNuc1jgj-2AsLPSmw3DoyQlNQMl8kQEwrAiUACBkGawEFThwBARIBAQEEATEEAQEBAQEBAQEBAQUBEggBAQEBAYlCFPAsBNZxoDp5kAcRyeBlQoE5SM7DSgNuYTFSAFoAYABo3tj9DHAAeAA
----------------------------------------------------------------------------------------------------
access_token_expires_at => 2026-05-07 11:41:20
----------------------------------------------------------------------------------------------------
refresh_token => d5ab04e2-2109-4c0b-b513-8cba1dd54371
----------------------------------------------------------------------------------------------------
refresh_token_expires_at =>
----------------------------------------------------------------------------------------------------
root@docker_lamp_1:/home/jiminny# php artisan crm:sync-opportunity --teamId=2 --from='2026-05-01 00:00:00'
Syncing opportunity for Hubspot
Syncing opportunities modified since 2026-05-01 00:00:00...
Synced 6 opportunities.
root@docker_lamp_1:/home/jiminny# php artisan optimize:clear && supervisorctl restart all
INFO Clearing cached bootstrap files.
config [PASSWORD_DOTS] 7.40ms DONE
cache [PASSWORD_DOTS] 35.37ms DONE
compiled [PASSWORD_DOTS] 2.98ms DONE
events [PASSWORD_DOTS] 1.70ms DONE
routes [PASSWORD_DOTS] 1.64ms DONE
views [PASSWORD_DOTS] 6.48ms DONE
jiminny-worker-processing-delayed:jiminny-worker-processing-delayed_00: stopped
jiminny-worker-processing-2:jiminny-worker-processing-2_00: stopped
jiminny-worker-processing-3:jiminny-worker-processing-3_00: stopped
jiminny-worker-processing-4:jiminny-worker-processing-4_00: stopped
jiminny-worker-processing-5:jiminny-worker-processing-5_00: stopped
worker-analytics:worker-analytics_00: stopped
worker-crm-update:worker-crm-update_00: stopped
worker-download:worker-download_00: stopped
worker-nudges:worker-nudges_00: stopped
worker-crm-sync:worker-crm-sync_00: stopped
worker:worker_00: stopped
worker-calendar:worker-calendar_00: stopped
worker-emails:worker-emails_00: stopped
jiminny-worker-processing-1:jiminny-worker-processing-1_00: stopped
worker-audio:worker-audio_00: stopped
worker-conferences:worker-conferences_00: stopped
worker-es-update:worker-es-update_00: stopped
artisan-schedule:artisan-schedule_00: stopped
artisan-schedule:artisan-schedule_00: started
jiminny-worker-processing-1:jiminny-worker-processing-1_00: started
jiminny-worker-processing-2:jiminny-worker-processing-2_00: started
jiminny-worker-processing-3:jiminny-worker-processing-3_00: started
jiminny-worker-processing-4:jiminny-worker-processing-4_00: started
jiminny-worker-processing-5:jiminny-worker-processing-5_00: started
jiminny-worker-processing-delayed:jiminny-worker-processing-delayed_00: started
worker:worker_00: started
worker-analytics:worker-analytics_00: started
worker-audio:worker-audio_00: started
worker-calendar:worker-calendar_00: started
worker-conferences:worker-conferences_00: started
worker-crm-sync:worker-crm-sync_00: started
worker-crm-update:worker-crm-update_00: started
worker-download:worker-download_00: started
worker-emails:worker-emails_00: started
worker-es-update:worker-es-update_00: started
worker-nudges:worker-nudges_00: started
root@docker_lamp_1:/home/jiminny#
DOCKER
Close Tab
DEV (docker)
Close Tab
APP (-zsh)
Close Tab
-zsh
Close Tab
screenpipe"
Close Tab
-zsh
Close Tab
⌥⌘1
DEV (docker)...
|
[{"role":"AXTextArea","text [{"role":"AXTextArea","text":"Last login: Thu May 7 09:44:56 on ttys006\n\nPoetry could not find a pyproject.toml file in /Users/lukas/jiminny/app or its parents\n\nPoetry could not find a pyproject.toml file in /Users/lukas/jiminny/app or its parents\nlukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/jiminny/app (JY-20773-fix-automated-reports-user-pilot-tracking) $ dev\nroot@docker_lamp_1:/home/jiminny# php artisan crm:sync-opportunity --teamId=2 --from='2026-05-01 00:00:00'\nSyncing opportunity for Hubspot\nYour HubSpot account has become disconnected. Please login to Jiminny to reconnect. skipping...\nroot@docker_lamp_1:/home/jiminny# php artisan jiminny:token-info -A 1499 -R\n----------------------------------------------------------------------------------------------------\naccess_token => CNeR-JHgMxIZQlNQMl8kQEwrAgwACAkUAhIJBB4BAQEDBxiCiYwCIN7Y_Qwo0qwCMhTnG549n-YtNuc1jgj-2AsLPSmw3DoyQlNQMl8kQEwrAiUACBkGawEFThwBARIBAQEEATEEAQEBAQEBAQEBAQUBEggBAQEBAYlCFPAsBNZxoDp5kAcRyeBlQoE5SM7DSgNuYTFSAFoAYABo3tj9DHAAeAA\n----------------------------------------------------------------------------------------------------\naccess_token_expires_at => 2026-05-07 11:41:20\n----------------------------------------------------------------------------------------------------\nrefresh_token => d5ab04e2-2109-4c0b-b513-8cba1dd54371\n----------------------------------------------------------------------------------------------------\nrefresh_token_expires_at => \n----------------------------------------------------------------------------------------------------\nroot@docker_lamp_1:/home/jiminny# php artisan crm:sync-opportunity --teamId=2 --from='2026-05-01 00:00:00'\nSyncing opportunity for Hubspot\nSyncing opportunities modified since 2026-05-01 00:00:00...\nSynced 6 opportunities.\nroot@docker_lamp_1:/home/jiminny# php artisan optimize:clear && supervisorctl restart all\n\n INFO Clearing cached bootstrap files. \n\n config ............................................................................................................................... 7.40ms DONE\n cache ............................................................................................................................... 35.37ms DONE\n compiled ............................................................................................................................. 2.98ms DONE\n events ............................................................................................................................... 1.70ms DONE\n routes ............................................................................................................................... 1.64ms DONE\n views ................................................................................................................................ 6.48ms DONE\n\njiminny-worker-processing-delayed:jiminny-worker-processing-delayed_00: stopped\njiminny-worker-processing-2:jiminny-worker-processing-2_00: stopped\njiminny-worker-processing-3:jiminny-worker-processing-3_00: stopped\njiminny-worker-processing-4:jiminny-worker-processing-4_00: stopped\njiminny-worker-processing-5:jiminny-worker-processing-5_00: stopped\nworker-analytics:worker-analytics_00: stopped\nworker-crm-update:worker-crm-update_00: stopped\nworker-download:worker-download_00: stopped\nworker-nudges:worker-nudges_00: stopped\nworker-crm-sync:worker-crm-sync_00: stopped\nworker:worker_00: stopped\nworker-calendar:worker-calendar_00: stopped\nworker-emails:worker-emails_00: stopped\njiminny-worker-processing-1:jiminny-worker-processing-1_00: stopped\nworker-audio:worker-audio_00: stopped\nworker-conferences:worker-conferences_00: stopped\nworker-es-update:worker-es-update_00: stopped\nartisan-schedule:artisan-schedule_00: stopped\nartisan-schedule:artisan-schedule_00: started\njiminny-worker-processing-1:jiminny-worker-processing-1_00: started\njiminny-worker-processing-2:jiminny-worker-processing-2_00: started\njiminny-worker-processing-3:jiminny-worker-processing-3_00: started\njiminny-worker-processing-4:jiminny-worker-processing-4_00: started\njiminny-worker-processing-5:jiminny-worker-processing-5_00: started\njiminny-worker-processing-delayed:jiminny-worker-processing-delayed_00: started\nworker:worker_00: started\nworker-analytics:worker-analytics_00: started\nworker-audio:worker-audio_00: started\nworker-calendar:worker-calendar_00: started\nworker-conferences:worker-conferences_00: started\nworker-crm-sync:worker-crm-sync_00: started\nworker-crm-update:worker-crm-update_00: started\nworker-download:worker-download_00: started\nworker-emails:worker-emails_00: started\nworker-es-update:worker-es-update_00: started\nworker-nudges:worker-nudges_00: started\nroot@docker_lamp_1:/home/jiminny#","depth":4,"on_screen":true,"value":"Last login: Thu May 7 09:44:56 on ttys006\n\nPoetry could not find a pyproject.toml file in /Users/lukas/jiminny/app or its parents\n\nPoetry could not find a pyproject.toml file in /Users/lukas/jiminny/app or its parents\nlukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/jiminny/app (JY-20773-fix-automated-reports-user-pilot-tracking) $ dev\nroot@docker_lamp_1:/home/jiminny# php artisan crm:sync-opportunity --teamId=2 --from='2026-05-01 00:00:00'\nSyncing opportunity for Hubspot\nYour HubSpot account has become disconnected. Please login to Jiminny to reconnect. skipping...\nroot@docker_lamp_1:/home/jiminny# php artisan jiminny:token-info -A 1499 -R\n----------------------------------------------------------------------------------------------------\naccess_token => CNeR-JHgMxIZQlNQMl8kQEwrAgwACAkUAhIJBB4BAQEDBxiCiYwCIN7Y_Qwo0qwCMhTnG549n-YtNuc1jgj-2AsLPSmw3DoyQlNQMl8kQEwrAiUACBkGawEFThwBARIBAQEEATEEAQEBAQEBAQEBAQUBEggBAQEBAYlCFPAsBNZxoDp5kAcRyeBlQoE5SM7DSgNuYTFSAFoAYABo3tj9DHAAeAA\n----------------------------------------------------------------------------------------------------\naccess_token_expires_at => 2026-05-07 11:41:20\n----------------------------------------------------------------------------------------------------\nrefresh_token => d5ab04e2-2109-4c0b-b513-8cba1dd54371\n----------------------------------------------------------------------------------------------------\nrefresh_token_expires_at => \n----------------------------------------------------------------------------------------------------\nroot@docker_lamp_1:/home/jiminny# php artisan crm:sync-opportunity --teamId=2 --from='2026-05-01 00:00:00'\nSyncing opportunity for Hubspot\nSyncing opportunities modified since 2026-05-01 00:00:00...\nSynced 6 opportunities.\nroot@docker_lamp_1:/home/jiminny# php artisan optimize:clear && supervisorctl restart all\n\n INFO Clearing cached bootstrap files. \n\n config ............................................................................................................................... 7.40ms DONE\n cache ............................................................................................................................... 35.37ms DONE\n compiled ............................................................................................................................. 2.98ms DONE\n events ............................................................................................................................... 1.70ms DONE\n routes ............................................................................................................................... 1.64ms DONE\n views ................................................................................................................................ 6.48ms DONE\n\njiminny-worker-processing-delayed:jiminny-worker-processing-delayed_00: stopped\njiminny-worker-processing-2:jiminny-worker-processing-2_00: stopped\njiminny-worker-processing-3:jiminny-worker-processing-3_00: stopped\njiminny-worker-processing-4:jiminny-worker-processing-4_00: stopped\njiminny-worker-processing-5:jiminny-worker-processing-5_00: stopped\nworker-analytics:worker-analytics_00: stopped\nworker-crm-update:worker-crm-update_00: stopped\nworker-download:worker-download_00: stopped\nworker-nudges:worker-nudges_00: stopped\nworker-crm-sync:worker-crm-sync_00: stopped\nworker:worker_00: stopped\nworker-calendar:worker-calendar_00: stopped\nworker-emails:worker-emails_00: stopped\njiminny-worker-processing-1:jiminny-worker-processing-1_00: stopped\nworker-audio:worker-audio_00: stopped\nworker-conferences:worker-conferences_00: stopped\nworker-es-update:worker-es-update_00: stopped\nartisan-schedule:artisan-schedule_00: stopped\nartisan-schedule:artisan-schedule_00: started\njiminny-worker-processing-1:jiminny-worker-processing-1_00: started\njiminny-worker-processing-2:jiminny-worker-processing-2_00: started\njiminny-worker-processing-3:jiminny-worker-processing-3_00: started\njiminny-worker-processing-4:jiminny-worker-processing-4_00: started\njiminny-worker-processing-5:jiminny-worker-processing-5_00: started\njiminny-worker-processing-delayed:jiminny-worker-processing-delayed_00: started\nworker:worker_00: started\nworker-analytics:worker-analytics_00: started\nworker-audio:worker-audio_00: started\nworker-calendar:worker-calendar_00: started\nworker-conferences:worker-conferences_00: started\nworker-crm-sync:worker-crm-sync_00: started\nworker-crm-update:worker-crm-update_00: started\nworker-download:worker-download_00: started\nworker-emails:worker-emails_00: started\nworker-es-update:worker-es-update_00: started\nworker-nudges:worker-nudges_00: started\nroot@docker_lamp_1:/home/jiminny#","is_focused":true},{"role":"AXRadioButton","text":"DOCKER","depth":2,"bounds":{"left":0.0,"top":0.05888889,"width":0.16458334,"height":0.026666667},"on_screen":true,"role_description":"radio button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Close Tab","depth":3,"bounds":{"left":0.004166667,"top":0.06333333,"width":0.011111111,"height":0.017777778},"on_screen":true,"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXRadioButton","text":"DEV (docker)","depth":2,"bounds":{"left":0.16458334,"top":0.05888889,"width":0.16458334,"height":0.026666667},"on_screen":true,"role_description":"radio button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Close Tab","depth":3,"bounds":{"left":0.16875,"top":0.06333333,"width":0.011111111,"height":0.017777778},"on_screen":true,"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXRadioButton","text":"APP (-zsh)","depth":2,"bounds":{"left":0.32916668,"top":0.05888889,"width":0.16423611,"height":0.026666667},"on_screen":true,"role_description":"radio button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Close Tab","depth":3,"bounds":{"left":0.33333334,"top":0.06333333,"width":0.011111111,"height":0.017777778},"on_screen":true,"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXRadioButton","text":"-zsh","depth":2,"bounds":{"left":0.49340278,"top":0.05888889,"width":0.16423611,"height":0.026666667},"on_screen":true,"role_description":"radio button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Close Tab","depth":3,"bounds":{"left":0.49756944,"top":0.06333333,"width":0.011111111,"height":0.017777778},"on_screen":true,"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXRadioButton","text":"screenpipe\"","depth":2,"bounds":{"left":0.6576389,"top":0.05888889,"width":0.16423611,"height":0.026666667},"on_screen":true,"role_description":"radio button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Close Tab","depth":3,"bounds":{"left":0.66180557,"top":0.06333333,"width":0.011111111,"height":0.017777778},"on_screen":true,"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXRadioButton","text":"-zsh","depth":2,"bounds":{"left":0.821875,"top":0.05888889,"width":0.16423611,"height":0.026666667},"on_screen":true,"role_description":"radio button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Close Tab","depth":3,"bounds":{"left":0.82604164,"top":0.06333333,"width":0.011111111,"height":0.017777778},"on_screen":true,"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"⌥⌘1","depth":1,"bounds":{"left":0.95763886,"top":0.032222223,"width":0.03888889,"height":0.018888889},"on_screen":true,"automation_id":"_NS:8","role_description":"text"},{"role":"AXStaticText","text":"DEV (docker)","depth":1,"bounds":{"left":0.47013888,"top":0.033333335,"width":0.0625,"height":0.017777778},"on_screen":true,"role_description":"text"}]...
|
3360244759534273448
|
1549467769159461127
|
visual_change
|
accessibility
|
NULL
|
Last login: Thu May 7 09:44:56 on ttys006
Poetry Last login: Thu May 7 09:44:56 on ttys006
Poetry could not find a pyproject.toml file in /Users/lukas/jiminny/app or its parents
Poetry could not find a pyproject.toml file in /Users/lukas/jiminny/app or its parents
lukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/jiminny/app (JY-20773-fix-automated-reports-user-pilot-tracking) $ dev
root@docker_lamp_1:/home/jiminny# php artisan crm:sync-opportunity --teamId=2 --from='2026-05-01 00:00:00'
Syncing opportunity for Hubspot
Your HubSpot account has become disconnected. Please login to Jiminny to reconnect. skipping...
root@docker_lamp_1:/home/jiminny# php artisan jiminny:token-info -A 1499 -R
----------------------------------------------------------------------------------------------------
access_token => CNeR-JHgMxIZQlNQMl8kQEwrAgwACAkUAhIJBB4BAQEDBxiCiYwCIN7Y_Qwo0qwCMhTnG549n-YtNuc1jgj-2AsLPSmw3DoyQlNQMl8kQEwrAiUACBkGawEFThwBARIBAQEEATEEAQEBAQEBAQEBAQUBEggBAQEBAYlCFPAsBNZxoDp5kAcRyeBlQoE5SM7DSgNuYTFSAFoAYABo3tj9DHAAeAA
----------------------------------------------------------------------------------------------------
access_token_expires_at => 2026-05-07 11:41:20
----------------------------------------------------------------------------------------------------
refresh_token => d5ab04e2-2109-4c0b-b513-8cba1dd54371
----------------------------------------------------------------------------------------------------
refresh_token_expires_at =>
----------------------------------------------------------------------------------------------------
root@docker_lamp_1:/home/jiminny# php artisan crm:sync-opportunity --teamId=2 --from='2026-05-01 00:00:00'
Syncing opportunity for Hubspot
Syncing opportunities modified since 2026-05-01 00:00:00...
Synced 6 opportunities.
root@docker_lamp_1:/home/jiminny# php artisan optimize:clear && supervisorctl restart all
INFO Clearing cached bootstrap files.
config [PASSWORD_DOTS] 7.40ms DONE
cache [PASSWORD_DOTS] 35.37ms DONE
compiled [PASSWORD_DOTS] 2.98ms DONE
events [PASSWORD_DOTS] 1.70ms DONE
routes [PASSWORD_DOTS] 1.64ms DONE
views [PASSWORD_DOTS] 6.48ms DONE
jiminny-worker-processing-delayed:jiminny-worker-processing-delayed_00: stopped
jiminny-worker-processing-2:jiminny-worker-processing-2_00: stopped
jiminny-worker-processing-3:jiminny-worker-processing-3_00: stopped
jiminny-worker-processing-4:jiminny-worker-processing-4_00: stopped
jiminny-worker-processing-5:jiminny-worker-processing-5_00: stopped
worker-analytics:worker-analytics_00: stopped
worker-crm-update:worker-crm-update_00: stopped
worker-download:worker-download_00: stopped
worker-nudges:worker-nudges_00: stopped
worker-crm-sync:worker-crm-sync_00: stopped
worker:worker_00: stopped
worker-calendar:worker-calendar_00: stopped
worker-emails:worker-emails_00: stopped
jiminny-worker-processing-1:jiminny-worker-processing-1_00: stopped
worker-audio:worker-audio_00: stopped
worker-conferences:worker-conferences_00: stopped
worker-es-update:worker-es-update_00: stopped
artisan-schedule:artisan-schedule_00: stopped
artisan-schedule:artisan-schedule_00: started
jiminny-worker-processing-1:jiminny-worker-processing-1_00: started
jiminny-worker-processing-2:jiminny-worker-processing-2_00: started
jiminny-worker-processing-3:jiminny-worker-processing-3_00: started
jiminny-worker-processing-4:jiminny-worker-processing-4_00: started
jiminny-worker-processing-5:jiminny-worker-processing-5_00: started
jiminny-worker-processing-delayed:jiminny-worker-processing-delayed_00: started
worker:worker_00: started
worker-analytics:worker-analytics_00: started
worker-audio:worker-audio_00: started
worker-calendar:worker-calendar_00: started
worker-conferences:worker-conferences_00: started
worker-crm-sync:worker-crm-sync_00: started
worker-crm-update:worker-crm-update_00: started
worker-download:worker-download_00: started
worker-emails:worker-emails_00: started
worker-es-update:worker-es-update_00: started
worker-nudges:worker-nudges_00: started
root@docker_lamp_1:/home/jiminny#
DOCKER
Close Tab
DEV (docker)
Close Tab
APP (-zsh)
Close Tab
-zsh
Close Tab
screenpipe"
Close Tab
-zsh
Close Tab
⌥⌘1
DEV (docker)...
|
2721
|
NULL
|
NULL
|
NULL
|
|
2723
|
112
|
33
|
2026-05-07T11:37:25.915934+00:00
|
/Users/lukas/.screenpipe/data/data/2026-05-07/1778 /Users/lukas/.screenpipe/data/data/2026-05-07/1778153845915_m2.jpg...
|
iTerm2
|
DEV (docker)
|
True
|
NULL
|
monitor_2
|
NULL
|
NULL
|
NULL
|
NULL
|
Last login: Thu May 7 09:44:56 on ttys006
Poetry Last login: Thu May 7 09:44:56 on ttys006
Poetry could not find a pyproject.toml file in /Users/lukas/jiminny/app or its parents
Poetry could not find a pyproject.toml file in /Users/lukas/jiminny/app or its parents
lukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/jiminny/app (JY-20773-fix-automated-reports-user-pilot-tracking) $ dev
root@docker_lamp_1:/home/jiminny# php artisan crm:sync-opportunity --teamId=2 --from='2026-05-01 00:00:00'
Syncing opportunity for Hubspot
Your HubSpot account has become disconnected. Please login to Jiminny to reconnect. skipping...
root@docker_lamp_1:/home/jiminny# php artisan jiminny:token-info -A 1499 -R
----------------------------------------------------------------------------------------------------
access_token => CNeR-JHgMxIZQlNQMl8kQEwrAgwACAkUAhIJBB4BAQEDBxiCiYwCIN7Y_Qwo0qwCMhTnG549n-YtNuc1jgj-2AsLPSmw3DoyQlNQMl8kQEwrAiUACBkGawEFThwBARIBAQEEATEEAQEBAQEBAQEBAQUBEggBAQEBAYlCFPAsBNZxoDp5kAcRyeBlQoE5SM7DSgNuYTFSAFoAYABo3tj9DHAAeAA
----------------------------------------------------------------------------------------------------
access_token_expires_at => 2026-05-07 11:41:20
----------------------------------------------------------------------------------------------------
refresh_token => d5ab04e2-2109-4c0b-b513-8cba1dd54371
----------------------------------------------------------------------------------------------------
refresh_token_expires_at =>
----------------------------------------------------------------------------------------------------
root@docker_lamp_1:/home/jiminny# php artisan crm:sync-opportunity --teamId=2 --from='2026-05-01 00:00:00'
Syncing opportunity for Hubspot
Syncing opportunities modified since 2026-05-01 00:00:00...
Synced 6 opportunities.
root@docker_lamp_1:/home/jiminny# php artisan optimize:clear && supervisorctl restart all
INFO Clearing cached bootstrap files.
config [PASSWORD_DOTS] 7.40ms DONE
cache [PASSWORD_DOTS] 35.37ms DONE
compiled [PASSWORD_DOTS] 2.98ms DONE
events [PASSWORD_DOTS] 1.70ms DONE
routes [PASSWORD_DOTS] 1.64ms DONE
views [PASSWORD_DOTS] 6.48ms DONE
jiminny-worker-processing-delayed:jiminny-worker-processing-delayed_00: stopped
jiminny-worker-processing-2:jiminny-worker-processing-2_00: stopped
jiminny-worker-processing-3:jiminny-worker-processing-3_00: stopped
jiminny-worker-processing-4:jiminny-worker-processing-4_00: stopped
jiminny-worker-processing-5:jiminny-worker-processing-5_00: stopped
worker-analytics:worker-analytics_00: stopped
worker-crm-update:worker-crm-update_00: stopped
worker-download:worker-download_00: stopped
worker-nudges:worker-nudges_00: stopped
worker-crm-sync:worker-crm-sync_00: stopped
worker:worker_00: stopped
worker-calendar:worker-calendar_00: stopped
worker-emails:worker-emails_00: stopped
jiminny-worker-processing-1:jiminny-worker-processing-1_00: stopped
worker-audio:worker-audio_00: stopped
worker-conferences:worker-conferences_00: stopped
worker-es-update:worker-es-update_00: stopped
artisan-schedule:artisan-schedule_00: stopped
artisan-schedule:artisan-schedule_00: started
jiminny-worker-processing-1:jiminny-worker-processing-1_00: started
jiminny-worker-processing-2:jiminny-worker-processing-2_00: started
jiminny-worker-processing-3:jiminny-worker-processing-3_00: started
jiminny-worker-processing-4:jiminny-worker-processing-4_00: started
jiminny-worker-processing-5:jiminny-worker-processing-5_00: started
jiminny-worker-processing-delayed:jiminny-worker-processing-delayed_00: started
worker:worker_00: started
worker-analytics:worker-analytics_00: started
worker-audio:worker-audio_00: started
worker-calendar:worker-calendar_00: started
worker-conferences:worker-conferences_00: started
worker-crm-sync:worker-crm-sync_00: started
worker-crm-update:worker-crm-update_00: started
worker-download:worker-download_00: started
worker-emails:worker-emails_00: started
worker-es-update:worker-es-update_00: started
worker-nudges:worker-nudges_00: started
root@docker_lamp_1:/home/jiminny#
DOCKER
Close Tab
DEV (docker)
Close Tab
APP (-zsh)
Close Tab
-zsh
Close Tab
screenpipe"
Close Tab
-zsh
Close Tab
⌥⌘1
DEV (docker)...
|
[{"role":"AXTextArea","text [{"role":"AXTextArea","text":"Last login: Thu May 7 09:44:56 on ttys006\n\nPoetry could not find a pyproject.toml file in /Users/lukas/jiminny/app or its parents\n\nPoetry could not find a pyproject.toml file in /Users/lukas/jiminny/app or its parents\nlukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/jiminny/app (JY-20773-fix-automated-reports-user-pilot-tracking) $ dev\nroot@docker_lamp_1:/home/jiminny# php artisan crm:sync-opportunity --teamId=2 --from='2026-05-01 00:00:00'\nSyncing opportunity for Hubspot\nYour HubSpot account has become disconnected. Please login to Jiminny to reconnect. skipping...\nroot@docker_lamp_1:/home/jiminny# php artisan jiminny:token-info -A 1499 -R\n----------------------------------------------------------------------------------------------------\naccess_token => CNeR-JHgMxIZQlNQMl8kQEwrAgwACAkUAhIJBB4BAQEDBxiCiYwCIN7Y_Qwo0qwCMhTnG549n-YtNuc1jgj-2AsLPSmw3DoyQlNQMl8kQEwrAiUACBkGawEFThwBARIBAQEEATEEAQEBAQEBAQEBAQUBEggBAQEBAYlCFPAsBNZxoDp5kAcRyeBlQoE5SM7DSgNuYTFSAFoAYABo3tj9DHAAeAA\n----------------------------------------------------------------------------------------------------\naccess_token_expires_at => 2026-05-07 11:41:20\n----------------------------------------------------------------------------------------------------\nrefresh_token => d5ab04e2-2109-4c0b-b513-8cba1dd54371\n----------------------------------------------------------------------------------------------------\nrefresh_token_expires_at => \n----------------------------------------------------------------------------------------------------\nroot@docker_lamp_1:/home/jiminny# php artisan crm:sync-opportunity --teamId=2 --from='2026-05-01 00:00:00'\nSyncing opportunity for Hubspot\nSyncing opportunities modified since 2026-05-01 00:00:00...\nSynced 6 opportunities.\nroot@docker_lamp_1:/home/jiminny# php artisan optimize:clear && supervisorctl restart all\n\n INFO Clearing cached bootstrap files. \n\n config ............................................................................................................................... 7.40ms DONE\n cache ............................................................................................................................... 35.37ms DONE\n compiled ............................................................................................................................. 2.98ms DONE\n events ............................................................................................................................... 1.70ms DONE\n routes ............................................................................................................................... 1.64ms DONE\n views ................................................................................................................................ 6.48ms DONE\n\njiminny-worker-processing-delayed:jiminny-worker-processing-delayed_00: stopped\njiminny-worker-processing-2:jiminny-worker-processing-2_00: stopped\njiminny-worker-processing-3:jiminny-worker-processing-3_00: stopped\njiminny-worker-processing-4:jiminny-worker-processing-4_00: stopped\njiminny-worker-processing-5:jiminny-worker-processing-5_00: stopped\nworker-analytics:worker-analytics_00: stopped\nworker-crm-update:worker-crm-update_00: stopped\nworker-download:worker-download_00: stopped\nworker-nudges:worker-nudges_00: stopped\nworker-crm-sync:worker-crm-sync_00: stopped\nworker:worker_00: stopped\nworker-calendar:worker-calendar_00: stopped\nworker-emails:worker-emails_00: stopped\njiminny-worker-processing-1:jiminny-worker-processing-1_00: stopped\nworker-audio:worker-audio_00: stopped\nworker-conferences:worker-conferences_00: stopped\nworker-es-update:worker-es-update_00: stopped\nartisan-schedule:artisan-schedule_00: stopped\nartisan-schedule:artisan-schedule_00: started\njiminny-worker-processing-1:jiminny-worker-processing-1_00: started\njiminny-worker-processing-2:jiminny-worker-processing-2_00: started\njiminny-worker-processing-3:jiminny-worker-processing-3_00: started\njiminny-worker-processing-4:jiminny-worker-processing-4_00: started\njiminny-worker-processing-5:jiminny-worker-processing-5_00: started\njiminny-worker-processing-delayed:jiminny-worker-processing-delayed_00: started\nworker:worker_00: started\nworker-analytics:worker-analytics_00: started\nworker-audio:worker-audio_00: started\nworker-calendar:worker-calendar_00: started\nworker-conferences:worker-conferences_00: started\nworker-crm-sync:worker-crm-sync_00: started\nworker-crm-update:worker-crm-update_00: started\nworker-download:worker-download_00: started\nworker-emails:worker-emails_00: started\nworker-es-update:worker-es-update_00: started\nworker-nudges:worker-nudges_00: started\nroot@docker_lamp_1:/home/jiminny#","depth":4,"bounds":{"left":0.27027926,"top":0.67517954,"width":0.4800532,"height":0.32482046},"on_screen":true,"value":"Last login: Thu May 7 09:44:56 on ttys006\n\nPoetry could not find a pyproject.toml file in /Users/lukas/jiminny/app or its parents\n\nPoetry could not find a pyproject.toml file in /Users/lukas/jiminny/app or its parents\nlukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/jiminny/app (JY-20773-fix-automated-reports-user-pilot-tracking) $ dev\nroot@docker_lamp_1:/home/jiminny# php artisan crm:sync-opportunity --teamId=2 --from='2026-05-01 00:00:00'\nSyncing opportunity for Hubspot\nYour HubSpot account has become disconnected. Please login to Jiminny to reconnect. skipping...\nroot@docker_lamp_1:/home/jiminny# php artisan jiminny:token-info -A 1499 -R\n----------------------------------------------------------------------------------------------------\naccess_token => CNeR-JHgMxIZQlNQMl8kQEwrAgwACAkUAhIJBB4BAQEDBxiCiYwCIN7Y_Qwo0qwCMhTnG549n-YtNuc1jgj-2AsLPSmw3DoyQlNQMl8kQEwrAiUACBkGawEFThwBARIBAQEEATEEAQEBAQEBAQEBAQUBEggBAQEBAYlCFPAsBNZxoDp5kAcRyeBlQoE5SM7DSgNuYTFSAFoAYABo3tj9DHAAeAA\n----------------------------------------------------------------------------------------------------\naccess_token_expires_at => 2026-05-07 11:41:20\n----------------------------------------------------------------------------------------------------\nrefresh_token => d5ab04e2-2109-4c0b-b513-8cba1dd54371\n----------------------------------------------------------------------------------------------------\nrefresh_token_expires_at => \n----------------------------------------------------------------------------------------------------\nroot@docker_lamp_1:/home/jiminny# php artisan crm:sync-opportunity --teamId=2 --from='2026-05-01 00:00:00'\nSyncing opportunity for Hubspot\nSyncing opportunities modified since 2026-05-01 00:00:00...\nSynced 6 opportunities.\nroot@docker_lamp_1:/home/jiminny# php artisan optimize:clear && supervisorctl restart all\n\n INFO Clearing cached bootstrap files. \n\n config ............................................................................................................................... 7.40ms DONE\n cache ............................................................................................................................... 35.37ms DONE\n compiled ............................................................................................................................. 2.98ms DONE\n events ............................................................................................................................... 1.70ms DONE\n routes ............................................................................................................................... 1.64ms DONE\n views ................................................................................................................................ 6.48ms DONE\n\njiminny-worker-processing-delayed:jiminny-worker-processing-delayed_00: stopped\njiminny-worker-processing-2:jiminny-worker-processing-2_00: stopped\njiminny-worker-processing-3:jiminny-worker-processing-3_00: stopped\njiminny-worker-processing-4:jiminny-worker-processing-4_00: stopped\njiminny-worker-processing-5:jiminny-worker-processing-5_00: stopped\nworker-analytics:worker-analytics_00: stopped\nworker-crm-update:worker-crm-update_00: stopped\nworker-download:worker-download_00: stopped\nworker-nudges:worker-nudges_00: stopped\nworker-crm-sync:worker-crm-sync_00: stopped\nworker:worker_00: stopped\nworker-calendar:worker-calendar_00: stopped\nworker-emails:worker-emails_00: stopped\njiminny-worker-processing-1:jiminny-worker-processing-1_00: stopped\nworker-audio:worker-audio_00: stopped\nworker-conferences:worker-conferences_00: stopped\nworker-es-update:worker-es-update_00: stopped\nartisan-schedule:artisan-schedule_00: stopped\nartisan-schedule:artisan-schedule_00: started\njiminny-worker-processing-1:jiminny-worker-processing-1_00: started\njiminny-worker-processing-2:jiminny-worker-processing-2_00: started\njiminny-worker-processing-3:jiminny-worker-processing-3_00: started\njiminny-worker-processing-4:jiminny-worker-processing-4_00: started\njiminny-worker-processing-5:jiminny-worker-processing-5_00: started\njiminny-worker-processing-delayed:jiminny-worker-processing-delayed_00: started\nworker:worker_00: started\nworker-analytics:worker-analytics_00: started\nworker-audio:worker-audio_00: started\nworker-calendar:worker-calendar_00: started\nworker-conferences:worker-conferences_00: started\nworker-crm-sync:worker-crm-sync_00: started\nworker-crm-update:worker-crm-update_00: started\nworker-download:worker-download_00: started\nworker-emails:worker-emails_00: started\nworker-es-update:worker-es-update_00: started\nworker-nudges:worker-nudges_00: started\nroot@docker_lamp_1:/home/jiminny#","is_focused":true},{"role":"AXRadioButton","text":"DOCKER","depth":2,"bounds":{"left":0.27027926,"top":1.0,"width":0.0787899,"height":-0.042298436},"on_screen":true,"role_description":"radio button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Close Tab","depth":3,"bounds":{"left":0.27227393,"top":1.0,"width":0.005319149,"height":-0.04549086},"on_screen":true,"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXRadioButton","text":"DEV (docker)","depth":2,"bounds":{"left":0.34906915,"top":1.0,"width":0.0787899,"height":-0.042298436},"on_screen":true,"role_description":"radio button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Close Tab","depth":3,"bounds":{"left":0.35106382,"top":1.0,"width":0.005319149,"height":-0.04549086},"on_screen":true,"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXRadioButton","text":"APP (-zsh)","depth":2,"bounds":{"left":0.42785904,"top":1.0,"width":0.07862367,"height":-0.042298436},"on_screen":true,"role_description":"radio button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Close Tab","depth":3,"bounds":{"left":0.42985374,"top":1.0,"width":0.005319149,"height":-0.04549086},"on_screen":true,"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXRadioButton","text":"-zsh","depth":2,"bounds":{"left":0.5064827,"top":1.0,"width":0.07862367,"height":-0.042298436},"on_screen":true,"role_description":"radio button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Close Tab","depth":3,"bounds":{"left":0.5084774,"top":1.0,"width":0.005319149,"height":-0.04549086},"on_screen":true,"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXRadioButton","text":"screenpipe\"","depth":2,"bounds":{"left":0.5851064,"top":1.0,"width":0.07862367,"height":-0.042298436},"on_screen":true,"role_description":"radio button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Close Tab","depth":3,"bounds":{"left":0.58710104,"top":1.0,"width":0.005319149,"height":-0.04549086},"on_screen":true,"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXRadioButton","text":"-zsh","depth":2,"bounds":{"left":0.66373,"top":1.0,"width":0.07862367,"height":-0.042298436},"on_screen":true,"role_description":"radio button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Close Tab","depth":3,"bounds":{"left":0.66572475,"top":1.0,"width":0.005319149,"height":-0.04549086},"on_screen":true,"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"⌥⌘1","depth":1,"bounds":{"left":0.7287234,"top":1.0,"width":0.01861702,"height":-0.023144484},"on_screen":true,"automation_id":"_NS:8","role_description":"text"},{"role":"AXStaticText","text":"DEV (docker)","depth":1,"bounds":{"left":0.49534574,"top":1.0,"width":0.029920213,"height":-0.02394259},"on_screen":true,"role_description":"text"}]...
|
3360244759534273448
|
1549467769159461127
|
idle
|
accessibility
|
NULL
|
Last login: Thu May 7 09:44:56 on ttys006
Poetry Last login: Thu May 7 09:44:56 on ttys006
Poetry could not find a pyproject.toml file in /Users/lukas/jiminny/app or its parents
Poetry could not find a pyproject.toml file in /Users/lukas/jiminny/app or its parents
lukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/jiminny/app (JY-20773-fix-automated-reports-user-pilot-tracking) $ dev
root@docker_lamp_1:/home/jiminny# php artisan crm:sync-opportunity --teamId=2 --from='2026-05-01 00:00:00'
Syncing opportunity for Hubspot
Your HubSpot account has become disconnected. Please login to Jiminny to reconnect. skipping...
root@docker_lamp_1:/home/jiminny# php artisan jiminny:token-info -A 1499 -R
----------------------------------------------------------------------------------------------------
access_token => CNeR-JHgMxIZQlNQMl8kQEwrAgwACAkUAhIJBB4BAQEDBxiCiYwCIN7Y_Qwo0qwCMhTnG549n-YtNuc1jgj-2AsLPSmw3DoyQlNQMl8kQEwrAiUACBkGawEFThwBARIBAQEEATEEAQEBAQEBAQEBAQUBEggBAQEBAYlCFPAsBNZxoDp5kAcRyeBlQoE5SM7DSgNuYTFSAFoAYABo3tj9DHAAeAA
----------------------------------------------------------------------------------------------------
access_token_expires_at => 2026-05-07 11:41:20
----------------------------------------------------------------------------------------------------
refresh_token => d5ab04e2-2109-4c0b-b513-8cba1dd54371
----------------------------------------------------------------------------------------------------
refresh_token_expires_at =>
----------------------------------------------------------------------------------------------------
root@docker_lamp_1:/home/jiminny# php artisan crm:sync-opportunity --teamId=2 --from='2026-05-01 00:00:00'
Syncing opportunity for Hubspot
Syncing opportunities modified since 2026-05-01 00:00:00...
Synced 6 opportunities.
root@docker_lamp_1:/home/jiminny# php artisan optimize:clear && supervisorctl restart all
INFO Clearing cached bootstrap files.
config [PASSWORD_DOTS] 7.40ms DONE
cache [PASSWORD_DOTS] 35.37ms DONE
compiled [PASSWORD_DOTS] 2.98ms DONE
events [PASSWORD_DOTS] 1.70ms DONE
routes [PASSWORD_DOTS] 1.64ms DONE
views [PASSWORD_DOTS] 6.48ms DONE
jiminny-worker-processing-delayed:jiminny-worker-processing-delayed_00: stopped
jiminny-worker-processing-2:jiminny-worker-processing-2_00: stopped
jiminny-worker-processing-3:jiminny-worker-processing-3_00: stopped
jiminny-worker-processing-4:jiminny-worker-processing-4_00: stopped
jiminny-worker-processing-5:jiminny-worker-processing-5_00: stopped
worker-analytics:worker-analytics_00: stopped
worker-crm-update:worker-crm-update_00: stopped
worker-download:worker-download_00: stopped
worker-nudges:worker-nudges_00: stopped
worker-crm-sync:worker-crm-sync_00: stopped
worker:worker_00: stopped
worker-calendar:worker-calendar_00: stopped
worker-emails:worker-emails_00: stopped
jiminny-worker-processing-1:jiminny-worker-processing-1_00: stopped
worker-audio:worker-audio_00: stopped
worker-conferences:worker-conferences_00: stopped
worker-es-update:worker-es-update_00: stopped
artisan-schedule:artisan-schedule_00: stopped
artisan-schedule:artisan-schedule_00: started
jiminny-worker-processing-1:jiminny-worker-processing-1_00: started
jiminny-worker-processing-2:jiminny-worker-processing-2_00: started
jiminny-worker-processing-3:jiminny-worker-processing-3_00: started
jiminny-worker-processing-4:jiminny-worker-processing-4_00: started
jiminny-worker-processing-5:jiminny-worker-processing-5_00: started
jiminny-worker-processing-delayed:jiminny-worker-processing-delayed_00: started
worker:worker_00: started
worker-analytics:worker-analytics_00: started
worker-audio:worker-audio_00: started
worker-calendar:worker-calendar_00: started
worker-conferences:worker-conferences_00: started
worker-crm-sync:worker-crm-sync_00: started
worker-crm-update:worker-crm-update_00: started
worker-download:worker-download_00: started
worker-emails:worker-emails_00: started
worker-es-update:worker-es-update_00: started
worker-nudges:worker-nudges_00: started
root@docker_lamp_1:/home/jiminny#
DOCKER
Close Tab
DEV (docker)
Close Tab
APP (-zsh)
Close Tab
-zsh
Close Tab
screenpipe"
Close Tab
-zsh
Close Tab
⌥⌘1
DEV (docker)...
|
NULL
|
NULL
|
NULL
|
NULL
|
|
2724
|
111
|
33
|
2026-05-07T11:37:38.776199+00:00
|
/Users/lukas/.screenpipe/data/data/2026-05-07/1778 /Users/lukas/.screenpipe/data/data/2026-05-07/1778153858776_m1.jpg...
|
PhpStorm
|
faVsco.js – custom.log
|
True
|
NULL
|
monitor_1
|
NULL
|
NULL
|
NULL
|
NULL
|
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
Editor for custom.log
Sync Changes
Hide This Notification
Code changed:
Hide
2
60
Previous Highlighted Error
Next Highlighted Error
<?php
declare(strict_types=1);
namespace Jiminny\Services\Crm\Hubspot;
use HubSpot\Client\Crm\Deals\ApiException as DealApiException;
use HubSpot\Client\Crm\Contacts\ApiException as ContactApiException;
use HubSpot\Client\Crm\Companies\ApiException as CompanyApiException;
use HubSpot\Client\Crm\Contacts\Model\SimplePublicObjectWithAssociations as ContactsWithAssociations;
use HubSpot\Client\Crm\Companies\Model\SimplePublicObjectWithAssociations as CompaniesWithAssociations;
use HubSpot\Client\Crm\Deals\Model\SimplePublicObjectWithAssociations as DealWithAssociations;
use HubSpot\Client\Crm\Objects\Model\SimplePublicObjectInput;
use HubSpot\Client\Crm\Objects\Model\SimplePublicObjectWithAssociations as ObjectWithAssociations;
use HubSpot\Client\Crm\Pipelines\Model\Error;
use HubSpot\Client\Crm\Pipelines\Model\PipelineStage;
use HubSpot\Client\Crm\Properties\Model\Property;
use HubSpot\Discovery\Discovery;
use Jiminny\Component\Utility\Service\ProviderRateLimiter;
use Jiminny\Exceptions\CrmException;
use Jiminny\Exceptions\RateLimitException;
use Jiminny\Exceptions\SocialAccountTokenInvalidException;
use Jiminny\Jobs\Crm\NoteObject;
use Jiminny\Models\Crm\Field;
use Jiminny\Services\Crm\BaseClient;
use Jiminny\Services\Crm\Hubspot\DTO\Response\Owner;
use Jiminny\Services\SocialAccountService;
use SevenShores\Hubspot\Exceptions\BadRequest;
use SevenShores\Hubspot\Exceptions\HubspotException;
use SevenShores\Hubspot\Factory;
use SevenShores\Hubspot\Http\Response;
use Jiminny\Services\Crm\Hubspot\Pagination\HubspotPaginationService;
use Throwable;
/**
* @phpstan-type CrmFieldOption array{id:string, label:string, value?:string}
*/
class Client extends BaseClient implements HubspotClientInterface
{
public const string MIN_API_VERSION = '2';
public const string BASE_URL = '[URL_WITH_CREDENTIALS] T
* @param callable(): T $apiCall
* @return T
*
* @throws RateLimitException
*/
private function executeRequest(callable $apiCall)
{
if (! $this->rateLimiter->canMakeRequest($this->config)) {
$retryAfter = $this->rateLimiter->requestAvailableIn($this->config);
$this->log->warning('[Hubspot] Rate limit exceeded, deferring request', [
'team_id' => $this->config->team_id,
'config_id' => $this->config->getId(),
'retry_after' => $retryAfter,
]);
throw new RateLimitException(
'Hubspot rate limit reached for configuration ' . $this->config->getId(),
$retryAfter,
);
}
$this->rateLimiter->incrementRequestCount($this->config);
try {
return $apiCall();
} catch (Throwable $e) {
if ($this->isHubspotRateLimit($e)) {
$retryAfter = $this->parseRetryAfter($e);
$this->log->warning('[Hubspot] Received 429 from API', [
'team_id' => $this->config->team_id,
'config_id' => $this->config->getId(),
'retry_after' => $retryAfter,
'reason' => $e->getMessage(),
]);
throw new RateLimitException('Hubspot returned 429', $retryAfter, $e);
}
throw $e;
}
}
private function isHubspotRateLimit(Throwable $e): bool
{
return method_exists($e, 'getCode') && (int) $e->getCode() === 429;
}
private function parseRetryAfter(Throwable $e): int
{
if (method_exists($e, 'getResponseHeaders')) {
$headers = $e->getResponseHeaders() ?: [];
$value = $headers['Retry-After'] ?? $headers['retry-after'] ?? null;
if (is_array($value)) {
$value = $value[0] ?? null;
}
if (is_numeric($value)) {
return (int) $value;
}
}
return 10;
}
public function getMinimumApiVersion(): string
{
return self::MIN_API_VERSION;
}
public function getInstance(): Factory
{
return new Factory([
'key' => $this->accessToken,
'oauth2' => true,
'base_url' => $this->baseUrl,
]);
}
public function getNewInstance(): Discovery
{
return \HubSpot\Factory::createWithAccessToken($this->accessToken);
}
/**
* Secondly and daily limits for Hubspot API
*
* Product Tier: Free & Starter | Professional & Enterprise | API add-on (any tier)
* Burst: 100/10 seconds | 150/10 seconds | 200/10 seconds
* Daily: 250,000 | 500,000 | 1,000,000
*
* Official documentation states: The search endpoints are rate limited to five requests per second.
* Since with 5 RPS were still hitting secondly rate limits we lowered it to 4
*/
public function getPaginatedData(array $payload, string $type, int $offset = 0): array
{
$total = 0;
$lastId = null;
$rows = [];
foreach ($this->getPaginatedDataGenerator($payload, $type, $offset, $total, $lastId) as $row) {
$rows[] = $row;
}
return ['results' => $rows, 'total' => $total, 'last_record' => $lastId];
}
/**
* @throws HubspotException
* @throws SocialAccountTokenInvalidException
* @throws BadRequest
*/
public function getPaginatedDataGenerator(
array $payload,
string $type,
int $offset = 0,
int &$total = 0,
?string &$lastRecordId = null
): \Generator {
return $this->paginationService->getPaginatedDataGenerator(
$this,
$payload,
$type,
$offset,
$total,
$lastRecordId
);
}
/**
* @throws DealApiException
* @throws CrmException
*/
public function getOpportunityById(string $crmId, array $fields): array
{
try {
$deal = $this->executeRequest(fn () => $this->getNewInstance()->crm()->deals()->basicApi()->getById(
$crmId,
implode(',', $fields),
'companies,contacts'
));
} catch (DealApiException $e) {
$this->log->info('[Hubspot] Failed to fetch opportunity', [
'crm_id' => $crmId,
'reason' => $e->getMessage(),
]);
throw $e;
}
if (! $deal instanceof DealWithAssociations) {
throw new CrmException('Deal not found');
}
return [
'id' => $deal->getId(),
'properties' => $deal->getProperties(),
'associations' => $deal->getAssociations(),
];
}
/**
* Generic batch read method for HubSpot objects
*
* @param string $objectType The object type ('deals', 'companies', 'contacts')
* @param array<string> $crmIds Array of HubSpot object IDs (max 100)
* @param array<string> $fields Array of property names to fetch
*
* @return array<string, array> Array keyed by CRM ID with object data
*/
private function batchReadObjects(string $objectType, array $crmIds, array $fields): array
{
if (empty($crmIds)) {
return [];
}
$this->validateBatchSize($objectType, $crmIds);
$this->ensureValidToken();
try {
$batchConfig = $this->createBatchConfiguration($objectType);
$batchReadRequest = $this->prepareBatchRequest($batchConfig, $crmIds, $fields);
$response = $batchConfig['api']->read($batchReadRequest);
$this->validateApiResponse($response, $objectType);
$results = $this->processApiResults($response);
$this->logBatchResults($objectType, $crmIds, $results);
return $results;
} catch (\Throwable $e) {
$this->handleBatchError($e, $objectType, $crmIds);
}
}
private function validateBatchSize(string $objectType, array $crmIds): void
{
if (count($crmIds) > 100) {
throw new \InvalidArgumentException("Batch size cannot exceed 100 {$objectType}");
}
}
private function createBatchConfiguration(string $objectType): array
{
$configurations = [
'deals' => [
'batchReadRequest' => new \HubSpot\Client\Crm\Deals\Model\BatchReadInputSimplePublicObjectId(),
'inputClass' => \HubSpot\Client\Crm\Deals\Model\SimplePublicObjectId::class,
'api' => $this->getNewInstance()->crm()->deals()->batchApi(),
],
'companies' => [
'batchReadRequest' => new \HubSpot\Client\Crm\Companies\Model\BatchReadInputSimplePublicObjectId(),
'inputClass' => \HubSpot\Client\Crm\Companies\Model\SimplePublicObjectId::class,
'api' => $this->getNewInstance()->crm()->companies()->batchApi(),
],
'contacts' => [
'batchReadRequest' => new \HubSpot\Client\Crm\Contacts\Model\BatchReadInputSimplePublicObjectId(),
'inputClass' => \HubSpot\Client\Crm\Contacts\Model\SimplePublicObjectId::class,
'api' => $this->getNewInstance()->crm()->contacts()->batchApi(),
],
];
if (! isset($configurations[$objectType])) {
throw new \InvalidArgumentException("Unsupported object type: {$objectType}");
}
return $configurations[$objectType];
}
private function prepareBatchRequest(array $batchConfig, array $crmIds, array $fields): object
{
$batchReadRequest = $batchConfig['batchReadRequest'];
$inputClass = $batchConfig['inputClass'];
$inputs = array_map(function ($crmId) use ($inputClass) {
$input = new $inputClass();
$input->setId($crmId);
return $input;
}, $crmIds);
$batchReadRequest->setInputs($inputs);
$batchReadRequest->setProperties($fields);
return $batchReadRequest;
}
private function validateApiResponse($response, string $objectType): void
{
if (! $response) {
throw new CrmException("HubSpot API returned null response for {$objectType} batch read");
}
}
private function processApiResults($response): array
{
$results = [];
$responseResults = $response->getResults();
if ($responseResults) {
foreach ($responseResults as $object) {
if ($object && $object->getId()) {
$results[$object->getId()] = [
'id' => $object->getId(),
'properties' => $object->getProperties() ?: [],
];
}
}
}
return $results;
}
private function logBatchResults(string $objectType, array $crmIds, array $results): void
{
$this->log->info("[HubSpot] Batch fetched {$objectType}", [
'requested_count' => count($crmIds),
'returned_count' => count($results),
'crm_ids' => $crmIds,
]);
}
private function handleBatchError(\Throwable $e, string $objectType, array $crmIds): void
{
$errorMessage = $e->getMessage() ?: 'Unknown error';
$errorTrace = $e->getTraceAsString() ?: 'No trace available';
$this->log->error("[HubSpot] Failed to batch fetch {$objectType}", [
'crm_ids' => $crmIds,
'error' => $errorMessage,
'trace' => $errorTrace,
]);
throw new CrmException("Failed to batch fetch {$objectType}: " . $errorMessage);
}
/**
* Batch read multiple opportunities by their CRM IDs
*
* @param array<string> $crmIds Array of HubSpot deal IDs (max 100)
* @param array<string> $fields Array of property names to fetch
*
* @return array<string, array> Array keyed by CRM ID with opportunity data
*/
public function getOpportunitiesByIds(array $crmIds, array $fields): array
{
return $this->batchReadObjects('deals', $crmIds, $fields);
}
/**
* Batch read multiple companies by their CRM IDs
*
* @param array<string> $crmIds Array of HubSpot company IDs (max 100)
* @param array<string> $fields Array of property names to fetch
*
* @return array<string, array> Array keyed by CRM ID with company data
*/
public function getCompaniesByIds(array $crmIds, array $fields): array
{
return $this->batchReadObjects('companies', $crmIds, $fields);
}
/**
* Batch read multiple contacts by their CRM IDs
*
* @param array<string> $crmIds Array of HubSpot contact IDs (max 100)
* @param array<string> $fields Array of property names to fetch
*
* @return array<string, array> Array keyed by CRM ID with contact data
*/
public function getContactsByIds(array $crmIds, array $fields): array
{
return $this->batchReadObjects('contacts', $crmIds, $fields);
}
/**
* @throws CompanyApiException
* @throws CrmException
*/
public function getAccountById(string $crmId, array $fields): array
{
try {
$company = $this->getNewInstance()->crm()->companies()->basicApi()->getById(
$crmId,
implode(',', $fields),
);
} catch (CompanyApiException $e) {
$this->log->info('[Hubspot] Failed to fetch account', [
'crm_id' => $crmId,
'reason' => $e->getMessage(),
]);
throw $e;
}
if (! $company instanceof CompaniesWithAssociations) {
throw new CrmException('Account not found');
}
return [
'id' => $company->getId(),
'properties' => $company->getProperties(),
];
}
/**
* @throws ContactApiException
* @throws CrmException
*/
public function getContactById(string $crmId, array $fields): array
{
try {
$contact = $this->getNewInstance()->crm()->contacts()->basicApi()->getById(
$crmId,
implode(',', $fields)
);
} catch (ContactApiException $e) {
$this->log->info('[Hubspot] Failed to fetch contact', [
'crm_id' => $crmId,
'reason' => $e->getMessage(),
]);
throw $e;
}
if (! $contact instanceof ContactsWithAssociations) {
throw new CrmException('Contact not found');
}
return [
'id' => $contact->getId(),
'properties' => $contact->getProperties(),
];
}
/**
* This is email search request that Hubspot offers as GET (more generous quota)
*/
public function getContactByEmail(string $email, array $fields = []): array
{
try {
$contact = $this->getNewInstance()->crm()->contacts()->basicApi()->getById(
$email,
implode(',', $fields),
null,
false,
'email'
);
return [
'id' => $contact->getId(),
'properties' => $contact->getProperties(),
];
} catch (ContactApiException $e) {
$this->log->info('[Hubspot] Failed to fetch contact', [
'email' => $email,
'reason' => $e->getMessage(),
]);
return [];
}
}
/**
* @throws CrmException
*/
public function fetchProperty(string $objectType, string $propertyId): Property
{
$result = $this->getNewInstance()->crm()->properties()->coreApi()->getByName($objectType, $propertyId);
if (! $result instanceof Property) {
$this->log->error('[Hubspot] Failed to fetch property', [
'object_type' => $objectType,
'property_id' => $propertyId,
'reason' => $result->getMessage(),
]);
throw new CrmException('Failed to fetch property');
}
return $result;
}
/**
* @return array<CrmFieldOption>
*/
public function fetchPropertyOptions(string $objectType, string $propertyId): array
{
/** @var array<CrmFieldOption> */
return $this->fetchProperty($objectType, $propertyId)->getOptions();
}
/**
* @return array<array{id:string, label:string, deleted:bool}>
*/
public function fetchCallDispositions(): array
{
/** @var Response $response */
$response = $this->getInstance()->engagements()->getCallDispositions();
/**
* @var array<array{
* id:string,
* label:string,
* deleted: bool
* }>
*/
return $response->toArray();
}
/**
* @return array<CrmFieldOption>
*/
public function fetchOpportunityPipelineStages(): array
{
$stages = [];
$apiResponse = $this->getNewInstance()->crm()->pipelines()->pipelinesApi()->getAll('deals');
if ($apiResponse instanceof Error) {
$this->log->error('[Hubspot] Failed to fetch opportunity pipelines', [
'reason' => $apiResponse->getMessage(),
]);
return [];
}
foreach ($apiResponse->getResults() as $pipeline) {
$pipelineStages = array_map(
static function (PipelineStage $stage) {
return [
'id' => $stage->getId(),
'label' => $stage->getLabel(),
];
},
$pipeline->getStages()
);
$stages = array_merge($stages, $pipelineStages);
}
return $stages;
}
public function fetchOpportunityPipelines(): array
{
$pipelines = [];
try {
$apiResponse = $this->makeRequest('/crm/v3/pipelines/deals');
} catch (\Exception $e) {
$this->log->info('[Hubspot] Failed to fetch opportunity pipelines', [
'reason' => $e->getMessage(),
]);
return [];
}
$response = $apiResponse->toArray();
foreach ($response['results'] as $pipeline) {
$pipelines[] = [
'id' => $pipeline['id'],
'label' => $pipeline['label'],
];
}
return $pipelines;
}
/**
* @return array<CrmFieldOption>
*/
public function fetchMeetingOutcomeFieldOptions(Field $field): array
{
return $field->getCrmProviderId() === 'meetingOutcome'
? $this->fetchMeetingOutcomeTypes()
: $this->fetchCallActivityTypes();
}
public function fetchMeetingOutcomeTypes(): array
{
return $this->extractMeetingTypeOptions(
'[URL_WITH_CREDENTIALS] Response $response */
$response = $this->getInstance()
->getClient()
->request('GET', $endpoint);
/**
* @var array<array{
* value: string,
* label: string,
* displayOrder: int
* }> $optionData
*/
$optionData = $response->toArray()['options'] ?? [];
$options = [];
foreach ($optionData as $item) {
$options[] = [
'id' => $item['value'],
'value' => $item['value'],
'label' => $item['label'],
'display_order' => $item['displayOrder'],
];
}
return $options;
}
/**
* @return array<CrmFieldOption>
*/
public function fetchDispositionFieldOptions(): array
{
$options = [];
$dispositions = $this->fetchCallDispositions();
foreach ($dispositions as $disposition) {
if ($disposition['deleted'] !== false) {
continue;
}
$option['value'] = $disposition['id'];
$option['id'] = $disposition['id'];
$option['label'] = $disposition['label'];
$options[] = $option;
}
return $options;
}
/**
* @return array<CrmFieldOption>
*/
public function fetchOpportunityFieldOptions(Field $field): array
{
if ($field->isStageField()) {
return $this->fetchOpportunityPipelineStages();
}
if ($field->isPipelineField()) {
return $this->fetchOpportunityPipelines();
}
return $this->fetchPropertyOptions('deals', $field->getCrmProviderId());
}
/**
* @throws BadRequest
* @throws HubspotException
*/
public function makeRequest(string $endpoint, $method = 'GET', $payload = [], ?string $queryString = null)
{
$endpoint = self::BASE_URL . $endpoint;
if ($method === 'GET') {
$response = $this->getInstance()->getClient()?->request(
method: $method,
endpoint: $endpoint,
query_string: $queryString
);
} else {
$response = $this->getInstance()->getClient()->request($method, $endpoint, [
'json' => ($payload),
]);
}
$max = $response->getHeaderLine('X-HubSpot-RateLimit-Max'); // "110"
$remaining = $response->getHeaderLine('X-HubSpot-RateLimit-Remaining'); // "109"
$interval = $response->getHeaderLine('X-HubSpot-RateLimit-Interval-Milliseconds'); // "10000"
$body = json_decode((string) $response->getBody(), true);
\Illuminate\Support\Facades\Log::channel('custom_channel')->info('$max ' . PHP_EOL . print_r($max, true));
\Illuminate\Support\Facades\Log::channel('custom_channel')->info('$remaining ' . PHP_EOL . print_r($remaining, true));
\Illuminate\Support\Facades\Log::channel('custom_channel')->info('$interval ' . PHP_EOL . print_r($interval, true));
\Illuminate\Support\Facades\Log::channel('custom_channel')->info('$body ' . PHP_EOL . print_r($body, true));
return $response;
}
/**
* @throws BadRequest
* @throws HubspotException
*/
public function createMeeting(array $payload): Response
{
$endpoint = '/crm/v3/objects/meetings';
return $this->makeRequest($endpoint, 'POST', $payload);
}
/**
* @throws BadRequest
* @throws HubspotException
*/
public function updateMeeting(string $meetingId, array $payload): Response
{
$endpoint = '/crm/v3/objects/meetings/' . $meetingId;
return $this->makeRequest($endpoint, 'PATCH', $payload);
}
/**
* @throws \Exception
*/
public function createNote(
string $body,
string $ownerId,
int $timestamp,
string $objectId,
NoteObject $noteObject
): ?string {
try {
$noteInput = new SimplePublicObjectInput([
'properties' => [
'hs_note_body' => $body,
'hubspot_owner_id' => $ownerId,
'hs_timestamp' => $timestamp,
],
]);
// Create note
$note = $this->getNewInstance()->crm()->objects()->basicApi()->create('note', $noteInput);
$this->getNewInstance()->crm()->objects()->associationsApi()->create(
'note',
$note->getId(),
$this->getNoteObject($noteObject),
$objectId,
$this->getNoteAssociationType($noteObject),
);
return $note->getId();
} catch (\Exception $e) {
$this->log->error('[Hubspot] Failed to create note', [
'objectId' => $objectId,
'noteObject' => $noteObject->getObjectType(),
'reason' => $e->getMessage(),
]);
\Sentry::captureException($e);
}
return null;
}
public function updateEngagement(string $objectId, array $engagement, array $metadata): void
{
$this->getInstance()->engagements()->update($objectId, $engagement, $metadata);
}
public function getEngagementData(string $engagementId): array
{
$engagement = $this->getInstance()->engagements()->get($engagementId);
return $engagement->toArray();
}
public function createEngagement(array $engagement, array $associations, array $metadata): Response
{
return $this->getInstance()
->engagements()
->create($engagement, $associations, $metadata);
}
public function isUnauthorizedException(\Exception $e): bool
{
// Check for specific HubSpot API exception types first
if ($e instanceof BadRequest) {
// BadRequest can contain 401 status codes
return $e->getCode() === 401;
}
// Check for HTTP client exceptions with status codes
if ($e instanceof \GuzzleHttp\Exception\RequestException && $e->hasResponse()) {
$response = $e->getResponse();
if ($response !== null) {
return $response->getStatusCode() === 401;
}
}
// Check for Guzzle HTTP exceptions
if ($e instanceof \GuzzleHttp\Exception\ClientException) {
return $e->getCode() === 401;
}
// Fallback to string matching as last resort, but be more specific
$message = strtolower($e->getMessage());
return str_contains($message, '401 unauthorized') ||
str_contains($message, 'http 401') ||
str_contains($message, 'status code 401') ||
(preg_match('/\b401\b/', $message) && str_contains($message, 'unauthorized'));
}
/**
* Validates and refreshes the access token if needed before API requests.
* This ensures long-running processes don't fail due to token expiration.
*
* @throws SocialAccountTokenInvalidException
*/
public function ensureValidToken(): void
{
if ($this->oauthAccount === null) {
return;
}
$newToken = $this->tokenManager->ensureValidToken($this->oauthAccount);
if ($newToken !== null) {
$this->accessToken = $newToken;
}
}
public function getConfig()
{
return $this->config;
}
// returns only active (archived=false)
public function getOwners(): array
{
return $this->getNewInstance()->crm()->owners()->getAll();
}
/**
* @param bool $archived
*
* @return array<Owner>|[]
*/
public function getOwnersArchived(bool $archived = true): array
{
$endpoint = '/crm/v3/owners';
$queryParams = [
'archived' => $archived ? 'true' : 'false',
];
$queryString = http_build_query($queryParams);
$owners = [];
try {
$response = $this->makeRequest(endpoint: $endpoint, queryString: $queryString);
$responseData = $response?->toArray();
foreach ($responseData['results'] as $result) {
try {
$owners[] = Owner::create($result);
} catch (Throwable $e) {
$this->log->error('[HubSpot] Failed to process owner data', [
'result' => $result,
'error' => $e->getMessage(),
]);
continue;
}
}
} catch (Throwable $e) {
$this->log->error('HubSpot] Failed to fetch owners', [
'archived' => $archived,
'error' => $e->getMessage(),
]);
return [];
}
return $owners;
}
public function getMeeting(string $engagementId): ObjectWithAssociations
{
return $this->getNewInstance()->crm()->objects()->basicApi()
->getById('meeting', $engagementId, null, 'contact,company,deal');
}
public function deleteEngagement(string $engagementId): void
{
$this->getInstance()->engagements()->delete((int) $engagementId);
}
public function getAssociationsData(array $ids, string $fromObject, string $toObject): array
{
$associationData = [];
$idChunks = array_chunk($ids, self::ASSOCIATIONS_BATCH_SIZE_LIMIT);
foreach ($idChunks as $idChunk) {
try {
$batchInput = new \HubSpot\Client\Crm\Associations\Model\BatchInputPublicObjectId();
$batchInput->setInputs(array_map(function ($id) {
$publicObjectId = new \HubSpot\Client\Crm\Associations\Model\PublicObjectId();
$publicObjectId->setId($id);
return $publicObjectId;
}, $idChunk));
$associatedObjectsData = $this
->getNewInstance()
->crm()
->associations()
->batchApi()
->read($fromObject, $toObject, $batchInput);
if ($associatedObjectsData instanceof \HubSpot\Client\Crm\Associations\Model\BatchResponsePublicAssociationMulti) {
foreach ($associatedObjectsData->getResults() as $association) {
$from = $association->getFrom()->getId();
$toAssociations = $association->getTo();
if (! empty($toAssociations)) {
$associationData[$from] = array_map(function ($item) {
return $item->getId();
}, $toAssociations);
}
}
}
} catch (\Exception $e) {
$this->log->error('[Hubspot] Failed to fetch associations', [
'from_object' => $fromObject,
'to_object' => $toObject,
'reason' => $e->getMessage(),
]);
}
}
return $associationData;
}
/**
* @throws \Exception
*/
private function getNoteAssociationType(NoteObject $noteObject): string
{
return match($noteObject) {
NoteObject::Opportunity => 'note_to_deal',
NoteObject::Lead, NoteObject::Contact => 'note_to_contact', // or 'note_to_lead' if your portal supports it
NoteObject::Account => 'note_to_company',
NoteObject::Call, NoteObject::Event => throw new \Exception('Not supported'),
};
}
/**
* @throws \Exception
*/
private function getNoteObject(NoteObject $noteObject): string
{
return match($noteObject) {
NoteObject::Opportunity => 'deal',
NoteObject::Lead, NoteObject::Contact => 'contact',
NoteObject::Account => 'company',
NoteObject::Call, NoteObject::Event => throw new \Exception('Not supported'),
};
}
public function addAssociations(string $objectType, string $associationType, array $payload): Response
{
$endpoint = "/crm/v4/associations/$objectType/$associationType/batch/create";
return $this->makeRequest($endpoint, 'POST', $payload);
}
public function removeAssociations(string $objectType, string $associationType, array $payload): Response
{
$endpoint = "/crm/v4/associations/$objectType/$associationType/batch/archive";
return $this->makeRequest($endpoint, 'POST', $payload);
}
}
Project
Project
New File or Directory…
Expand Selected
Collapse All
Options
Hide...
|
[{"role":"AXButton","text" [{"role":"AXButton","text":"Project: faVsco.js, menu","depth":5,"on_screen":true,"help_text":"~/jiminny/app","role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"master, menu","depth":5,"on_screen":true,"help_text":"Git Branch: master","role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Start Listening for PHP Debug Connections","depth":5,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"AskJiminnyReportActivityServiceTest","depth":6,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Run 'AskJiminnyReportActivityServiceTest'","depth":6,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Debug 'AskJiminnyReportActivityServiceTest'","depth":6,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"More Actions","depth":6,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"JetBrains AI","depth":5,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Search Everywhere","depth":5,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"IDE and Project Settings","depth":5,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Sync Changes","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.018055556,"height":0.026666667},"on_screen":false,"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Hide This Notification","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.018055556,"height":0.026666667},"on_screen":false,"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Code changed:","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.088194445,"height":0.027777778},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Hide","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.018055556,"height":0.026666667},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXTextArea","text":"Editor for custom.log","depth":4,"on_screen":true,"role_description":"text entry area","is_enabled":true,"is_focused":true,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Sync Changes","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.018055556,"height":0.026666667},"on_screen":false,"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Hide This Notification","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.018055556,"height":0.026666667},"on_screen":false,"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Code changed:","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.088194445,"height":0.027777778},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Hide","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.018055556,"height":0.026666667},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"2","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.016666668,"height":0.02111111},"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"60","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.021527778,"height":0.02111111},"on_screen":false,"role_description":"text"},{"role":"AXButton","text":"Previous Highlighted Error","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.015277778,"height":0.025555555},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Next Highlighted Error","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.014583333,"height":0.025555555},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXTextArea","text":"<?php\n\ndeclare(strict_types=1);\n\nnamespace Jiminny\\Services\\Crm\\Hubspot;\n\nuse HubSpot\\Client\\Crm\\Deals\\ApiException as DealApiException;\nuse HubSpot\\Client\\Crm\\Contacts\\ApiException as ContactApiException;\nuse HubSpot\\Client\\Crm\\Companies\\ApiException as CompanyApiException;\nuse HubSpot\\Client\\Crm\\Contacts\\Model\\SimplePublicObjectWithAssociations as ContactsWithAssociations;\nuse HubSpot\\Client\\Crm\\Companies\\Model\\SimplePublicObjectWithAssociations as CompaniesWithAssociations;\nuse HubSpot\\Client\\Crm\\Deals\\Model\\SimplePublicObjectWithAssociations as DealWithAssociations;\nuse HubSpot\\Client\\Crm\\Objects\\Model\\SimplePublicObjectInput;\nuse HubSpot\\Client\\Crm\\Objects\\Model\\SimplePublicObjectWithAssociations as ObjectWithAssociations;\nuse HubSpot\\Client\\Crm\\Pipelines\\Model\\Error;\nuse HubSpot\\Client\\Crm\\Pipelines\\Model\\PipelineStage;\nuse HubSpot\\Client\\Crm\\Properties\\Model\\Property;\nuse HubSpot\\Discovery\\Discovery;\nuse Jiminny\\Component\\Utility\\Service\\ProviderRateLimiter;\nuse Jiminny\\Exceptions\\CrmException;\nuse Jiminny\\Exceptions\\RateLimitException;\nuse Jiminny\\Exceptions\\SocialAccountTokenInvalidException;\nuse Jiminny\\Jobs\\Crm\\NoteObject;\nuse Jiminny\\Models\\Crm\\Field;\nuse Jiminny\\Services\\Crm\\BaseClient;\nuse Jiminny\\Services\\Crm\\Hubspot\\DTO\\Response\\Owner;\nuse Jiminny\\Services\\SocialAccountService;\nuse SevenShores\\Hubspot\\Exceptions\\BadRequest;\nuse SevenShores\\Hubspot\\Exceptions\\HubspotException;\nuse SevenShores\\Hubspot\\Factory;\nuse SevenShores\\Hubspot\\Http\\Response;\nuse Jiminny\\Services\\Crm\\Hubspot\\Pagination\\HubspotPaginationService;\nuse Throwable;\n\n/**\n * @phpstan-type CrmFieldOption array{id:string, label:string, value?:string}\n */\nclass Client extends BaseClient implements HubspotClientInterface\n{\n public const string MIN_API_VERSION = '2';\n\n public const string BASE_URL = 'https://api.hubapi.com';\n\n public const int ASSOCIATIONS_BATCH_SIZE_LIMIT = 1000;\n\n private HubspotPaginationService $paginationService;\n private HubspotTokenManager $tokenManager;\n private ProviderRateLimiter $rateLimiter;\n\n public function __construct(\n SocialAccountService $socialAccountService,\n HubspotPaginationService $paginationService,\n HubspotTokenManager $tokenManager,\n ProviderRateLimiter $rateLimiter,\n ) {\n parent::__construct($socialAccountService);\n $this->paginationService = $paginationService;\n $this->tokenManager = $tokenManager;\n $this->rateLimiter = $rateLimiter;\n\n $this->setBaseUrl(self::BASE_URL);\n $this->setVersion(self::MIN_API_VERSION);\n }\n\n /**\n * Single entry point for every HubSpot API call. Enforces the per-portal\n * rate limit configured in the rate_limits table (morphed to the current\n * Configuration) and reacts to a real 429 from HubSpot by translating it\n * into a RateLimitException carrying Retry-After.\n *\n * Wrap any outbound HubSpot call (SDK or raw HTTP) like:\n *\n * $this->executeRequest(fn () => $this->getNewInstance()->crm()->...);\n *\n * @template T\n * @param callable(): T $apiCall\n * @return T\n *\n * @throws RateLimitException\n */\n private function executeRequest(callable $apiCall)\n {\n if (! $this->rateLimiter->canMakeRequest($this->config)) {\n $retryAfter = $this->rateLimiter->requestAvailableIn($this->config);\n\n $this->log->warning('[Hubspot] Rate limit exceeded, deferring request', [\n 'team_id' => $this->config->team_id,\n 'config_id' => $this->config->getId(),\n 'retry_after' => $retryAfter,\n ]);\n\n throw new RateLimitException(\n 'Hubspot rate limit reached for configuration ' . $this->config->getId(),\n $retryAfter,\n );\n }\n\n $this->rateLimiter->incrementRequestCount($this->config);\n\n try {\n return $apiCall();\n } catch (Throwable $e) {\n if ($this->isHubspotRateLimit($e)) {\n $retryAfter = $this->parseRetryAfter($e);\n\n $this->log->warning('[Hubspot] Received 429 from API', [\n 'team_id' => $this->config->team_id,\n 'config_id' => $this->config->getId(),\n 'retry_after' => $retryAfter,\n 'reason' => $e->getMessage(),\n ]);\n\n throw new RateLimitException('Hubspot returned 429', $retryAfter, $e);\n }\n\n throw $e;\n }\n }\n\n private function isHubspotRateLimit(Throwable $e): bool\n {\n return method_exists($e, 'getCode') && (int) $e->getCode() === 429;\n }\n\n private function parseRetryAfter(Throwable $e): int\n {\n if (method_exists($e, 'getResponseHeaders')) {\n $headers = $e->getResponseHeaders() ?: [];\n $value = $headers['Retry-After'] ?? $headers['retry-after'] ?? null;\n if (is_array($value)) {\n $value = $value[0] ?? null;\n }\n if (is_numeric($value)) {\n return (int) $value;\n }\n }\n\n return 10;\n }\n\n public function getMinimumApiVersion(): string\n {\n return self::MIN_API_VERSION;\n }\n\n public function getInstance(): Factory\n {\n return new Factory([\n 'key' => $this->accessToken,\n 'oauth2' => true,\n 'base_url' => $this->baseUrl,\n ]);\n }\n\n public function getNewInstance(): Discovery\n {\n return \\HubSpot\\Factory::createWithAccessToken($this->accessToken);\n }\n\n /**\n * Secondly and daily limits for Hubspot API\n *\n * Product Tier: Free & Starter | Professional & Enterprise | API add-on (any tier)\n * Burst: 100/10 seconds | 150/10 seconds | 200/10 seconds\n * Daily: 250,000 | 500,000 | 1,000,000\n *\n * Official documentation states: The search endpoints are rate limited to five requests per second.\n * Since with 5 RPS were still hitting secondly rate limits we lowered it to 4\n */\n public function getPaginatedData(array $payload, string $type, int $offset = 0): array\n {\n $total = 0;\n $lastId = null;\n $rows = [];\n foreach ($this->getPaginatedDataGenerator($payload, $type, $offset, $total, $lastId) as $row) {\n $rows[] = $row;\n }\n\n return ['results' => $rows, 'total' => $total, 'last_record' => $lastId];\n }\n\n /**\n * @throws HubspotException\n * @throws SocialAccountTokenInvalidException\n * @throws BadRequest\n */\n public function getPaginatedDataGenerator(\n array $payload,\n string $type,\n int $offset = 0,\n int &$total = 0,\n ?string &$lastRecordId = null\n ): \\Generator {\n return $this->paginationService->getPaginatedDataGenerator(\n $this,\n $payload,\n $type,\n $offset,\n $total,\n $lastRecordId\n );\n }\n\n /**\n * @throws DealApiException\n * @throws CrmException\n */\n public function getOpportunityById(string $crmId, array $fields): array\n {\n try {\n $deal = $this->executeRequest(fn () => $this->getNewInstance()->crm()->deals()->basicApi()->getById(\n $crmId,\n implode(',', $fields),\n 'companies,contacts'\n ));\n } catch (DealApiException $e) {\n $this->log->info('[Hubspot] Failed to fetch opportunity', [\n 'crm_id' => $crmId,\n 'reason' => $e->getMessage(),\n ]);\n\n throw $e;\n }\n\n if (! $deal instanceof DealWithAssociations) {\n throw new CrmException('Deal not found');\n }\n\n return [\n 'id' => $deal->getId(),\n 'properties' => $deal->getProperties(),\n 'associations' => $deal->getAssociations(),\n ];\n }\n\n /**\n * Generic batch read method for HubSpot objects\n *\n * @param string $objectType The object type ('deals', 'companies', 'contacts')\n * @param array<string> $crmIds Array of HubSpot object IDs (max 100)\n * @param array<string> $fields Array of property names to fetch\n *\n * @return array<string, array> Array keyed by CRM ID with object data\n */\n private function batchReadObjects(string $objectType, array $crmIds, array $fields): array\n {\n if (empty($crmIds)) {\n return [];\n }\n\n $this->validateBatchSize($objectType, $crmIds);\n $this->ensureValidToken();\n\n try {\n $batchConfig = $this->createBatchConfiguration($objectType);\n $batchReadRequest = $this->prepareBatchRequest($batchConfig, $crmIds, $fields);\n $response = $batchConfig['api']->read($batchReadRequest);\n\n $this->validateApiResponse($response, $objectType);\n\n $results = $this->processApiResults($response);\n $this->logBatchResults($objectType, $crmIds, $results);\n\n return $results;\n } catch (\\Throwable $e) {\n $this->handleBatchError($e, $objectType, $crmIds);\n }\n }\n\n private function validateBatchSize(string $objectType, array $crmIds): void\n {\n if (count($crmIds) > 100) {\n throw new \\InvalidArgumentException(\"Batch size cannot exceed 100 {$objectType}\");\n }\n }\n\n private function createBatchConfiguration(string $objectType): array\n {\n $configurations = [\n 'deals' => [\n 'batchReadRequest' => new \\HubSpot\\Client\\Crm\\Deals\\Model\\BatchReadInputSimplePublicObjectId(),\n 'inputClass' => \\HubSpot\\Client\\Crm\\Deals\\Model\\SimplePublicObjectId::class,\n 'api' => $this->getNewInstance()->crm()->deals()->batchApi(),\n ],\n 'companies' => [\n 'batchReadRequest' => new \\HubSpot\\Client\\Crm\\Companies\\Model\\BatchReadInputSimplePublicObjectId(),\n 'inputClass' => \\HubSpot\\Client\\Crm\\Companies\\Model\\SimplePublicObjectId::class,\n 'api' => $this->getNewInstance()->crm()->companies()->batchApi(),\n ],\n 'contacts' => [\n 'batchReadRequest' => new \\HubSpot\\Client\\Crm\\Contacts\\Model\\BatchReadInputSimplePublicObjectId(),\n 'inputClass' => \\HubSpot\\Client\\Crm\\Contacts\\Model\\SimplePublicObjectId::class,\n 'api' => $this->getNewInstance()->crm()->contacts()->batchApi(),\n ],\n ];\n\n if (! isset($configurations[$objectType])) {\n throw new \\InvalidArgumentException(\"Unsupported object type: {$objectType}\");\n }\n\n return $configurations[$objectType];\n }\n\n private function prepareBatchRequest(array $batchConfig, array $crmIds, array $fields): object\n {\n $batchReadRequest = $batchConfig['batchReadRequest'];\n $inputClass = $batchConfig['inputClass'];\n\n $inputs = array_map(function ($crmId) use ($inputClass) {\n $input = new $inputClass();\n $input->setId($crmId);\n\n return $input;\n }, $crmIds);\n\n $batchReadRequest->setInputs($inputs);\n $batchReadRequest->setProperties($fields);\n\n return $batchReadRequest;\n }\n\n private function validateApiResponse($response, string $objectType): void\n {\n if (! $response) {\n throw new CrmException(\"HubSpot API returned null response for {$objectType} batch read\");\n }\n }\n\n private function processApiResults($response): array\n {\n $results = [];\n $responseResults = $response->getResults();\n\n if ($responseResults) {\n foreach ($responseResults as $object) {\n if ($object && $object->getId()) {\n $results[$object->getId()] = [\n 'id' => $object->getId(),\n 'properties' => $object->getProperties() ?: [],\n ];\n }\n }\n }\n\n return $results;\n }\n\n private function logBatchResults(string $objectType, array $crmIds, array $results): void\n {\n $this->log->info(\"[HubSpot] Batch fetched {$objectType}\", [\n 'requested_count' => count($crmIds),\n 'returned_count' => count($results),\n 'crm_ids' => $crmIds,\n ]);\n }\n\n private function handleBatchError(\\Throwable $e, string $objectType, array $crmIds): void\n {\n $errorMessage = $e->getMessage() ?: 'Unknown error';\n $errorTrace = $e->getTraceAsString() ?: 'No trace available';\n\n $this->log->error(\"[HubSpot] Failed to batch fetch {$objectType}\", [\n 'crm_ids' => $crmIds,\n 'error' => $errorMessage,\n 'trace' => $errorTrace,\n ]);\n\n throw new CrmException(\"Failed to batch fetch {$objectType}: \" . $errorMessage);\n }\n\n /**\n * Batch read multiple opportunities by their CRM IDs\n *\n * @param array<string> $crmIds Array of HubSpot deal IDs (max 100)\n * @param array<string> $fields Array of property names to fetch\n *\n * @return array<string, array> Array keyed by CRM ID with opportunity data\n */\n public function getOpportunitiesByIds(array $crmIds, array $fields): array\n {\n return $this->batchReadObjects('deals', $crmIds, $fields);\n }\n\n /**\n * Batch read multiple companies by their CRM IDs\n *\n * @param array<string> $crmIds Array of HubSpot company IDs (max 100)\n * @param array<string> $fields Array of property names to fetch\n *\n * @return array<string, array> Array keyed by CRM ID with company data\n */\n public function getCompaniesByIds(array $crmIds, array $fields): array\n {\n return $this->batchReadObjects('companies', $crmIds, $fields);\n }\n\n /**\n * Batch read multiple contacts by their CRM IDs\n *\n * @param array<string> $crmIds Array of HubSpot contact IDs (max 100)\n * @param array<string> $fields Array of property names to fetch\n *\n * @return array<string, array> Array keyed by CRM ID with contact data\n */\n public function getContactsByIds(array $crmIds, array $fields): array\n {\n return $this->batchReadObjects('contacts', $crmIds, $fields);\n }\n\n /**\n * @throws CompanyApiException\n * @throws CrmException\n */\n public function getAccountById(string $crmId, array $fields): array\n {\n try {\n $company = $this->getNewInstance()->crm()->companies()->basicApi()->getById(\n $crmId,\n implode(',', $fields),\n );\n } catch (CompanyApiException $e) {\n $this->log->info('[Hubspot] Failed to fetch account', [\n 'crm_id' => $crmId,\n 'reason' => $e->getMessage(),\n ]);\n\n throw $e;\n }\n\n if (! $company instanceof CompaniesWithAssociations) {\n throw new CrmException('Account not found');\n }\n\n return [\n 'id' => $company->getId(),\n 'properties' => $company->getProperties(),\n ];\n }\n\n /**\n * @throws ContactApiException\n * @throws CrmException\n */\n public function getContactById(string $crmId, array $fields): array\n {\n try {\n $contact = $this->getNewInstance()->crm()->contacts()->basicApi()->getById(\n $crmId,\n implode(',', $fields)\n );\n } catch (ContactApiException $e) {\n $this->log->info('[Hubspot] Failed to fetch contact', [\n 'crm_id' => $crmId,\n 'reason' => $e->getMessage(),\n ]);\n\n throw $e;\n }\n\n if (! $contact instanceof ContactsWithAssociations) {\n throw new CrmException('Contact not found');\n }\n\n return [\n 'id' => $contact->getId(),\n 'properties' => $contact->getProperties(),\n ];\n }\n\n /**\n * This is email search request that Hubspot offers as GET (more generous quota)\n */\n public function getContactByEmail(string $email, array $fields = []): array\n {\n try {\n $contact = $this->getNewInstance()->crm()->contacts()->basicApi()->getById(\n $email,\n implode(',', $fields),\n null,\n false,\n 'email'\n );\n\n return [\n 'id' => $contact->getId(),\n 'properties' => $contact->getProperties(),\n ];\n } catch (ContactApiException $e) {\n $this->log->info('[Hubspot] Failed to fetch contact', [\n 'email' => $email,\n 'reason' => $e->getMessage(),\n ]);\n\n return [];\n }\n }\n\n /**\n * @throws CrmException\n */\n public function fetchProperty(string $objectType, string $propertyId): Property\n {\n $result = $this->getNewInstance()->crm()->properties()->coreApi()->getByName($objectType, $propertyId);\n\n if (! $result instanceof Property) {\n $this->log->error('[Hubspot] Failed to fetch property', [\n 'object_type' => $objectType,\n 'property_id' => $propertyId,\n 'reason' => $result->getMessage(),\n ]);\n\n throw new CrmException('Failed to fetch property');\n }\n\n return $result;\n }\n\n /**\n * @return array<CrmFieldOption>\n */\n public function fetchPropertyOptions(string $objectType, string $propertyId): array\n {\n /** @var array<CrmFieldOption> */\n return $this->fetchProperty($objectType, $propertyId)->getOptions();\n }\n\n /**\n * @return array<array{id:string, label:string, deleted:bool}>\n */\n public function fetchCallDispositions(): array\n {\n /** @var Response $response */\n $response = $this->getInstance()->engagements()->getCallDispositions();\n\n /**\n * @var array<array{\n * id:string,\n * label:string,\n * deleted: bool\n * }>\n */\n return $response->toArray();\n }\n\n /**\n * @return array<CrmFieldOption>\n */\n public function fetchOpportunityPipelineStages(): array\n {\n $stages = [];\n $apiResponse = $this->getNewInstance()->crm()->pipelines()->pipelinesApi()->getAll('deals');\n\n if ($apiResponse instanceof Error) {\n $this->log->error('[Hubspot] Failed to fetch opportunity pipelines', [\n 'reason' => $apiResponse->getMessage(),\n ]);\n\n return [];\n }\n\n foreach ($apiResponse->getResults() as $pipeline) {\n $pipelineStages = array_map(\n static function (PipelineStage $stage) {\n return [\n 'id' => $stage->getId(),\n 'label' => $stage->getLabel(),\n ];\n },\n $pipeline->getStages()\n );\n\n $stages = array_merge($stages, $pipelineStages);\n }\n\n return $stages;\n }\n\n public function fetchOpportunityPipelines(): array\n {\n $pipelines = [];\n\n try {\n $apiResponse = $this->makeRequest('/crm/v3/pipelines/deals');\n } catch (\\Exception $e) {\n $this->log->info('[Hubspot] Failed to fetch opportunity pipelines', [\n 'reason' => $e->getMessage(),\n ]);\n\n return [];\n }\n\n $response = $apiResponse->toArray();\n\n foreach ($response['results'] as $pipeline) {\n $pipelines[] = [\n 'id' => $pipeline['id'],\n 'label' => $pipeline['label'],\n ];\n }\n\n return $pipelines;\n }\n\n /**\n * @return array<CrmFieldOption>\n */\n public function fetchMeetingOutcomeFieldOptions(Field $field): array\n {\n return $field->getCrmProviderId() === 'meetingOutcome'\n ? $this->fetchMeetingOutcomeTypes()\n : $this->fetchCallActivityTypes();\n }\n\n public function fetchMeetingOutcomeTypes(): array\n {\n return $this->extractMeetingTypeOptions(\n 'https://api.hubapi.com/crm/v3/properties/meeting/hs_meeting_outcome'\n );\n }\n\n public function fetchCallActivityTypes(): array\n {\n return $this->extractMeetingTypeOptions(\n 'https://api.hubapi.com/crm/v3/properties/call/hs_activity_type'\n );\n }\n\n private function extractMeetingTypeOptions(string $endpoint): array\n {\n /** @var Response $response */\n $response = $this->getInstance()\n ->getClient()\n ->request('GET', $endpoint);\n\n /**\n * @var array<array{\n * value: string,\n * label: string,\n * displayOrder: int\n * }> $optionData\n */\n $optionData = $response->toArray()['options'] ?? [];\n\n $options = [];\n foreach ($optionData as $item) {\n $options[] = [\n 'id' => $item['value'],\n 'value' => $item['value'],\n 'label' => $item['label'],\n 'display_order' => $item['displayOrder'],\n ];\n }\n\n return $options;\n }\n\n /**\n * @return array<CrmFieldOption>\n */\n public function fetchDispositionFieldOptions(): array\n {\n $options = [];\n\n $dispositions = $this->fetchCallDispositions();\n\n foreach ($dispositions as $disposition) {\n if ($disposition['deleted'] !== false) {\n continue;\n }\n\n $option['value'] = $disposition['id'];\n $option['id'] = $disposition['id'];\n $option['label'] = $disposition['label'];\n\n $options[] = $option;\n }\n\n return $options;\n }\n\n /**\n * @return array<CrmFieldOption>\n */\n public function fetchOpportunityFieldOptions(Field $field): array\n {\n if ($field->isStageField()) {\n return $this->fetchOpportunityPipelineStages();\n }\n\n if ($field->isPipelineField()) {\n return $this->fetchOpportunityPipelines();\n }\n\n return $this->fetchPropertyOptions('deals', $field->getCrmProviderId());\n }\n\n /**\n * @throws BadRequest\n * @throws HubspotException\n */\n public function makeRequest(string $endpoint, $method = 'GET', $payload = [], ?string $queryString = null)\n {\n $endpoint = self::BASE_URL . $endpoint;\n\n if ($method === 'GET') {\n $response = $this->getInstance()->getClient()?->request(\n method: $method,\n endpoint: $endpoint,\n query_string: $queryString\n );\n } else {\n $response = $this->getInstance()->getClient()->request($method, $endpoint, [\n 'json' => ($payload),\n ]);\n }\n\n $max = $response->getHeaderLine('X-HubSpot-RateLimit-Max'); // \"110\"\n $remaining = $response->getHeaderLine('X-HubSpot-RateLimit-Remaining'); // \"109\"\n $interval = $response->getHeaderLine('X-HubSpot-RateLimit-Interval-Milliseconds'); // \"10000\"\n $body = json_decode((string) $response->getBody(), true);\n\n \\Illuminate\\Support\\Facades\\Log::channel('custom_channel')->info('$max ' . PHP_EOL . print_r($max, true));\n \\Illuminate\\Support\\Facades\\Log::channel('custom_channel')->info('$remaining ' . PHP_EOL . print_r($remaining, true));\n \\Illuminate\\Support\\Facades\\Log::channel('custom_channel')->info('$interval ' . PHP_EOL . print_r($interval, true));\n \\Illuminate\\Support\\Facades\\Log::channel('custom_channel')->info('$body ' . PHP_EOL . print_r($body, true));\n\n return $response;\n }\n\n /**\n * @throws BadRequest\n * @throws HubspotException\n */\n public function createMeeting(array $payload): Response\n {\n $endpoint = '/crm/v3/objects/meetings';\n\n return $this->makeRequest($endpoint, 'POST', $payload);\n }\n\n /**\n * @throws BadRequest\n * @throws HubspotException\n */\n public function updateMeeting(string $meetingId, array $payload): Response\n {\n $endpoint = '/crm/v3/objects/meetings/' . $meetingId;\n\n return $this->makeRequest($endpoint, 'PATCH', $payload);\n }\n\n /**\n * @throws \\Exception\n */\n public function createNote(\n string $body,\n string $ownerId,\n int $timestamp,\n string $objectId,\n NoteObject $noteObject\n ): ?string {\n try {\n $noteInput = new SimplePublicObjectInput([\n 'properties' => [\n 'hs_note_body' => $body,\n 'hubspot_owner_id' => $ownerId,\n 'hs_timestamp' => $timestamp,\n ],\n ]);\n\n // Create note\n $note = $this->getNewInstance()->crm()->objects()->basicApi()->create('note', $noteInput);\n\n $this->getNewInstance()->crm()->objects()->associationsApi()->create(\n 'note',\n $note->getId(),\n $this->getNoteObject($noteObject),\n $objectId,\n $this->getNoteAssociationType($noteObject),\n );\n\n return $note->getId();\n } catch (\\Exception $e) {\n $this->log->error('[Hubspot] Failed to create note', [\n 'objectId' => $objectId,\n 'noteObject' => $noteObject->getObjectType(),\n 'reason' => $e->getMessage(),\n ]);\n\n \\Sentry::captureException($e);\n }\n\n return null;\n }\n\n public function updateEngagement(string $objectId, array $engagement, array $metadata): void\n {\n $this->getInstance()->engagements()->update($objectId, $engagement, $metadata);\n }\n\n public function getEngagementData(string $engagementId): array\n {\n $engagement = $this->getInstance()->engagements()->get($engagementId);\n\n return $engagement->toArray();\n }\n\n public function createEngagement(array $engagement, array $associations, array $metadata): Response\n {\n return $this->getInstance()\n ->engagements()\n ->create($engagement, $associations, $metadata);\n }\n\n public function isUnauthorizedException(\\Exception $e): bool\n {\n // Check for specific HubSpot API exception types first\n if ($e instanceof BadRequest) {\n // BadRequest can contain 401 status codes\n return $e->getCode() === 401;\n }\n\n // Check for HTTP client exceptions with status codes\n if ($e instanceof \\GuzzleHttp\\Exception\\RequestException && $e->hasResponse()) {\n $response = $e->getResponse();\n if ($response !== null) {\n return $response->getStatusCode() === 401;\n }\n }\n\n // Check for Guzzle HTTP exceptions\n if ($e instanceof \\GuzzleHttp\\Exception\\ClientException) {\n return $e->getCode() === 401;\n }\n\n // Fallback to string matching as last resort, but be more specific\n $message = strtolower($e->getMessage());\n\n return str_contains($message, '401 unauthorized') ||\n str_contains($message, 'http 401') ||\n str_contains($message, 'status code 401') ||\n (preg_match('/\\b401\\b/', $message) && str_contains($message, 'unauthorized'));\n }\n\n /**\n * Validates and refreshes the access token if needed before API requests.\n * This ensures long-running processes don't fail due to token expiration.\n *\n * @throws SocialAccountTokenInvalidException\n */\n public function ensureValidToken(): void\n {\n if ($this->oauthAccount === null) {\n return;\n }\n\n $newToken = $this->tokenManager->ensureValidToken($this->oauthAccount);\n if ($newToken !== null) {\n $this->accessToken = $newToken;\n }\n }\n\n public function getConfig()\n {\n return $this->config;\n }\n\n // returns only active (archived=false)\n public function getOwners(): array\n {\n return $this->getNewInstance()->crm()->owners()->getAll();\n }\n\n /**\n * @param bool $archived\n *\n * @return array<Owner>|[]\n */\n public function getOwnersArchived(bool $archived = true): array\n {\n $endpoint = '/crm/v3/owners';\n $queryParams = [\n 'archived' => $archived ? 'true' : 'false',\n ];\n $queryString = http_build_query($queryParams);\n\n $owners = [];\n\n try {\n $response = $this->makeRequest(endpoint: $endpoint, queryString: $queryString);\n $responseData = $response?->toArray();\n\n foreach ($responseData['results'] as $result) {\n try {\n $owners[] = Owner::create($result);\n } catch (Throwable $e) {\n $this->log->error('[HubSpot] Failed to process owner data', [\n 'result' => $result,\n 'error' => $e->getMessage(),\n ]);\n\n continue;\n }\n }\n } catch (Throwable $e) {\n $this->log->error('HubSpot] Failed to fetch owners', [\n 'archived' => $archived,\n 'error' => $e->getMessage(),\n ]);\n\n return [];\n }\n\n return $owners;\n }\n\n public function getMeeting(string $engagementId): ObjectWithAssociations\n {\n return $this->getNewInstance()->crm()->objects()->basicApi()\n ->getById('meeting', $engagementId, null, 'contact,company,deal');\n }\n\n public function deleteEngagement(string $engagementId): void\n {\n $this->getInstance()->engagements()->delete((int) $engagementId);\n }\n\n public function getAssociationsData(array $ids, string $fromObject, string $toObject): array\n {\n $associationData = [];\n $idChunks = array_chunk($ids, self::ASSOCIATIONS_BATCH_SIZE_LIMIT);\n\n foreach ($idChunks as $idChunk) {\n try {\n $batchInput = new \\HubSpot\\Client\\Crm\\Associations\\Model\\BatchInputPublicObjectId();\n $batchInput->setInputs(array_map(function ($id) {\n $publicObjectId = new \\HubSpot\\Client\\Crm\\Associations\\Model\\PublicObjectId();\n $publicObjectId->setId($id);\n\n return $publicObjectId;\n }, $idChunk));\n\n $associatedObjectsData = $this\n ->getNewInstance()\n ->crm()\n ->associations()\n ->batchApi()\n ->read($fromObject, $toObject, $batchInput);\n\n if ($associatedObjectsData instanceof \\HubSpot\\Client\\Crm\\Associations\\Model\\BatchResponsePublicAssociationMulti) {\n foreach ($associatedObjectsData->getResults() as $association) {\n $from = $association->getFrom()->getId();\n $toAssociations = $association->getTo();\n\n if (! empty($toAssociations)) {\n $associationData[$from] = array_map(function ($item) {\n return $item->getId();\n }, $toAssociations);\n }\n }\n }\n } catch (\\Exception $e) {\n $this->log->error('[Hubspot] Failed to fetch associations', [\n 'from_object' => $fromObject,\n 'to_object' => $toObject,\n 'reason' => $e->getMessage(),\n ]);\n }\n }\n\n return $associationData;\n }\n\n /**\n * @throws \\Exception\n */\n private function getNoteAssociationType(NoteObject $noteObject): string\n {\n return match($noteObject) {\n NoteObject::Opportunity => 'note_to_deal',\n NoteObject::Lead, NoteObject::Contact => 'note_to_contact', // or 'note_to_lead' if your portal supports it\n NoteObject::Account => 'note_to_company',\n NoteObject::Call, NoteObject::Event => throw new \\Exception('Not supported'),\n };\n }\n\n /**\n * @throws \\Exception\n */\n private function getNoteObject(NoteObject $noteObject): string\n {\n return match($noteObject) {\n NoteObject::Opportunity => 'deal',\n NoteObject::Lead, NoteObject::Contact => 'contact',\n NoteObject::Account => 'company',\n NoteObject::Call, NoteObject::Event => throw new \\Exception('Not supported'),\n };\n }\n\n public function addAssociations(string $objectType, string $associationType, array $payload): Response\n {\n $endpoint = \"/crm/v4/associations/$objectType/$associationType/batch/create\";\n\n return $this->makeRequest($endpoint, 'POST', $payload);\n }\n\n public function removeAssociations(string $objectType, string $associationType, array $payload): Response\n {\n $endpoint = \"/crm/v4/associations/$objectType/$associationType/batch/archive\";\n\n return $this->makeRequest($endpoint, 'POST', $payload);\n }\n}","depth":4,"on_screen":true,"value":"<?php\n\ndeclare(strict_types=1);\n\nnamespace Jiminny\\Services\\Crm\\Hubspot;\n\nuse HubSpot\\Client\\Crm\\Deals\\ApiException as DealApiException;\nuse HubSpot\\Client\\Crm\\Contacts\\ApiException as ContactApiException;\nuse HubSpot\\Client\\Crm\\Companies\\ApiException as CompanyApiException;\nuse HubSpot\\Client\\Crm\\Contacts\\Model\\SimplePublicObjectWithAssociations as ContactsWithAssociations;\nuse HubSpot\\Client\\Crm\\Companies\\Model\\SimplePublicObjectWithAssociations as CompaniesWithAssociations;\nuse HubSpot\\Client\\Crm\\Deals\\Model\\SimplePublicObjectWithAssociations as DealWithAssociations;\nuse HubSpot\\Client\\Crm\\Objects\\Model\\SimplePublicObjectInput;\nuse HubSpot\\Client\\Crm\\Objects\\Model\\SimplePublicObjectWithAssociations as ObjectWithAssociations;\nuse HubSpot\\Client\\Crm\\Pipelines\\Model\\Error;\nuse HubSpot\\Client\\Crm\\Pipelines\\Model\\PipelineStage;\nuse HubSpot\\Client\\Crm\\Properties\\Model\\Property;\nuse HubSpot\\Discovery\\Discovery;\nuse Jiminny\\Component\\Utility\\Service\\ProviderRateLimiter;\nuse Jiminny\\Exceptions\\CrmException;\nuse Jiminny\\Exceptions\\RateLimitException;\nuse Jiminny\\Exceptions\\SocialAccountTokenInvalidException;\nuse Jiminny\\Jobs\\Crm\\NoteObject;\nuse Jiminny\\Models\\Crm\\Field;\nuse Jiminny\\Services\\Crm\\BaseClient;\nuse Jiminny\\Services\\Crm\\Hubspot\\DTO\\Response\\Owner;\nuse Jiminny\\Services\\SocialAccountService;\nuse SevenShores\\Hubspot\\Exceptions\\BadRequest;\nuse SevenShores\\Hubspot\\Exceptions\\HubspotException;\nuse SevenShores\\Hubspot\\Factory;\nuse SevenShores\\Hubspot\\Http\\Response;\nuse Jiminny\\Services\\Crm\\Hubspot\\Pagination\\HubspotPaginationService;\nuse Throwable;\n\n/**\n * @phpstan-type CrmFieldOption array{id:string, label:string, value?:string}\n */\nclass Client extends BaseClient implements HubspotClientInterface\n{\n public const string MIN_API_VERSION = '2';\n\n public const string BASE_URL = 'https://api.hubapi.com';\n\n public const int ASSOCIATIONS_BATCH_SIZE_LIMIT = 1000;\n\n private HubspotPaginationService $paginationService;\n private HubspotTokenManager $tokenManager;\n private ProviderRateLimiter $rateLimiter;\n\n public function __construct(\n SocialAccountService $socialAccountService,\n HubspotPaginationService $paginationService,\n HubspotTokenManager $tokenManager,\n ProviderRateLimiter $rateLimiter,\n ) {\n parent::__construct($socialAccountService);\n $this->paginationService = $paginationService;\n $this->tokenManager = $tokenManager;\n $this->rateLimiter = $rateLimiter;\n\n $this->setBaseUrl(self::BASE_URL);\n $this->setVersion(self::MIN_API_VERSION);\n }\n\n /**\n * Single entry point for every HubSpot API call. Enforces the per-portal\n * rate limit configured in the rate_limits table (morphed to the current\n * Configuration) and reacts to a real 429 from HubSpot by translating it\n * into a RateLimitException carrying Retry-After.\n *\n * Wrap any outbound HubSpot call (SDK or raw HTTP) like:\n *\n * $this->executeRequest(fn () => $this->getNewInstance()->crm()->...);\n *\n * @template T\n * @param callable(): T $apiCall\n * @return T\n *\n * @throws RateLimitException\n */\n private function executeRequest(callable $apiCall)\n {\n if (! $this->rateLimiter->canMakeRequest($this->config)) {\n $retryAfter = $this->rateLimiter->requestAvailableIn($this->config);\n\n $this->log->warning('[Hubspot] Rate limit exceeded, deferring request', [\n 'team_id' => $this->config->team_id,\n 'config_id' => $this->config->getId(),\n 'retry_after' => $retryAfter,\n ]);\n\n throw new RateLimitException(\n 'Hubspot rate limit reached for configuration ' . $this->config->getId(),\n $retryAfter,\n );\n }\n\n $this->rateLimiter->incrementRequestCount($this->config);\n\n try {\n return $apiCall();\n } catch (Throwable $e) {\n if ($this->isHubspotRateLimit($e)) {\n $retryAfter = $this->parseRetryAfter($e);\n\n $this->log->warning('[Hubspot] Received 429 from API', [\n 'team_id' => $this->config->team_id,\n 'config_id' => $this->config->getId(),\n 'retry_after' => $retryAfter,\n 'reason' => $e->getMessage(),\n ]);\n\n throw new RateLimitException('Hubspot returned 429', $retryAfter, $e);\n }\n\n throw $e;\n }\n }\n\n private function isHubspotRateLimit(Throwable $e): bool\n {\n return method_exists($e, 'getCode') && (int) $e->getCode() === 429;\n }\n\n private function parseRetryAfter(Throwable $e): int\n {\n if (method_exists($e, 'getResponseHeaders')) {\n $headers = $e->getResponseHeaders() ?: [];\n $value = $headers['Retry-After'] ?? $headers['retry-after'] ?? null;\n if (is_array($value)) {\n $value = $value[0] ?? null;\n }\n if (is_numeric($value)) {\n return (int) $value;\n }\n }\n\n return 10;\n }\n\n public function getMinimumApiVersion(): string\n {\n return self::MIN_API_VERSION;\n }\n\n public function getInstance(): Factory\n {\n return new Factory([\n 'key' => $this->accessToken,\n 'oauth2' => true,\n 'base_url' => $this->baseUrl,\n ]);\n }\n\n public function getNewInstance(): Discovery\n {\n return \\HubSpot\\Factory::createWithAccessToken($this->accessToken);\n }\n\n /**\n * Secondly and daily limits for Hubspot API\n *\n * Product Tier: Free & Starter | Professional & Enterprise | API add-on (any tier)\n * Burst: 100/10 seconds | 150/10 seconds | 200/10 seconds\n * Daily: 250,000 | 500,000 | 1,000,000\n *\n * Official documentation states: The search endpoints are rate limited to five requests per second.\n * Since with 5 RPS were still hitting secondly rate limits we lowered it to 4\n */\n public function getPaginatedData(array $payload, string $type, int $offset = 0): array\n {\n $total = 0;\n $lastId = null;\n $rows = [];\n foreach ($this->getPaginatedDataGenerator($payload, $type, $offset, $total, $lastId) as $row) {\n $rows[] = $row;\n }\n\n return ['results' => $rows, 'total' => $total, 'last_record' => $lastId];\n }\n\n /**\n * @throws HubspotException\n * @throws SocialAccountTokenInvalidException\n * @throws BadRequest\n */\n public function getPaginatedDataGenerator(\n array $payload,\n string $type,\n int $offset = 0,\n int &$total = 0,\n ?string &$lastRecordId = null\n ): \\Generator {\n return $this->paginationService->getPaginatedDataGenerator(\n $this,\n $payload,\n $type,\n $offset,\n $total,\n $lastRecordId\n );\n }\n\n /**\n * @throws DealApiException\n * @throws CrmException\n */\n public function getOpportunityById(string $crmId, array $fields): array\n {\n try {\n $deal = $this->executeRequest(fn () => $this->getNewInstance()->crm()->deals()->basicApi()->getById(\n $crmId,\n implode(',', $fields),\n 'companies,contacts'\n ));\n } catch (DealApiException $e) {\n $this->log->info('[Hubspot] Failed to fetch opportunity', [\n 'crm_id' => $crmId,\n 'reason' => $e->getMessage(),\n ]);\n\n throw $e;\n }\n\n if (! $deal instanceof DealWithAssociations) {\n throw new CrmException('Deal not found');\n }\n\n return [\n 'id' => $deal->getId(),\n 'properties' => $deal->getProperties(),\n 'associations' => $deal->getAssociations(),\n ];\n }\n\n /**\n * Generic batch read method for HubSpot objects\n *\n * @param string $objectType The object type ('deals', 'companies', 'contacts')\n * @param array<string> $crmIds Array of HubSpot object IDs (max 100)\n * @param array<string> $fields Array of property names to fetch\n *\n * @return array<string, array> Array keyed by CRM ID with object data\n */\n private function batchReadObjects(string $objectType, array $crmIds, array $fields): array\n {\n if (empty($crmIds)) {\n return [];\n }\n\n $this->validateBatchSize($objectType, $crmIds);\n $this->ensureValidToken();\n\n try {\n $batchConfig = $this->createBatchConfiguration($objectType);\n $batchReadRequest = $this->prepareBatchRequest($batchConfig, $crmIds, $fields);\n $response = $batchConfig['api']->read($batchReadRequest);\n\n $this->validateApiResponse($response, $objectType);\n\n $results = $this->processApiResults($response);\n $this->logBatchResults($objectType, $crmIds, $results);\n\n return $results;\n } catch (\\Throwable $e) {\n $this->handleBatchError($e, $objectType, $crmIds);\n }\n }\n\n private function validateBatchSize(string $objectType, array $crmIds): void\n {\n if (count($crmIds) > 100) {\n throw new \\InvalidArgumentException(\"Batch size cannot exceed 100 {$objectType}\");\n }\n }\n\n private function createBatchConfiguration(string $objectType): array\n {\n $configurations = [\n 'deals' => [\n 'batchReadRequest' => new \\HubSpot\\Client\\Crm\\Deals\\Model\\BatchReadInputSimplePublicObjectId(),\n 'inputClass' => \\HubSpot\\Client\\Crm\\Deals\\Model\\SimplePublicObjectId::class,\n 'api' => $this->getNewInstance()->crm()->deals()->batchApi(),\n ],\n 'companies' => [\n 'batchReadRequest' => new \\HubSpot\\Client\\Crm\\Companies\\Model\\BatchReadInputSimplePublicObjectId(),\n 'inputClass' => \\HubSpot\\Client\\Crm\\Companies\\Model\\SimplePublicObjectId::class,\n 'api' => $this->getNewInstance()->crm()->companies()->batchApi(),\n ],\n 'contacts' => [\n 'batchReadRequest' => new \\HubSpot\\Client\\Crm\\Contacts\\Model\\BatchReadInputSimplePublicObjectId(),\n 'inputClass' => \\HubSpot\\Client\\Crm\\Contacts\\Model\\SimplePublicObjectId::class,\n 'api' => $this->getNewInstance()->crm()->contacts()->batchApi(),\n ],\n ];\n\n if (! isset($configurations[$objectType])) {\n throw new \\InvalidArgumentException(\"Unsupported object type: {$objectType}\");\n }\n\n return $configurations[$objectType];\n }\n\n private function prepareBatchRequest(array $batchConfig, array $crmIds, array $fields): object\n {\n $batchReadRequest = $batchConfig['batchReadRequest'];\n $inputClass = $batchConfig['inputClass'];\n\n $inputs = array_map(function ($crmId) use ($inputClass) {\n $input = new $inputClass();\n $input->setId($crmId);\n\n return $input;\n }, $crmIds);\n\n $batchReadRequest->setInputs($inputs);\n $batchReadRequest->setProperties($fields);\n\n return $batchReadRequest;\n }\n\n private function validateApiResponse($response, string $objectType): void\n {\n if (! $response) {\n throw new CrmException(\"HubSpot API returned null response for {$objectType} batch read\");\n }\n }\n\n private function processApiResults($response): array\n {\n $results = [];\n $responseResults = $response->getResults();\n\n if ($responseResults) {\n foreach ($responseResults as $object) {\n if ($object && $object->getId()) {\n $results[$object->getId()] = [\n 'id' => $object->getId(),\n 'properties' => $object->getProperties() ?: [],\n ];\n }\n }\n }\n\n return $results;\n }\n\n private function logBatchResults(string $objectType, array $crmIds, array $results): void\n {\n $this->log->info(\"[HubSpot] Batch fetched {$objectType}\", [\n 'requested_count' => count($crmIds),\n 'returned_count' => count($results),\n 'crm_ids' => $crmIds,\n ]);\n }\n\n private function handleBatchError(\\Throwable $e, string $objectType, array $crmIds): void\n {\n $errorMessage = $e->getMessage() ?: 'Unknown error';\n $errorTrace = $e->getTraceAsString() ?: 'No trace available';\n\n $this->log->error(\"[HubSpot] Failed to batch fetch {$objectType}\", [\n 'crm_ids' => $crmIds,\n 'error' => $errorMessage,\n 'trace' => $errorTrace,\n ]);\n\n throw new CrmException(\"Failed to batch fetch {$objectType}: \" . $errorMessage);\n }\n\n /**\n * Batch read multiple opportunities by their CRM IDs\n *\n * @param array<string> $crmIds Array of HubSpot deal IDs (max 100)\n * @param array<string> $fields Array of property names to fetch\n *\n * @return array<string, array> Array keyed by CRM ID with opportunity data\n */\n public function getOpportunitiesByIds(array $crmIds, array $fields): array\n {\n return $this->batchReadObjects('deals', $crmIds, $fields);\n }\n\n /**\n * Batch read multiple companies by their CRM IDs\n *\n * @param array<string> $crmIds Array of HubSpot company IDs (max 100)\n * @param array<string> $fields Array of property names to fetch\n *\n * @return array<string, array> Array keyed by CRM ID with company data\n */\n public function getCompaniesByIds(array $crmIds, array $fields): array\n {\n return $this->batchReadObjects('companies', $crmIds, $fields);\n }\n\n /**\n * Batch read multiple contacts by their CRM IDs\n *\n * @param array<string> $crmIds Array of HubSpot contact IDs (max 100)\n * @param array<string> $fields Array of property names to fetch\n *\n * @return array<string, array> Array keyed by CRM ID with contact data\n */\n public function getContactsByIds(array $crmIds, array $fields): array\n {\n return $this->batchReadObjects('contacts', $crmIds, $fields);\n }\n\n /**\n * @throws CompanyApiException\n * @throws CrmException\n */\n public function getAccountById(string $crmId, array $fields): array\n {\n try {\n $company = $this->getNewInstance()->crm()->companies()->basicApi()->getById(\n $crmId,\n implode(',', $fields),\n );\n } catch (CompanyApiException $e) {\n $this->log->info('[Hubspot] Failed to fetch account', [\n 'crm_id' => $crmId,\n 'reason' => $e->getMessage(),\n ]);\n\n throw $e;\n }\n\n if (! $company instanceof CompaniesWithAssociations) {\n throw new CrmException('Account not found');\n }\n\n return [\n 'id' => $company->getId(),\n 'properties' => $company->getProperties(),\n ];\n }\n\n /**\n * @throws ContactApiException\n * @throws CrmException\n */\n public function getContactById(string $crmId, array $fields): array\n {\n try {\n $contact = $this->getNewInstance()->crm()->contacts()->basicApi()->getById(\n $crmId,\n implode(',', $fields)\n );\n } catch (ContactApiException $e) {\n $this->log->info('[Hubspot] Failed to fetch contact', [\n 'crm_id' => $crmId,\n 'reason' => $e->getMessage(),\n ]);\n\n throw $e;\n }\n\n if (! $contact instanceof ContactsWithAssociations) {\n throw new CrmException('Contact not found');\n }\n\n return [\n 'id' => $contact->getId(),\n 'properties' => $contact->getProperties(),\n ];\n }\n\n /**\n * This is email search request that Hubspot offers as GET (more generous quota)\n */\n public function getContactByEmail(string $email, array $fields = []): array\n {\n try {\n $contact = $this->getNewInstance()->crm()->contacts()->basicApi()->getById(\n $email,\n implode(',', $fields),\n null,\n false,\n 'email'\n );\n\n return [\n 'id' => $contact->getId(),\n 'properties' => $contact->getProperties(),\n ];\n } catch (ContactApiException $e) {\n $this->log->info('[Hubspot] Failed to fetch contact', [\n 'email' => $email,\n 'reason' => $e->getMessage(),\n ]);\n\n return [];\n }\n }\n\n /**\n * @throws CrmException\n */\n public function fetchProperty(string $objectType, string $propertyId): Property\n {\n $result = $this->getNewInstance()->crm()->properties()->coreApi()->getByName($objectType, $propertyId);\n\n if (! $result instanceof Property) {\n $this->log->error('[Hubspot] Failed to fetch property', [\n 'object_type' => $objectType,\n 'property_id' => $propertyId,\n 'reason' => $result->getMessage(),\n ]);\n\n throw new CrmException('Failed to fetch property');\n }\n\n return $result;\n }\n\n /**\n * @return array<CrmFieldOption>\n */\n public function fetchPropertyOptions(string $objectType, string $propertyId): array\n {\n /** @var array<CrmFieldOption> */\n return $this->fetchProperty($objectType, $propertyId)->getOptions();\n }\n\n /**\n * @return array<array{id:string, label:string, deleted:bool}>\n */\n public function fetchCallDispositions(): array\n {\n /** @var Response $response */\n $response = $this->getInstance()->engagements()->getCallDispositions();\n\n /**\n * @var array<array{\n * id:string,\n * label:string,\n * deleted: bool\n * }>\n */\n return $response->toArray();\n }\n\n /**\n * @return array<CrmFieldOption>\n */\n public function fetchOpportunityPipelineStages(): array\n {\n $stages = [];\n $apiResponse = $this->getNewInstance()->crm()->pipelines()->pipelinesApi()->getAll('deals');\n\n if ($apiResponse instanceof Error) {\n $this->log->error('[Hubspot] Failed to fetch opportunity pipelines', [\n 'reason' => $apiResponse->getMessage(),\n ]);\n\n return [];\n }\n\n foreach ($apiResponse->getResults() as $pipeline) {\n $pipelineStages = array_map(\n static function (PipelineStage $stage) {\n return [\n 'id' => $stage->getId(),\n 'label' => $stage->getLabel(),\n ];\n },\n $pipeline->getStages()\n );\n\n $stages = array_merge($stages, $pipelineStages);\n }\n\n return $stages;\n }\n\n public function fetchOpportunityPipelines(): array\n {\n $pipelines = [];\n\n try {\n $apiResponse = $this->makeRequest('/crm/v3/pipelines/deals');\n } catch (\\Exception $e) {\n $this->log->info('[Hubspot] Failed to fetch opportunity pipelines', [\n 'reason' => $e->getMessage(),\n ]);\n\n return [];\n }\n\n $response = $apiResponse->toArray();\n\n foreach ($response['results'] as $pipeline) {\n $pipelines[] = [\n 'id' => $pipeline['id'],\n 'label' => $pipeline['label'],\n ];\n }\n\n return $pipelines;\n }\n\n /**\n * @return array<CrmFieldOption>\n */\n public function fetchMeetingOutcomeFieldOptions(Field $field): array\n {\n return $field->getCrmProviderId() === 'meetingOutcome'\n ? $this->fetchMeetingOutcomeTypes()\n : $this->fetchCallActivityTypes();\n }\n\n public function fetchMeetingOutcomeTypes(): array\n {\n return $this->extractMeetingTypeOptions(\n 'https://api.hubapi.com/crm/v3/properties/meeting/hs_meeting_outcome'\n );\n }\n\n public function fetchCallActivityTypes(): array\n {\n return $this->extractMeetingTypeOptions(\n 'https://api.hubapi.com/crm/v3/properties/call/hs_activity_type'\n );\n }\n\n private function extractMeetingTypeOptions(string $endpoint): array\n {\n /** @var Response $response */\n $response = $this->getInstance()\n ->getClient()\n ->request('GET', $endpoint);\n\n /**\n * @var array<array{\n * value: string,\n * label: string,\n * displayOrder: int\n * }> $optionData\n */\n $optionData = $response->toArray()['options'] ?? [];\n\n $options = [];\n foreach ($optionData as $item) {\n $options[] = [\n 'id' => $item['value'],\n 'value' => $item['value'],\n 'label' => $item['label'],\n 'display_order' => $item['displayOrder'],\n ];\n }\n\n return $options;\n }\n\n /**\n * @return array<CrmFieldOption>\n */\n public function fetchDispositionFieldOptions(): array\n {\n $options = [];\n\n $dispositions = $this->fetchCallDispositions();\n\n foreach ($dispositions as $disposition) {\n if ($disposition['deleted'] !== false) {\n continue;\n }\n\n $option['value'] = $disposition['id'];\n $option['id'] = $disposition['id'];\n $option['label'] = $disposition['label'];\n\n $options[] = $option;\n }\n\n return $options;\n }\n\n /**\n * @return array<CrmFieldOption>\n */\n public function fetchOpportunityFieldOptions(Field $field): array\n {\n if ($field->isStageField()) {\n return $this->fetchOpportunityPipelineStages();\n }\n\n if ($field->isPipelineField()) {\n return $this->fetchOpportunityPipelines();\n }\n\n return $this->fetchPropertyOptions('deals', $field->getCrmProviderId());\n }\n\n /**\n * @throws BadRequest\n * @throws HubspotException\n */\n public function makeRequest(string $endpoint, $method = 'GET', $payload = [], ?string $queryString = null)\n {\n $endpoint = self::BASE_URL . $endpoint;\n\n if ($method === 'GET') {\n $response = $this->getInstance()->getClient()?->request(\n method: $method,\n endpoint: $endpoint,\n query_string: $queryString\n );\n } else {\n $response = $this->getInstance()->getClient()->request($method, $endpoint, [\n 'json' => ($payload),\n ]);\n }\n\n $max = $response->getHeaderLine('X-HubSpot-RateLimit-Max'); // \"110\"\n $remaining = $response->getHeaderLine('X-HubSpot-RateLimit-Remaining'); // \"109\"\n $interval = $response->getHeaderLine('X-HubSpot-RateLimit-Interval-Milliseconds'); // \"10000\"\n $body = json_decode((string) $response->getBody(), true);\n\n \\Illuminate\\Support\\Facades\\Log::channel('custom_channel')->info('$max ' . PHP_EOL . print_r($max, true));\n \\Illuminate\\Support\\Facades\\Log::channel('custom_channel')->info('$remaining ' . PHP_EOL . print_r($remaining, true));\n \\Illuminate\\Support\\Facades\\Log::channel('custom_channel')->info('$interval ' . PHP_EOL . print_r($interval, true));\n \\Illuminate\\Support\\Facades\\Log::channel('custom_channel')->info('$body ' . PHP_EOL . print_r($body, true));\n\n return $response;\n }\n\n /**\n * @throws BadRequest\n * @throws HubspotException\n */\n public function createMeeting(array $payload): Response\n {\n $endpoint = '/crm/v3/objects/meetings';\n\n return $this->makeRequest($endpoint, 'POST', $payload);\n }\n\n /**\n * @throws BadRequest\n * @throws HubspotException\n */\n public function updateMeeting(string $meetingId, array $payload): Response\n {\n $endpoint = '/crm/v3/objects/meetings/' . $meetingId;\n\n return $this->makeRequest($endpoint, 'PATCH', $payload);\n }\n\n /**\n * @throws \\Exception\n */\n public function createNote(\n string $body,\n string $ownerId,\n int $timestamp,\n string $objectId,\n NoteObject $noteObject\n ): ?string {\n try {\n $noteInput = new SimplePublicObjectInput([\n 'properties' => [\n 'hs_note_body' => $body,\n 'hubspot_owner_id' => $ownerId,\n 'hs_timestamp' => $timestamp,\n ],\n ]);\n\n // Create note\n $note = $this->getNewInstance()->crm()->objects()->basicApi()->create('note', $noteInput);\n\n $this->getNewInstance()->crm()->objects()->associationsApi()->create(\n 'note',\n $note->getId(),\n $this->getNoteObject($noteObject),\n $objectId,\n $this->getNoteAssociationType($noteObject),\n );\n\n return $note->getId();\n } catch (\\Exception $e) {\n $this->log->error('[Hubspot] Failed to create note', [\n 'objectId' => $objectId,\n 'noteObject' => $noteObject->getObjectType(),\n 'reason' => $e->getMessage(),\n ]);\n\n \\Sentry::captureException($e);\n }\n\n return null;\n }\n\n public function updateEngagement(string $objectId, array $engagement, array $metadata): void\n {\n $this->getInstance()->engagements()->update($objectId, $engagement, $metadata);\n }\n\n public function getEngagementData(string $engagementId): array\n {\n $engagement = $this->getInstance()->engagements()->get($engagementId);\n\n return $engagement->toArray();\n }\n\n public function createEngagement(array $engagement, array $associations, array $metadata): Response\n {\n return $this->getInstance()\n ->engagements()\n ->create($engagement, $associations, $metadata);\n }\n\n public function isUnauthorizedException(\\Exception $e): bool\n {\n // Check for specific HubSpot API exception types first\n if ($e instanceof BadRequest) {\n // BadRequest can contain 401 status codes\n return $e->getCode() === 401;\n }\n\n // Check for HTTP client exceptions with status codes\n if ($e instanceof \\GuzzleHttp\\Exception\\RequestException && $e->hasResponse()) {\n $response = $e->getResponse();\n if ($response !== null) {\n return $response->getStatusCode() === 401;\n }\n }\n\n // Check for Guzzle HTTP exceptions\n if ($e instanceof \\GuzzleHttp\\Exception\\ClientException) {\n return $e->getCode() === 401;\n }\n\n // Fallback to string matching as last resort, but be more specific\n $message = strtolower($e->getMessage());\n\n return str_contains($message, '401 unauthorized') ||\n str_contains($message, 'http 401') ||\n str_contains($message, 'status code 401') ||\n (preg_match('/\\b401\\b/', $message) && str_contains($message, 'unauthorized'));\n }\n\n /**\n * Validates and refreshes the access token if needed before API requests.\n * This ensures long-running processes don't fail due to token expiration.\n *\n * @throws SocialAccountTokenInvalidException\n */\n public function ensureValidToken(): void\n {\n if ($this->oauthAccount === null) {\n return;\n }\n\n $newToken = $this->tokenManager->ensureValidToken($this->oauthAccount);\n if ($newToken !== null) {\n $this->accessToken = $newToken;\n }\n }\n\n public function getConfig()\n {\n return $this->config;\n }\n\n // returns only active (archived=false)\n public function getOwners(): array\n {\n return $this->getNewInstance()->crm()->owners()->getAll();\n }\n\n /**\n * @param bool $archived\n *\n * @return array<Owner>|[]\n */\n public function getOwnersArchived(bool $archived = true): array\n {\n $endpoint = '/crm/v3/owners';\n $queryParams = [\n 'archived' => $archived ? 'true' : 'false',\n ];\n $queryString = http_build_query($queryParams);\n\n $owners = [];\n\n try {\n $response = $this->makeRequest(endpoint: $endpoint, queryString: $queryString);\n $responseData = $response?->toArray();\n\n foreach ($responseData['results'] as $result) {\n try {\n $owners[] = Owner::create($result);\n } catch (Throwable $e) {\n $this->log->error('[HubSpot] Failed to process owner data', [\n 'result' => $result,\n 'error' => $e->getMessage(),\n ]);\n\n continue;\n }\n }\n } catch (Throwable $e) {\n $this->log->error('HubSpot] Failed to fetch owners', [\n 'archived' => $archived,\n 'error' => $e->getMessage(),\n ]);\n\n return [];\n }\n\n return $owners;\n }\n\n public function getMeeting(string $engagementId): ObjectWithAssociations\n {\n return $this->getNewInstance()->crm()->objects()->basicApi()\n ->getById('meeting', $engagementId, null, 'contact,company,deal');\n }\n\n public function deleteEngagement(string $engagementId): void\n {\n $this->getInstance()->engagements()->delete((int) $engagementId);\n }\n\n public function getAssociationsData(array $ids, string $fromObject, string $toObject): array\n {\n $associationData = [];\n $idChunks = array_chunk($ids, self::ASSOCIATIONS_BATCH_SIZE_LIMIT);\n\n foreach ($idChunks as $idChunk) {\n try {\n $batchInput = new \\HubSpot\\Client\\Crm\\Associations\\Model\\BatchInputPublicObjectId();\n $batchInput->setInputs(array_map(function ($id) {\n $publicObjectId = new \\HubSpot\\Client\\Crm\\Associations\\Model\\PublicObjectId();\n $publicObjectId->setId($id);\n\n return $publicObjectId;\n }, $idChunk));\n\n $associatedObjectsData = $this\n ->getNewInstance()\n ->crm()\n ->associations()\n ->batchApi()\n ->read($fromObject, $toObject, $batchInput);\n\n if ($associatedObjectsData instanceof \\HubSpot\\Client\\Crm\\Associations\\Model\\BatchResponsePublicAssociationMulti) {\n foreach ($associatedObjectsData->getResults() as $association) {\n $from = $association->getFrom()->getId();\n $toAssociations = $association->getTo();\n\n if (! empty($toAssociations)) {\n $associationData[$from] = array_map(function ($item) {\n return $item->getId();\n }, $toAssociations);\n }\n }\n }\n } catch (\\Exception $e) {\n $this->log->error('[Hubspot] Failed to fetch associations', [\n 'from_object' => $fromObject,\n 'to_object' => $toObject,\n 'reason' => $e->getMessage(),\n ]);\n }\n }\n\n return $associationData;\n }\n\n /**\n * @throws \\Exception\n */\n private function getNoteAssociationType(NoteObject $noteObject): string\n {\n return match($noteObject) {\n NoteObject::Opportunity => 'note_to_deal',\n NoteObject::Lead, NoteObject::Contact => 'note_to_contact', // or 'note_to_lead' if your portal supports it\n NoteObject::Account => 'note_to_company',\n NoteObject::Call, NoteObject::Event => throw new \\Exception('Not supported'),\n };\n }\n\n /**\n * @throws \\Exception\n */\n private function getNoteObject(NoteObject $noteObject): string\n {\n return match($noteObject) {\n NoteObject::Opportunity => 'deal',\n NoteObject::Lead, NoteObject::Contact => 'contact',\n NoteObject::Account => 'company',\n NoteObject::Call, NoteObject::Event => throw new \\Exception('Not supported'),\n };\n }\n\n public function addAssociations(string $objectType, string $associationType, array $payload): Response\n {\n $endpoint = \"/crm/v4/associations/$objectType/$associationType/batch/create\";\n\n return $this->makeRequest($endpoint, 'POST', $payload);\n }\n\n public function removeAssociations(string $objectType, string $associationType, array $payload): Response\n {\n $endpoint = \"/crm/v4/associations/$objectType/$associationType/batch/archive\";\n\n return $this->makeRequest($endpoint, 'POST', $payload);\n }\n}","role_description":"text entry area","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"Project","depth":3,"on_screen":false,"role_description":"text"},{"role":"AXButton","text":"Project","depth":3,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"New File or Directory…","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.018055556,"height":0.026666667},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Expand Selected","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.018055556,"height":0.026666667},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Collapse All","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.018055556,"height":0.026666667},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Options","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.018055556,"height":0.026666667},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Hide","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.018055556,"height":0.026666667},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false}]...
|
-188086479456983350
|
6378757184196315236
|
click
|
accessibility
|
NULL
|
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
Editor for custom.log
Sync Changes
Hide This Notification
Code changed:
Hide
2
60
Previous Highlighted Error
Next Highlighted Error
<?php
declare(strict_types=1);
namespace Jiminny\Services\Crm\Hubspot;
use HubSpot\Client\Crm\Deals\ApiException as DealApiException;
use HubSpot\Client\Crm\Contacts\ApiException as ContactApiException;
use HubSpot\Client\Crm\Companies\ApiException as CompanyApiException;
use HubSpot\Client\Crm\Contacts\Model\SimplePublicObjectWithAssociations as ContactsWithAssociations;
use HubSpot\Client\Crm\Companies\Model\SimplePublicObjectWithAssociations as CompaniesWithAssociations;
use HubSpot\Client\Crm\Deals\Model\SimplePublicObjectWithAssociations as DealWithAssociations;
use HubSpot\Client\Crm\Objects\Model\SimplePublicObjectInput;
use HubSpot\Client\Crm\Objects\Model\SimplePublicObjectWithAssociations as ObjectWithAssociations;
use HubSpot\Client\Crm\Pipelines\Model\Error;
use HubSpot\Client\Crm\Pipelines\Model\PipelineStage;
use HubSpot\Client\Crm\Properties\Model\Property;
use HubSpot\Discovery\Discovery;
use Jiminny\Component\Utility\Service\ProviderRateLimiter;
use Jiminny\Exceptions\CrmException;
use Jiminny\Exceptions\RateLimitException;
use Jiminny\Exceptions\SocialAccountTokenInvalidException;
use Jiminny\Jobs\Crm\NoteObject;
use Jiminny\Models\Crm\Field;
use Jiminny\Services\Crm\BaseClient;
use Jiminny\Services\Crm\Hubspot\DTO\Response\Owner;
use Jiminny\Services\SocialAccountService;
use SevenShores\Hubspot\Exceptions\BadRequest;
use SevenShores\Hubspot\Exceptions\HubspotException;
use SevenShores\Hubspot\Factory;
use SevenShores\Hubspot\Http\Response;
use Jiminny\Services\Crm\Hubspot\Pagination\HubspotPaginationService;
use Throwable;
/**
* @phpstan-type CrmFieldOption array{id:string, label:string, value?:string}
*/
class Client extends BaseClient implements HubspotClientInterface
{
public const string MIN_API_VERSION = '2';
public const string BASE_URL = '[URL_WITH_CREDENTIALS] T
* @param callable(): T $apiCall
* @return T
*
* @throws RateLimitException
*/
private function executeRequest(callable $apiCall)
{
if (! $this->rateLimiter->canMakeRequest($this->config)) {
$retryAfter = $this->rateLimiter->requestAvailableIn($this->config);
$this->log->warning('[Hubspot] Rate limit exceeded, deferring request', [
'team_id' => $this->config->team_id,
'config_id' => $this->config->getId(),
'retry_after' => $retryAfter,
]);
throw new RateLimitException(
'Hubspot rate limit reached for configuration ' . $this->config->getId(),
$retryAfter,
);
}
$this->rateLimiter->incrementRequestCount($this->config);
try {
return $apiCall();
} catch (Throwable $e) {
if ($this->isHubspotRateLimit($e)) {
$retryAfter = $this->parseRetryAfter($e);
$this->log->warning('[Hubspot] Received 429 from API', [
'team_id' => $this->config->team_id,
'config_id' => $this->config->getId(),
'retry_after' => $retryAfter,
'reason' => $e->getMessage(),
]);
throw new RateLimitException('Hubspot returned 429', $retryAfter, $e);
}
throw $e;
}
}
private function isHubspotRateLimit(Throwable $e): bool
{
return method_exists($e, 'getCode') && (int) $e->getCode() === 429;
}
private function parseRetryAfter(Throwable $e): int
{
if (method_exists($e, 'getResponseHeaders')) {
$headers = $e->getResponseHeaders() ?: [];
$value = $headers['Retry-After'] ?? $headers['retry-after'] ?? null;
if (is_array($value)) {
$value = $value[0] ?? null;
}
if (is_numeric($value)) {
return (int) $value;
}
}
return 10;
}
public function getMinimumApiVersion(): string
{
return self::MIN_API_VERSION;
}
public function getInstance(): Factory
{
return new Factory([
'key' => $this->accessToken,
'oauth2' => true,
'base_url' => $this->baseUrl,
]);
}
public function getNewInstance(): Discovery
{
return \HubSpot\Factory::createWithAccessToken($this->accessToken);
}
/**
* Secondly and daily limits for Hubspot API
*
* Product Tier: Free & Starter | Professional & Enterprise | API add-on (any tier)
* Burst: 100/10 seconds | 150/10 seconds | 200/10 seconds
* Daily: 250,000 | 500,000 | 1,000,000
*
* Official documentation states: The search endpoints are rate limited to five requests per second.
* Since with 5 RPS were still hitting secondly rate limits we lowered it to 4
*/
public function getPaginatedData(array $payload, string $type, int $offset = 0): array
{
$total = 0;
$lastId = null;
$rows = [];
foreach ($this->getPaginatedDataGenerator($payload, $type, $offset, $total, $lastId) as $row) {
$rows[] = $row;
}
return ['results' => $rows, 'total' => $total, 'last_record' => $lastId];
}
/**
* @throws HubspotException
* @throws SocialAccountTokenInvalidException
* @throws BadRequest
*/
public function getPaginatedDataGenerator(
array $payload,
string $type,
int $offset = 0,
int &$total = 0,
?string &$lastRecordId = null
): \Generator {
return $this->paginationService->getPaginatedDataGenerator(
$this,
$payload,
$type,
$offset,
$total,
$lastRecordId
);
}
/**
* @throws DealApiException
* @throws CrmException
*/
public function getOpportunityById(string $crmId, array $fields): array
{
try {
$deal = $this->executeRequest(fn () => $this->getNewInstance()->crm()->deals()->basicApi()->getById(
$crmId,
implode(',', $fields),
'companies,contacts'
));
} catch (DealApiException $e) {
$this->log->info('[Hubspot] Failed to fetch opportunity', [
'crm_id' => $crmId,
'reason' => $e->getMessage(),
]);
throw $e;
}
if (! $deal instanceof DealWithAssociations) {
throw new CrmException('Deal not found');
}
return [
'id' => $deal->getId(),
'properties' => $deal->getProperties(),
'associations' => $deal->getAssociations(),
];
}
/**
* Generic batch read method for HubSpot objects
*
* @param string $objectType The object type ('deals', 'companies', 'contacts')
* @param array<string> $crmIds Array of HubSpot object IDs (max 100)
* @param array<string> $fields Array of property names to fetch
*
* @return array<string, array> Array keyed by CRM ID with object data
*/
private function batchReadObjects(string $objectType, array $crmIds, array $fields): array
{
if (empty($crmIds)) {
return [];
}
$this->validateBatchSize($objectType, $crmIds);
$this->ensureValidToken();
try {
$batchConfig = $this->createBatchConfiguration($objectType);
$batchReadRequest = $this->prepareBatchRequest($batchConfig, $crmIds, $fields);
$response = $batchConfig['api']->read($batchReadRequest);
$this->validateApiResponse($response, $objectType);
$results = $this->processApiResults($response);
$this->logBatchResults($objectType, $crmIds, $results);
return $results;
} catch (\Throwable $e) {
$this->handleBatchError($e, $objectType, $crmIds);
}
}
private function validateBatchSize(string $objectType, array $crmIds): void
{
if (count($crmIds) > 100) {
throw new \InvalidArgumentException("Batch size cannot exceed 100 {$objectType}");
}
}
private function createBatchConfiguration(string $objectType): array
{
$configurations = [
'deals' => [
'batchReadRequest' => new \HubSpot\Client\Crm\Deals\Model\BatchReadInputSimplePublicObjectId(),
'inputClass' => \HubSpot\Client\Crm\Deals\Model\SimplePublicObjectId::class,
'api' => $this->getNewInstance()->crm()->deals()->batchApi(),
],
'companies' => [
'batchReadRequest' => new \HubSpot\Client\Crm\Companies\Model\BatchReadInputSimplePublicObjectId(),
'inputClass' => \HubSpot\Client\Crm\Companies\Model\SimplePublicObjectId::class,
'api' => $this->getNewInstance()->crm()->companies()->batchApi(),
],
'contacts' => [
'batchReadRequest' => new \HubSpot\Client\Crm\Contacts\Model\BatchReadInputSimplePublicObjectId(),
'inputClass' => \HubSpot\Client\Crm\Contacts\Model\SimplePublicObjectId::class,
'api' => $this->getNewInstance()->crm()->contacts()->batchApi(),
],
];
if (! isset($configurations[$objectType])) {
throw new \InvalidArgumentException("Unsupported object type: {$objectType}");
}
return $configurations[$objectType];
}
private function prepareBatchRequest(array $batchConfig, array $crmIds, array $fields): object
{
$batchReadRequest = $batchConfig['batchReadRequest'];
$inputClass = $batchConfig['inputClass'];
$inputs = array_map(function ($crmId) use ($inputClass) {
$input = new $inputClass();
$input->setId($crmId);
return $input;
}, $crmIds);
$batchReadRequest->setInputs($inputs);
$batchReadRequest->setProperties($fields);
return $batchReadRequest;
}
private function validateApiResponse($response, string $objectType): void
{
if (! $response) {
throw new CrmException("HubSpot API returned null response for {$objectType} batch read");
}
}
private function processApiResults($response): array
{
$results = [];
$responseResults = $response->getResults();
if ($responseResults) {
foreach ($responseResults as $object) {
if ($object && $object->getId()) {
$results[$object->getId()] = [
'id' => $object->getId(),
'properties' => $object->getProperties() ?: [],
];
}
}
}
return $results;
}
private function logBatchResults(string $objectType, array $crmIds, array $results): void
{
$this->log->info("[HubSpot] Batch fetched {$objectType}", [
'requested_count' => count($crmIds),
'returned_count' => count($results),
'crm_ids' => $crmIds,
]);
}
private function handleBatchError(\Throwable $e, string $objectType, array $crmIds): void
{
$errorMessage = $e->getMessage() ?: 'Unknown error';
$errorTrace = $e->getTraceAsString() ?: 'No trace available';
$this->log->error("[HubSpot] Failed to batch fetch {$objectType}", [
'crm_ids' => $crmIds,
'error' => $errorMessage,
'trace' => $errorTrace,
]);
throw new CrmException("Failed to batch fetch {$objectType}: " . $errorMessage);
}
/**
* Batch read multiple opportunities by their CRM IDs
*
* @param array<string> $crmIds Array of HubSpot deal IDs (max 100)
* @param array<string> $fields Array of property names to fetch
*
* @return array<string, array> Array keyed by CRM ID with opportunity data
*/
public function getOpportunitiesByIds(array $crmIds, array $fields): array
{
return $this->batchReadObjects('deals', $crmIds, $fields);
}
/**
* Batch read multiple companies by their CRM IDs
*
* @param array<string> $crmIds Array of HubSpot company IDs (max 100)
* @param array<string> $fields Array of property names to fetch
*
* @return array<string, array> Array keyed by CRM ID with company data
*/
public function getCompaniesByIds(array $crmIds, array $fields): array
{
return $this->batchReadObjects('companies', $crmIds, $fields);
}
/**
* Batch read multiple contacts by their CRM IDs
*
* @param array<string> $crmIds Array of HubSpot contact IDs (max 100)
* @param array<string> $fields Array of property names to fetch
*
* @return array<string, array> Array keyed by CRM ID with contact data
*/
public function getContactsByIds(array $crmIds, array $fields): array
{
return $this->batchReadObjects('contacts', $crmIds, $fields);
}
/**
* @throws CompanyApiException
* @throws CrmException
*/
public function getAccountById(string $crmId, array $fields): array
{
try {
$company = $this->getNewInstance()->crm()->companies()->basicApi()->getById(
$crmId,
implode(',', $fields),
);
} catch (CompanyApiException $e) {
$this->log->info('[Hubspot] Failed to fetch account', [
'crm_id' => $crmId,
'reason' => $e->getMessage(),
]);
throw $e;
}
if (! $company instanceof CompaniesWithAssociations) {
throw new CrmException('Account not found');
}
return [
'id' => $company->getId(),
'properties' => $company->getProperties(),
];
}
/**
* @throws ContactApiException
* @throws CrmException
*/
public function getContactById(string $crmId, array $fields): array
{
try {
$contact = $this->getNewInstance()->crm()->contacts()->basicApi()->getById(
$crmId,
implode(',', $fields)
);
} catch (ContactApiException $e) {
$this->log->info('[Hubspot] Failed to fetch contact', [
'crm_id' => $crmId,
'reason' => $e->getMessage(),
]);
throw $e;
}
if (! $contact instanceof ContactsWithAssociations) {
throw new CrmException('Contact not found');
}
return [
'id' => $contact->getId(),
'properties' => $contact->getProperties(),
];
}
/**
* This is email search request that Hubspot offers as GET (more generous quota)
*/
public function getContactByEmail(string $email, array $fields = []): array
{
try {
$contact = $this->getNewInstance()->crm()->contacts()->basicApi()->getById(
$email,
implode(',', $fields),
null,
false,
'email'
);
return [
'id' => $contact->getId(),
'properties' => $contact->getProperties(),
];
} catch (ContactApiException $e) {
$this->log->info('[Hubspot] Failed to fetch contact', [
'email' => $email,
'reason' => $e->getMessage(),
]);
return [];
}
}
/**
* @throws CrmException
*/
public function fetchProperty(string $objectType, string $propertyId): Property
{
$result = $this->getNewInstance()->crm()->properties()->coreApi()->getByName($objectType, $propertyId);
if (! $result instanceof Property) {
$this->log->error('[Hubspot] Failed to fetch property', [
'object_type' => $objectType,
'property_id' => $propertyId,
'reason' => $result->getMessage(),
]);
throw new CrmException('Failed to fetch property');
}
return $result;
}
/**
* @return array<CrmFieldOption>
*/
public function fetchPropertyOptions(string $objectType, string $propertyId): array
{
/** @var array<CrmFieldOption> */
return $this->fetchProperty($objectType, $propertyId)->getOptions();
}
/**
* @return array<array{id:string, label:string, deleted:bool}>
*/
public function fetchCallDispositions(): array
{
/** @var Response $response */
$response = $this->getInstance()->engagements()->getCallDispositions();
/**
* @var array<array{
* id:string,
* label:string,
* deleted: bool
* }>
*/
return $response->toArray();
}
/**
* @return array<CrmFieldOption>
*/
public function fetchOpportunityPipelineStages(): array
{
$stages = [];
$apiResponse = $this->getNewInstance()->crm()->pipelines()->pipelinesApi()->getAll('deals');
if ($apiResponse instanceof Error) {
$this->log->error('[Hubspot] Failed to fetch opportunity pipelines', [
'reason' => $apiResponse->getMessage(),
]);
return [];
}
foreach ($apiResponse->getResults() as $pipeline) {
$pipelineStages = array_map(
static function (PipelineStage $stage) {
return [
'id' => $stage->getId(),
'label' => $stage->getLabel(),
];
},
$pipeline->getStages()
);
$stages = array_merge($stages, $pipelineStages);
}
return $stages;
}
public function fetchOpportunityPipelines(): array
{
$pipelines = [];
try {
$apiResponse = $this->makeRequest('/crm/v3/pipelines/deals');
} catch (\Exception $e) {
$this->log->info('[Hubspot] Failed to fetch opportunity pipelines', [
'reason' => $e->getMessage(),
]);
return [];
}
$response = $apiResponse->toArray();
foreach ($response['results'] as $pipeline) {
$pipelines[] = [
'id' => $pipeline['id'],
'label' => $pipeline['label'],
];
}
return $pipelines;
}
/**
* @return array<CrmFieldOption>
*/
public function fetchMeetingOutcomeFieldOptions(Field $field): array
{
return $field->getCrmProviderId() === 'meetingOutcome'
? $this->fetchMeetingOutcomeTypes()
: $this->fetchCallActivityTypes();
}
public function fetchMeetingOutcomeTypes(): array
{
return $this->extractMeetingTypeOptions(
'[URL_WITH_CREDENTIALS] Response $response */
$response = $this->getInstance()
->getClient()
->request('GET', $endpoint);
/**
* @var array<array{
* value: string,
* label: string,
* displayOrder: int
* }> $optionData
*/
$optionData = $response->toArray()['options'] ?? [];
$options = [];
foreach ($optionData as $item) {
$options[] = [
'id' => $item['value'],
'value' => $item['value'],
'label' => $item['label'],
'display_order' => $item['displayOrder'],
];
}
return $options;
}
/**
* @return array<CrmFieldOption>
*/
public function fetchDispositionFieldOptions(): array
{
$options = [];
$dispositions = $this->fetchCallDispositions();
foreach ($dispositions as $disposition) {
if ($disposition['deleted'] !== false) {
continue;
}
$option['value'] = $disposition['id'];
$option['id'] = $disposition['id'];
$option['label'] = $disposition['label'];
$options[] = $option;
}
return $options;
}
/**
* @return array<CrmFieldOption>
*/
public function fetchOpportunityFieldOptions(Field $field): array
{
if ($field->isStageField()) {
return $this->fetchOpportunityPipelineStages();
}
if ($field->isPipelineField()) {
return $this->fetchOpportunityPipelines();
}
return $this->fetchPropertyOptions('deals', $field->getCrmProviderId());
}
/**
* @throws BadRequest
* @throws HubspotException
*/
public function makeRequest(string $endpoint, $method = 'GET', $payload = [], ?string $queryString = null)
{
$endpoint = self::BASE_URL . $endpoint;
if ($method === 'GET') {
$response = $this->getInstance()->getClient()?->request(
method: $method,
endpoint: $endpoint,
query_string: $queryString
);
} else {
$response = $this->getInstance()->getClient()->request($method, $endpoint, [
'json' => ($payload),
]);
}
$max = $response->getHeaderLine('X-HubSpot-RateLimit-Max'); // "110"
$remaining = $response->getHeaderLine('X-HubSpot-RateLimit-Remaining'); // "109"
$interval = $response->getHeaderLine('X-HubSpot-RateLimit-Interval-Milliseconds'); // "10000"
$body = json_decode((string) $response->getBody(), true);
\Illuminate\Support\Facades\Log::channel('custom_channel')->info('$max ' . PHP_EOL . print_r($max, true));
\Illuminate\Support\Facades\Log::channel('custom_channel')->info('$remaining ' . PHP_EOL . print_r($remaining, true));
\Illuminate\Support\Facades\Log::channel('custom_channel')->info('$interval ' . PHP_EOL . print_r($interval, true));
\Illuminate\Support\Facades\Log::channel('custom_channel')->info('$body ' . PHP_EOL . print_r($body, true));
return $response;
}
/**
* @throws BadRequest
* @throws HubspotException
*/
public function createMeeting(array $payload): Response
{
$endpoint = '/crm/v3/objects/meetings';
return $this->makeRequest($endpoint, 'POST', $payload);
}
/**
* @throws BadRequest
* @throws HubspotException
*/
public function updateMeeting(string $meetingId, array $payload): Response
{
$endpoint = '/crm/v3/objects/meetings/' . $meetingId;
return $this->makeRequest($endpoint, 'PATCH', $payload);
}
/**
* @throws \Exception
*/
public function createNote(
string $body,
string $ownerId,
int $timestamp,
string $objectId,
NoteObject $noteObject
): ?string {
try {
$noteInput = new SimplePublicObjectInput([
'properties' => [
'hs_note_body' => $body,
'hubspot_owner_id' => $ownerId,
'hs_timestamp' => $timestamp,
],
]);
// Create note
$note = $this->getNewInstance()->crm()->objects()->basicApi()->create('note', $noteInput);
$this->getNewInstance()->crm()->objects()->associationsApi()->create(
'note',
$note->getId(),
$this->getNoteObject($noteObject),
$objectId,
$this->getNoteAssociationType($noteObject),
);
return $note->getId();
} catch (\Exception $e) {
$this->log->error('[Hubspot] Failed to create note', [
'objectId' => $objectId,
'noteObject' => $noteObject->getObjectType(),
'reason' => $e->getMessage(),
]);
\Sentry::captureException($e);
}
return null;
}
public function updateEngagement(string $objectId, array $engagement, array $metadata): void
{
$this->getInstance()->engagements()->update($objectId, $engagement, $metadata);
}
public function getEngagementData(string $engagementId): array
{
$engagement = $this->getInstance()->engagements()->get($engagementId);
return $engagement->toArray();
}
public function createEngagement(array $engagement, array $associations, array $metadata): Response
{
return $this->getInstance()
->engagements()
->create($engagement, $associations, $metadata);
}
public function isUnauthorizedException(\Exception $e): bool
{
// Check for specific HubSpot API exception types first
if ($e instanceof BadRequest) {
// BadRequest can contain 401 status codes
return $e->getCode() === 401;
}
// Check for HTTP client exceptions with status codes
if ($e instanceof \GuzzleHttp\Exception\RequestException && $e->hasResponse()) {
$response = $e->getResponse();
if ($response !== null) {
return $response->getStatusCode() === 401;
}
}
// Check for Guzzle HTTP exceptions
if ($e instanceof \GuzzleHttp\Exception\ClientException) {
return $e->getCode() === 401;
}
// Fallback to string matching as last resort, but be more specific
$message = strtolower($e->getMessage());
return str_contains($message, '401 unauthorized') ||
str_contains($message, 'http 401') ||
str_contains($message, 'status code 401') ||
(preg_match('/\b401\b/', $message) && str_contains($message, 'unauthorized'));
}
/**
* Validates and refreshes the access token if needed before API requests.
* This ensures long-running processes don't fail due to token expiration.
*
* @throws SocialAccountTokenInvalidException
*/
public function ensureValidToken(): void
{
if ($this->oauthAccount === null) {
return;
}
$newToken = $this->tokenManager->ensureValidToken($this->oauthAccount);
if ($newToken !== null) {
$this->accessToken = $newToken;
}
}
public function getConfig()
{
return $this->config;
}
// returns only active (archived=false)
public function getOwners(): array
{
return $this->getNewInstance()->crm()->owners()->getAll();
}
/**
* @param bool $archived
*
* @return array<Owner>|[]
*/
public function getOwnersArchived(bool $archived = true): array
{
$endpoint = '/crm/v3/owners';
$queryParams = [
'archived' => $archived ? 'true' : 'false',
];
$queryString = http_build_query($queryParams);
$owners = [];
try {
$response = $this->makeRequest(endpoint: $endpoint, queryString: $queryString);
$responseData = $response?->toArray();
foreach ($responseData['results'] as $result) {
try {
$owners[] = Owner::create($result);
} catch (Throwable $e) {
$this->log->error('[HubSpot] Failed to process owner data', [
'result' => $result,
'error' => $e->getMessage(),
]);
continue;
}
}
} catch (Throwable $e) {
$this->log->error('HubSpot] Failed to fetch owners', [
'archived' => $archived,
'error' => $e->getMessage(),
]);
return [];
}
return $owners;
}
public function getMeeting(string $engagementId): ObjectWithAssociations
{
return $this->getNewInstance()->crm()->objects()->basicApi()
->getById('meeting', $engagementId, null, 'contact,company,deal');
}
public function deleteEngagement(string $engagementId): void
{
$this->getInstance()->engagements()->delete((int) $engagementId);
}
public function getAssociationsData(array $ids, string $fromObject, string $toObject): array
{
$associationData = [];
$idChunks = array_chunk($ids, self::ASSOCIATIONS_BATCH_SIZE_LIMIT);
foreach ($idChunks as $idChunk) {
try {
$batchInput = new \HubSpot\Client\Crm\Associations\Model\BatchInputPublicObjectId();
$batchInput->setInputs(array_map(function ($id) {
$publicObjectId = new \HubSpot\Client\Crm\Associations\Model\PublicObjectId();
$publicObjectId->setId($id);
return $publicObjectId;
}, $idChunk));
$associatedObjectsData = $this
->getNewInstance()
->crm()
->associations()
->batchApi()
->read($fromObject, $toObject, $batchInput);
if ($associatedObjectsData instanceof \HubSpot\Client\Crm\Associations\Model\BatchResponsePublicAssociationMulti) {
foreach ($associatedObjectsData->getResults() as $association) {
$from = $association->getFrom()->getId();
$toAssociations = $association->getTo();
if (! empty($toAssociations)) {
$associationData[$from] = array_map(function ($item) {
return $item->getId();
}, $toAssociations);
}
}
}
} catch (\Exception $e) {
$this->log->error('[Hubspot] Failed to fetch associations', [
'from_object' => $fromObject,
'to_object' => $toObject,
'reason' => $e->getMessage(),
]);
}
}
return $associationData;
}
/**
* @throws \Exception
*/
private function getNoteAssociationType(NoteObject $noteObject): string
{
return match($noteObject) {
NoteObject::Opportunity => 'note_to_deal',
NoteObject::Lead, NoteObject::Contact => 'note_to_contact', // or 'note_to_lead' if your portal supports it
NoteObject::Account => 'note_to_company',
NoteObject::Call, NoteObject::Event => throw new \Exception('Not supported'),
};
}
/**
* @throws \Exception
*/
private function getNoteObject(NoteObject $noteObject): string
{
return match($noteObject) {
NoteObject::Opportunity => 'deal',
NoteObject::Lead, NoteObject::Contact => 'contact',
NoteObject::Account => 'company',
NoteObject::Call, NoteObject::Event => throw new \Exception('Not supported'),
};
}
public function addAssociations(string $objectType, string $associationType, array $payload): Response
{
$endpoint = "/crm/v4/associations/$objectType/$associationType/batch/create";
return $this->makeRequest($endpoint, 'POST', $payload);
}
public function removeAssociations(string $objectType, string $associationType, array $payload): Response
{
$endpoint = "/crm/v4/associations/$objectType/$associationType/batch/archive";
return $this->makeRequest($endpoint, 'POST', $payload);
}
}
Project
Project
New File or Directory…
Expand Selected
Collapse All
Options
Hide...
|
NULL
|
NULL
|
NULL
|
NULL
|
|
2725
|
112
|
34
|
2026-05-07T11:37:38.838339+00:00
|
/Users/lukas/.screenpipe/data/data/2026-05-07/1778 /Users/lukas/.screenpipe/data/data/2026-05-07/1778153858838_m2.jpg...
|
PhpStorm
|
faVsco.js – custom.log
|
True
|
NULL
|
monitor_2
|
NULL
|
NULL
|
NULL
|
NULL
|
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
Editor for custom.log
Sync Changes
Hide This Notification
Code changed:
Hide
2
60
Previous Highlighted Error
Next Highlighted Error
<?php
declare(strict_types=1);
namespace Jiminny\Services\Crm\Hubspot;
use HubSpot\Client\Crm\Deals\ApiException as DealApiException;
use HubSpot\Client\Crm\Contacts\ApiException as ContactApiException;
use HubSpot\Client\Crm\Companies\ApiException as CompanyApiException;
use HubSpot\Client\Crm\Contacts\Model\SimplePublicObjectWithAssociations as ContactsWithAssociations;
use HubSpot\Client\Crm\Companies\Model\SimplePublicObjectWithAssociations as CompaniesWithAssociations;
use HubSpot\Client\Crm\Deals\Model\SimplePublicObjectWithAssociations as DealWithAssociations;
use HubSpot\Client\Crm\Objects\Model\SimplePublicObjectInput;
use HubSpot\Client\Crm\Objects\Model\SimplePublicObjectWithAssociations as ObjectWithAssociations;
use HubSpot\Client\Crm\Pipelines\Model\Error;
use HubSpot\Client\Crm\Pipelines\Model\PipelineStage;
use HubSpot\Client\Crm\Properties\Model\Property;
use HubSpot\Discovery\Discovery;
use Jiminny\Component\Utility\Service\ProviderRateLimiter;
use Jiminny\Exceptions\CrmException;
use Jiminny\Exceptions\RateLimitException;
use Jiminny\Exceptions\SocialAccountTokenInvalidException;
use Jiminny\Jobs\Crm\NoteObject;
use Jiminny\Models\Crm\Field;
use Jiminny\Services\Crm\BaseClient;
use Jiminny\Services\Crm\Hubspot\DTO\Response\Owner;
use Jiminny\Services\SocialAccountService;
use SevenShores\Hubspot\Exceptions\BadRequest;
use SevenShores\Hubspot\Exceptions\HubspotException;
use SevenShores\Hubspot\Factory;
use SevenShores\Hubspot\Http\Response;
use Jiminny\Services\Crm\Hubspot\Pagination\HubspotPaginationService;
use Throwable;
/**
* @phpstan-type CrmFieldOption array{id:string, label:string, value?:string}
*/
class Client extends BaseClient implements HubspotClientInterface
{
public const string MIN_API_VERSION = '2';
public const string BASE_URL = '[URL_WITH_CREDENTIALS] T
* @param callable(): T $apiCall
* @return T
*
* @throws RateLimitException
*/
private function executeRequest(callable $apiCall)
{
if (! $this->rateLimiter->canMakeRequest($this->config)) {
$retryAfter = $this->rateLimiter->requestAvailableIn($this->config);
$this->log->warning('[Hubspot] Rate limit exceeded, deferring request', [
'team_id' => $this->config->team_id,
'config_id' => $this->config->getId(),
'retry_after' => $retryAfter,
]);
throw new RateLimitException(
'Hubspot rate limit reached for configuration ' . $this->config->getId(),
$retryAfter,
);
}
$this->rateLimiter->incrementRequestCount($this->config);
try {
return $apiCall();
} catch (Throwable $e) {
if ($this->isHubspotRateLimit($e)) {
$retryAfter = $this->parseRetryAfter($e);
$this->log->warning('[Hubspot] Received 429 from API', [
'team_id' => $this->config->team_id,
'config_id' => $this->config->getId(),
'retry_after' => $retryAfter,
'reason' => $e->getMessage(),
]);
throw new RateLimitException('Hubspot returned 429', $retryAfter, $e);
}
throw $e;
}
}
private function isHubspotRateLimit(Throwable $e): bool
{
return method_exists($e, 'getCode') && (int) $e->getCode() === 429;
}
private function parseRetryAfter(Throwable $e): int
{
if (method_exists($e, 'getResponseHeaders')) {
$headers = $e->getResponseHeaders() ?: [];
$value = $headers['Retry-After'] ?? $headers['retry-after'] ?? null;
if (is_array($value)) {
$value = $value[0] ?? null;
}
if (is_numeric($value)) {
return (int) $value;
}
}
return 10;
}
public function getMinimumApiVersion(): string
{
return self::MIN_API_VERSION;
}
public function getInstance(): Factory
{
return new Factory([
'key' => $this->accessToken,
'oauth2' => true,
'base_url' => $this->baseUrl,
]);
}
public function getNewInstance(): Discovery
{
return \HubSpot\Factory::createWithAccessToken($this->accessToken);
}
/**
* Secondly and daily limits for Hubspot API
*
* Product Tier: Free & Starter | Professional & Enterprise | API add-on (any tier)
* Burst: 100/10 seconds | 150/10 seconds | 200/10 seconds
* Daily: 250,000 | 500,000 | 1,000,000
*
* Official documentation states: The search endpoints are rate limited to five requests per second.
* Since with 5 RPS were still hitting secondly rate limits we lowered it to 4
*/
public function getPaginatedData(array $payload, string $type, int $offset = 0): array
{
$total = 0;
$lastId = null;
$rows = [];
foreach ($this->getPaginatedDataGenerator($payload, $type, $offset, $total, $lastId) as $row) {
$rows[] = $row;
}
return ['results' => $rows, 'total' => $total, 'last_record' => $lastId];
}
/**
* @throws HubspotException
* @throws SocialAccountTokenInvalidException
* @throws BadRequest
*/
public function getPaginatedDataGenerator(
array $payload,
string $type,
int $offset = 0,
int &$total = 0,
?string &$lastRecordId = null
): \Generator {
return $this->paginationService->getPaginatedDataGenerator(
$this,
$payload,
$type,
$offset,
$total,
$lastRecordId
);
}
/**
* @throws DealApiException
* @throws CrmException
*/
public function getOpportunityById(string $crmId, array $fields): array
{
try {
$deal = $this->executeRequest(fn () => $this->getNewInstance()->crm()->deals()->basicApi()->getById(
$crmId,
implode(',', $fields),
'companies,contacts'
));
} catch (DealApiException $e) {
$this->log->info('[Hubspot] Failed to fetch opportunity', [
'crm_id' => $crmId,
'reason' => $e->getMessage(),
]);
throw $e;
}
if (! $deal instanceof DealWithAssociations) {
throw new CrmException('Deal not found');
}
return [
'id' => $deal->getId(),
'properties' => $deal->getProperties(),
'associations' => $deal->getAssociations(),
];
}
/**
* Generic batch read method for HubSpot objects
*
* @param string $objectType The object type ('deals', 'companies', 'contacts')
* @param array<string> $crmIds Array of HubSpot object IDs (max 100)
* @param array<string> $fields Array of property names to fetch
*
* @return array<string, array> Array keyed by CRM ID with object data
*/
private function batchReadObjects(string $objectType, array $crmIds, array $fields): array
{
if (empty($crmIds)) {
return [];
}
$this->validateBatchSize($objectType, $crmIds);
$this->ensureValidToken();
try {
$batchConfig = $this->createBatchConfiguration($objectType);
$batchReadRequest = $this->prepareBatchRequest($batchConfig, $crmIds, $fields);
$response = $batchConfig['api']->read($batchReadRequest);
$this->validateApiResponse($response, $objectType);
$results = $this->processApiResults($response);
$this->logBatchResults($objectType, $crmIds, $results);
return $results;
} catch (\Throwable $e) {
$this->handleBatchError($e, $objectType, $crmIds);
}
}
private function validateBatchSize(string $objectType, array $crmIds): void
{
if (count($crmIds) > 100) {
throw new \InvalidArgumentException("Batch size cannot exceed 100 {$objectType}");
}
}
private function createBatchConfiguration(string $objectType): array
{
$configurations = [
'deals' => [
'batchReadRequest' => new \HubSpot\Client\Crm\Deals\Model\BatchReadInputSimplePublicObjectId(),
'inputClass' => \HubSpot\Client\Crm\Deals\Model\SimplePublicObjectId::class,
'api' => $this->getNewInstance()->crm()->deals()->batchApi(),
],
'companies' => [
'batchReadRequest' => new \HubSpot\Client\Crm\Companies\Model\BatchReadInputSimplePublicObjectId(),
'inputClass' => \HubSpot\Client\Crm\Companies\Model\SimplePublicObjectId::class,
'api' => $this->getNewInstance()->crm()->companies()->batchApi(),
],
'contacts' => [
'batchReadRequest' => new \HubSpot\Client\Crm\Contacts\Model\BatchReadInputSimplePublicObjectId(),
'inputClass' => \HubSpot\Client\Crm\Contacts\Model\SimplePublicObjectId::class,
'api' => $this->getNewInstance()->crm()->contacts()->batchApi(),
],
];
if (! isset($configurations[$objectType])) {
throw new \InvalidArgumentException("Unsupported object type: {$objectType}");
}
return $configurations[$objectType];
}
private function prepareBatchRequest(array $batchConfig, array $crmIds, array $fields): object
{
$batchReadRequest = $batchConfig['batchReadRequest'];
$inputClass = $batchConfig['inputClass'];
$inputs = array_map(function ($crmId) use ($inputClass) {
$input = new $inputClass();
$input->setId($crmId);
return $input;
}, $crmIds);
$batchReadRequest->setInputs($inputs);
$batchReadRequest->setProperties($fields);
return $batchReadRequest;
}
private function validateApiResponse($response, string $objectType): void
{
if (! $response) {
throw new CrmException("HubSpot API returned null response for {$objectType} batch read");
}
}
private function processApiResults($response): array
{
$results = [];
$responseResults = $response->getResults();
if ($responseResults) {
foreach ($responseResults as $object) {
if ($object && $object->getId()) {
$results[$object->getId()] = [
'id' => $object->getId(),
'properties' => $object->getProperties() ?: [],
];
}
}
}
return $results;
}
private function logBatchResults(string $objectType, array $crmIds, array $results): void
{
$this->log->info("[HubSpot] Batch fetched {$objectType}", [
'requested_count' => count($crmIds),
'returned_count' => count($results),
'crm_ids' => $crmIds,
]);
}
private function handleBatchError(\Throwable $e, string $objectType, array $crmIds): void
{
$errorMessage = $e->getMessage() ?: 'Unknown error';
$errorTrace = $e->getTraceAsString() ?: 'No trace available';
$this->log->error("[HubSpot] Failed to batch fetch {$objectType}", [
'crm_ids' => $crmIds,
'error' => $errorMessage,
'trace' => $errorTrace,
]);
throw new CrmException("Failed to batch fetch {$objectType}: " . $errorMessage);
}
/**
* Batch read multiple opportunities by their CRM IDs
*
* @param array<string> $crmIds Array of HubSpot deal IDs (max 100)
* @param array<string> $fields Array of property names to fetch
*
* @return array<string, array> Array keyed by CRM ID with opportunity data
*/
public function getOpportunitiesByIds(array $crmIds, array $fields): array
{
return $this->batchReadObjects('deals', $crmIds, $fields);
}
/**
* Batch read multiple companies by their CRM IDs
*
* @param array<string> $crmIds Array of HubSpot company IDs (max 100)
* @param array<string> $fields Array of property names to fetch
*
* @return array<string, array> Array keyed by CRM ID with company data
*/
public function getCompaniesByIds(array $crmIds, array $fields): array
{
return $this->batchReadObjects('companies', $crmIds, $fields);
}
/**
* Batch read multiple contacts by their CRM IDs
*
* @param array<string> $crmIds Array of HubSpot contact IDs (max 100)
* @param array<string> $fields Array of property names to fetch
*
* @return array<string, array> Array keyed by CRM ID with contact data
*/
public function getContactsByIds(array $crmIds, array $fields): array
{
return $this->batchReadObjects('contacts', $crmIds, $fields);
}
/**
* @throws CompanyApiException
* @throws CrmException
*/
public function getAccountById(string $crmId, array $fields): array
{
try {
$company = $this->getNewInstance()->crm()->companies()->basicApi()->getById(
$crmId,
implode(',', $fields),
);
} catch (CompanyApiException $e) {
$this->log->info('[Hubspot] Failed to fetch account', [
'crm_id' => $crmId,
'reason' => $e->getMessage(),
]);
throw $e;
}
if (! $company instanceof CompaniesWithAssociations) {
throw new CrmException('Account not found');
}
return [
'id' => $company->getId(),
'properties' => $company->getProperties(),
];
}
/**
* @throws ContactApiException
* @throws CrmException
*/
public function getContactById(string $crmId, array $fields): array
{
try {
$contact = $this->getNewInstance()->crm()->contacts()->basicApi()->getById(
$crmId,
implode(',', $fields)
);
} catch (ContactApiException $e) {
$this->log->info('[Hubspot] Failed to fetch contact', [
'crm_id' => $crmId,
'reason' => $e->getMessage(),
]);
throw $e;
}
if (! $contact instanceof ContactsWithAssociations) {
throw new CrmException('Contact not found');
}
return [
'id' => $contact->getId(),
'properties' => $contact->getProperties(),
];
}
/**
* This is email search request that Hubspot offers as GET (more generous quota)
*/
public function getContactByEmail(string $email, array $fields = []): array
{
try {
$contact = $this->getNewInstance()->crm()->contacts()->basicApi()->getById(
$email,
implode(',', $fields),
null,
false,
'email'
);
return [
'id' => $contact->getId(),
'properties' => $contact->getProperties(),
];
} catch (ContactApiException $e) {
$this->log->info('[Hubspot] Failed to fetch contact', [
'email' => $email,
'reason' => $e->getMessage(),
]);
return [];
}
}
/**
* @throws CrmException
*/
public function fetchProperty(string $objectType, string $propertyId): Property
{
$result = $this->getNewInstance()->crm()->properties()->coreApi()->getByName($objectType, $propertyId);
if (! $result instanceof Property) {
$this->log->error('[Hubspot] Failed to fetch property', [
'object_type' => $objectType,
'property_id' => $propertyId,
'reason' => $result->getMessage(),
]);
throw new CrmException('Failed to fetch property');
}
return $result;
}
/**
* @return array<CrmFieldOption>
*/
public function fetchPropertyOptions(string $objectType, string $propertyId): array
{
/** @var array<CrmFieldOption> */
return $this->fetchProperty($objectType, $propertyId)->getOptions();
}
/**
* @return array<array{id:string, label:string, deleted:bool}>
*/
public function fetchCallDispositions(): array
{
/** @var Response $response */
$response = $this->getInstance()->engagements()->getCallDispositions();
/**
* @var array<array{
* id:string,
* label:string,
* deleted: bool
* }>
*/
return $response->toArray();
}
/**
* @return array<CrmFieldOption>
*/
public function fetchOpportunityPipelineStages(): array
{
$stages = [];
$apiResponse = $this->getNewInstance()->crm()->pipelines()->pipelinesApi()->getAll('deals');
if ($apiResponse instanceof Error) {
$this->log->error('[Hubspot] Failed to fetch opportunity pipelines', [
'reason' => $apiResponse->getMessage(),
]);
return [];
}
foreach ($apiResponse->getResults() as $pipeline) {
$pipelineStages = array_map(
static function (PipelineStage $stage) {
return [
'id' => $stage->getId(),
'label' => $stage->getLabel(),
];
},
$pipeline->getStages()
);
$stages = array_merge($stages, $pipelineStages);
}
return $stages;
}
public function fetchOpportunityPipelines(): array
{
$pipelines = [];
try {
$apiResponse = $this->makeRequest('/crm/v3/pipelines/deals');
} catch (\Exception $e) {
$this->log->info('[Hubspot] Failed to fetch opportunity pipelines', [
'reason' => $e->getMessage(),
]);
return [];
}
$response = $apiResponse->toArray();
foreach ($response['results'] as $pipeline) {
$pipelines[] = [
'id' => $pipeline['id'],
'label' => $pipeline['label'],
];
}
return $pipelines;
}
/**
* @return array<CrmFieldOption>
*/
public function fetchMeetingOutcomeFieldOptions(Field $field): array
{
return $field->getCrmProviderId() === 'meetingOutcome'
? $this->fetchMeetingOutcomeTypes()
: $this->fetchCallActivityTypes();
}
public function fetchMeetingOutcomeTypes(): array
{
return $this->extractMeetingTypeOptions(
'[URL_WITH_CREDENTIALS] Response $response */
$response = $this->getInstance()
->getClient()
->request('GET', $endpoint);
/**
* @var array<array{
* value: string,
* label: string,
* displayOrder: int
* }> $optionData
*/
$optionData = $response->toArray()['options'] ?? [];
$options = [];
foreach ($optionData as $item) {
$options[] = [
'id' => $item['value'],
'value' => $item['value'],
'label' => $item['label'],
'display_order' => $item['displayOrder'],
];
}
return $options;
}
/**
* @return array<CrmFieldOption>
*/
public function fetchDispositionFieldOptions(): array
{
$options = [];
$dispositions = $this->fetchCallDispositions();
foreach ($dispositions as $disposition) {
if ($disposition['deleted'] !== false) {
continue;
}
$option['value'] = $disposition['id'];
$option['id'] = $disposition['id'];
$option['label'] = $disposition['label'];
$options[] = $option;
}
return $options;
}
/**
* @return array<CrmFieldOption>
*/
public function fetchOpportunityFieldOptions(Field $field): array
{
if ($field->isStageField()) {
return $this->fetchOpportunityPipelineStages();
}
if ($field->isPipelineField()) {
return $this->fetchOpportunityPipelines();
}
return $this->fetchPropertyOptions('deals', $field->getCrmProviderId());
}
/**
* @throws BadRequest
* @throws HubspotException
*/
public function makeRequest(string $endpoint, $method = 'GET', $payload = [], ?string $queryString = null)
{
$endpoint = self::BASE_URL . $endpoint;
if ($method === 'GET') {
$response = $this->getInstance()->getClient()?->request(
method: $method,
endpoint: $endpoint,
query_string: $queryString
);
} else {
$response = $this->getInstance()->getClient()->request($method, $endpoint, [
'json' => ($payload),
]);
}
$max = $response->getHeaderLine('X-HubSpot-RateLimit-Max'); // "110"
$remaining = $response->getHeaderLine('X-HubSpot-RateLimit-Remaining'); // "109"
$interval = $response->getHeaderLine('X-HubSpot-RateLimit-Interval-Milliseconds'); // "10000"
$body = json_decode((string) $response->getBody(), true);
\Illuminate\Support\Facades\Log::channel('custom_channel')->info('$max ' . PHP_EOL . print_r($max, true));
\Illuminate\Support\Facades\Log::channel('custom_channel')->info('$remaining ' . PHP_EOL . print_r($remaining, true));
\Illuminate\Support\Facades\Log::channel('custom_channel')->info('$interval ' . PHP_EOL . print_r($interval, true));
\Illuminate\Support\Facades\Log::channel('custom_channel')->info('$body ' . PHP_EOL . print_r($body, true));
return $response;
}
/**
* @throws BadRequest
* @throws HubspotException
*/
public function createMeeting(array $payload): Response
{
$endpoint = '/crm/v3/objects/meetings';
return $this->makeRequest($endpoint, 'POST', $payload);
}
/**
* @throws BadRequest
* @throws HubspotException
*/
public function updateMeeting(string $meetingId, array $payload): Response
{
$endpoint = '/crm/v3/objects/meetings/' . $meetingId;
return $this->makeRequest($endpoint, 'PATCH', $payload);
}
/**
* @throws \Exception
*/
public function createNote(
string $body,
string $ownerId,
int $timestamp,
string $objectId,
NoteObject $noteObject
): ?string {
try {
$noteInput = new SimplePublicObjectInput([
'properties' => [
'hs_note_body' => $body,
'hubspot_owner_id' => $ownerId,
'hs_timestamp' => $timestamp,
],
]);
// Create note
$note = $this->getNewInstance()->crm()->objects()->basicApi()->create('note', $noteInput);
$this->getNewInstance()->crm()->objects()->associationsApi()->create(
'note',
$note->getId(),
$this->getNoteObject($noteObject),
$objectId,
$this->getNoteAssociationType($noteObject),
);
return $note->getId();
} catch (\Exception $e) {
$this->log->error('[Hubspot] Failed to create note', [
'objectId' => $objectId,
'noteObject' => $noteObject->getObjectType(),
'reason' => $e->getMessage(),
]);
\Sentry::captureException($e);
}
return null;
}
public function updateEngagement(string $objectId, array $engagement, array $metadata): void
{
$this->getInstance()->engagements()->update($objectId, $engagement, $metadata);
}
public function getEngagementData(string $engagementId): array
{
$engagement = $this->getInstance()->engagements()->get($engagementId);
return $engagement->toArray();
}
public function createEngagement(array $engagement, array $associations, array $metadata): Response
{
return $this->getInstance()
->engagements()
->create($engagement, $associations, $metadata);
}
public function isUnauthorizedException(\Exception $e): bool
{
// Check for specific HubSpot API exception types first
if ($e instanceof BadRequest) {
// BadRequest can contain 401 status codes
return $e->getCode() === 401;
}
// Check for HTTP client exceptions with status codes
if ($e instanceof \GuzzleHttp\Exception\RequestException && $e->hasResponse()) {
$response = $e->getResponse();
if ($response !== null) {
return $response->getStatusCode() === 401;
}
}
// Check for Guzzle HTTP exceptions
if ($e instanceof \GuzzleHttp\Exception\ClientException) {
return $e->getCode() === 401;
}
// Fallback to string matching as last resort, but be more specific
$message = strtolower($e->getMessage());
return str_contains($message, '401 unauthorized') ||
str_contains($message, 'http 401') ||
str_contains($message, 'status code 401') ||
(preg_match('/\b401\b/', $message) && str_contains($message, 'unauthorized'));
}
/**
* Validates and refreshes the access token if needed before API requests.
* This ensures long-running processes don't fail due to token expiration.
*
* @throws SocialAccountTokenInvalidException
*/
public function ensureValidToken(): void
{
if ($this->oauthAccount === null) {
return;
}
$newToken = $this->tokenManager->ensureValidToken($this->oauthAccount);
if ($newToken !== null) {
$this->accessToken = $newToken;
}
}
public function getConfig()
{
return $this->config;
}
// returns only active (archived=false)
public function getOwners(): array
{
return $this->getNewInstance()->crm()->owners()->getAll();
}
/**
* @param bool $archived
*
* @return array<Owner>|[]
*/
public function getOwnersArchived(bool $archived = true): array
{
$endpoint = '/crm/v3/owners';
$queryParams = [
'archived' => $archived ? 'true' : 'false',
];
$queryString = http_build_query($queryParams);
$owners = [];
try {
$response = $this->makeRequest(endpoint: $endpoint, queryString: $queryString);
$responseData = $response?->toArray();
foreach ($responseData['results'] as $result) {
try {
$owners[] = Owner::create($result);
} catch (Throwable $e) {
$this->log->error('[HubSpot] Failed to process owner data', [
'result' => $result,
'error' => $e->getMessage(),
]);
continue;
}
}
} catch (Throwable $e) {
$this->log->error('HubSpot] Failed to fetch owners', [
'archived' => $archived,
'error' => $e->getMessage(),
]);
return [];
}
return $owners;
}
public function getMeeting(string $engagementId): ObjectWithAssociations
{
return $this->getNewInstance()->crm()->objects()->basicApi()
->getById('meeting', $engagementId, null, 'contact,company,deal');
}
public function deleteEngagement(string $engagementId): void
{
$this->getInstance()->engagements()->delete((int) $engagementId);
}
public function getAssociationsData(array $ids, string $fromObject, string $toObject): array
{
$associationData = [];
$idChunks = array_chunk($ids, self::ASSOCIATIONS_BATCH_SIZE_LIMIT);
foreach ($idChunks as $idChunk) {
try {
$batchInput = new \HubSpot\Client\Crm\Associations\Model\BatchInputPublicObjectId();
$batchInput->setInputs(array_map(function ($id) {
$publicObjectId = new \HubSpot\Client\Crm\Associations\Model\PublicObjectId();
$publicObjectId->setId($id);
return $publicObjectId;
}, $idChunk));
$associatedObjectsData = $this
->getNewInstance()
->crm()
->associations()
->batchApi()
->read($fromObject, $toObject, $batchInput);
if ($associatedObjectsData instanceof \HubSpot\Client\Crm\Associations\Model\BatchResponsePublicAssociationMulti) {
foreach ($associatedObjectsData->getResults() as $association) {
$from = $association->getFrom()->getId();
$toAssociations = $association->getTo();
if (! empty($toAssociations)) {
$associationData[$from] = array_map(function ($item) {
return $item->getId();
}, $toAssociations);
}
}
}
} catch (\Exception $e) {
$this->log->error('[Hubspot] Failed to fetch associations', [
'from_object' => $fromObject,
'to_object' => $toObject,
'reason' => $e->getMessage(),
]);
}
}
return $associationData;
}
/**
* @throws \Exception
*/
private function getNoteAssociationType(NoteObject $noteObject): string
{
return match($noteObject) {
NoteObject::Opportunity => 'note_to_deal',
NoteObject::Lead, NoteObject::Contact => 'note_to_contact', // or 'note_to_lead' if your portal supports it
NoteObject::Account => 'note_to_company',
NoteObject::Call, NoteObject::Event => throw new \Exception('Not supported'),
};
}
/**
* @throws \Exception
*/
private function getNoteObject(NoteObject $noteObject): string
{
return match($noteObject) {
NoteObject::Opportunity => 'deal',
NoteObject::Lead, NoteObject::Contact => 'contact',
NoteObject::Account => 'company',
NoteObject::Call, NoteObject::Event => throw new \Exception('Not supported'),
};
}
public function addAssociations(string $objectType, string $associationType, array $payload): Response
{
$endpoint = "/crm/v4/associations/$objectType/$associationType/batch/create";
return $this->makeRequest($endpoint, 'POST', $payload);
}
public function removeAssociations(string $objectType, string $associationType, array $payload): Response
{
$endpoint = "/crm/v4/associations/$objectType/$associationType/batch/archive";
return $this->makeRequest($endpoint, 'POST', $payload);
}
}
Project
Project
New File or Directory…
Expand Selected
Collapse All
Options
Hide...
|
[{"role":"AXButton","text" [{"role":"AXButton","text":"Project: faVsco.js, menu","depth":5,"bounds":{"left":0.025930852,"top":0.019952115,"width":0.03856383,"height":0.025538707},"on_screen":true,"help_text":"~/jiminny/app","role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"master, menu","depth":5,"bounds":{"left":0.064494684,"top":0.019952115,"width":0.034242023,"height":0.025538707},"on_screen":true,"help_text":"Git Branch: master","role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Start Listening for PHP Debug Connections","depth":5,"bounds":{"left":0.8081782,"top":0.019952115,"width":0.011303191,"height":0.025538707},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"AskJiminnyReportActivityServiceTest","depth":6,"bounds":{"left":0.8234708,"top":0.019952115,"width":0.09208777,"height":0.025538707},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Run 'AskJiminnyReportActivityServiceTest'","depth":6,"bounds":{"left":0.9155585,"top":0.019952115,"width":0.011303191,"height":0.025538707},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Debug 'AskJiminnyReportActivityServiceTest'","depth":6,"bounds":{"left":0.9268617,"top":0.019952115,"width":0.011303191,"height":0.025538707},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"More Actions","depth":6,"bounds":{"left":0.9381649,"top":0.019952115,"width":0.011303191,"height":0.025538707},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"JetBrains AI","depth":5,"bounds":{"left":0.96609044,"top":0.019952115,"width":0.011303191,"height":0.025538707},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Search Everywhere","depth":5,"bounds":{"left":0.9773936,"top":0.019952115,"width":0.011303191,"height":0.025538707},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"IDE and Project Settings","depth":5,"bounds":{"left":0.9886968,"top":0.019952115,"width":0.011303186,"height":0.025538707},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Sync Changes","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.008643617,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Hide This Notification","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.008643617,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Code changed:","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.042220745,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Hide","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.008643617,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXTextArea","text":"Editor for custom.log","depth":4,"bounds":{"left":0.5475399,"top":0.0726257,"width":0.44082448,"height":0.9066241},"on_screen":true,"role_description":"text entry area","is_enabled":true,"is_focused":true,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Sync Changes","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.008643617,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Hide This Notification","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.008643617,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Code changed:","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.042220745,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Hide","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.008643617,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"2","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.007978723,"height":0.0},"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"60","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.010305851,"height":0.0},"on_screen":false,"role_description":"text"},{"role":"AXButton","text":"Previous Highlighted Error","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.00731383,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Next Highlighted Error","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.006981383,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXTextArea","text":"<?php\n\ndeclare(strict_types=1);\n\nnamespace Jiminny\\Services\\Crm\\Hubspot;\n\nuse HubSpot\\Client\\Crm\\Deals\\ApiException as DealApiException;\nuse HubSpot\\Client\\Crm\\Contacts\\ApiException as ContactApiException;\nuse HubSpot\\Client\\Crm\\Companies\\ApiException as CompanyApiException;\nuse HubSpot\\Client\\Crm\\Contacts\\Model\\SimplePublicObjectWithAssociations as ContactsWithAssociations;\nuse HubSpot\\Client\\Crm\\Companies\\Model\\SimplePublicObjectWithAssociations as CompaniesWithAssociations;\nuse HubSpot\\Client\\Crm\\Deals\\Model\\SimplePublicObjectWithAssociations as DealWithAssociations;\nuse HubSpot\\Client\\Crm\\Objects\\Model\\SimplePublicObjectInput;\nuse HubSpot\\Client\\Crm\\Objects\\Model\\SimplePublicObjectWithAssociations as ObjectWithAssociations;\nuse HubSpot\\Client\\Crm\\Pipelines\\Model\\Error;\nuse HubSpot\\Client\\Crm\\Pipelines\\Model\\PipelineStage;\nuse HubSpot\\Client\\Crm\\Properties\\Model\\Property;\nuse HubSpot\\Discovery\\Discovery;\nuse Jiminny\\Component\\Utility\\Service\\ProviderRateLimiter;\nuse Jiminny\\Exceptions\\CrmException;\nuse Jiminny\\Exceptions\\RateLimitException;\nuse Jiminny\\Exceptions\\SocialAccountTokenInvalidException;\nuse Jiminny\\Jobs\\Crm\\NoteObject;\nuse Jiminny\\Models\\Crm\\Field;\nuse Jiminny\\Services\\Crm\\BaseClient;\nuse Jiminny\\Services\\Crm\\Hubspot\\DTO\\Response\\Owner;\nuse Jiminny\\Services\\SocialAccountService;\nuse SevenShores\\Hubspot\\Exceptions\\BadRequest;\nuse SevenShores\\Hubspot\\Exceptions\\HubspotException;\nuse SevenShores\\Hubspot\\Factory;\nuse SevenShores\\Hubspot\\Http\\Response;\nuse Jiminny\\Services\\Crm\\Hubspot\\Pagination\\HubspotPaginationService;\nuse Throwable;\n\n/**\n * @phpstan-type CrmFieldOption array{id:string, label:string, value?:string}\n */\nclass Client extends BaseClient implements HubspotClientInterface\n{\n public const string MIN_API_VERSION = '2';\n\n public const string BASE_URL = 'https://api.hubapi.com';\n\n public const int ASSOCIATIONS_BATCH_SIZE_LIMIT = 1000;\n\n private HubspotPaginationService $paginationService;\n private HubspotTokenManager $tokenManager;\n private ProviderRateLimiter $rateLimiter;\n\n public function __construct(\n SocialAccountService $socialAccountService,\n HubspotPaginationService $paginationService,\n HubspotTokenManager $tokenManager,\n ProviderRateLimiter $rateLimiter,\n ) {\n parent::__construct($socialAccountService);\n $this->paginationService = $paginationService;\n $this->tokenManager = $tokenManager;\n $this->rateLimiter = $rateLimiter;\n\n $this->setBaseUrl(self::BASE_URL);\n $this->setVersion(self::MIN_API_VERSION);\n }\n\n /**\n * Single entry point for every HubSpot API call. Enforces the per-portal\n * rate limit configured in the rate_limits table (morphed to the current\n * Configuration) and reacts to a real 429 from HubSpot by translating it\n * into a RateLimitException carrying Retry-After.\n *\n * Wrap any outbound HubSpot call (SDK or raw HTTP) like:\n *\n * $this->executeRequest(fn () => $this->getNewInstance()->crm()->...);\n *\n * @template T\n * @param callable(): T $apiCall\n * @return T\n *\n * @throws RateLimitException\n */\n private function executeRequest(callable $apiCall)\n {\n if (! $this->rateLimiter->canMakeRequest($this->config)) {\n $retryAfter = $this->rateLimiter->requestAvailableIn($this->config);\n\n $this->log->warning('[Hubspot] Rate limit exceeded, deferring request', [\n 'team_id' => $this->config->team_id,\n 'config_id' => $this->config->getId(),\n 'retry_after' => $retryAfter,\n ]);\n\n throw new RateLimitException(\n 'Hubspot rate limit reached for configuration ' . $this->config->getId(),\n $retryAfter,\n );\n }\n\n $this->rateLimiter->incrementRequestCount($this->config);\n\n try {\n return $apiCall();\n } catch (Throwable $e) {\n if ($this->isHubspotRateLimit($e)) {\n $retryAfter = $this->parseRetryAfter($e);\n\n $this->log->warning('[Hubspot] Received 429 from API', [\n 'team_id' => $this->config->team_id,\n 'config_id' => $this->config->getId(),\n 'retry_after' => $retryAfter,\n 'reason' => $e->getMessage(),\n ]);\n\n throw new RateLimitException('Hubspot returned 429', $retryAfter, $e);\n }\n\n throw $e;\n }\n }\n\n private function isHubspotRateLimit(Throwable $e): bool\n {\n return method_exists($e, 'getCode') && (int) $e->getCode() === 429;\n }\n\n private function parseRetryAfter(Throwable $e): int\n {\n if (method_exists($e, 'getResponseHeaders')) {\n $headers = $e->getResponseHeaders() ?: [];\n $value = $headers['Retry-After'] ?? $headers['retry-after'] ?? null;\n if (is_array($value)) {\n $value = $value[0] ?? null;\n }\n if (is_numeric($value)) {\n return (int) $value;\n }\n }\n\n return 10;\n }\n\n public function getMinimumApiVersion(): string\n {\n return self::MIN_API_VERSION;\n }\n\n public function getInstance(): Factory\n {\n return new Factory([\n 'key' => $this->accessToken,\n 'oauth2' => true,\n 'base_url' => $this->baseUrl,\n ]);\n }\n\n public function getNewInstance(): Discovery\n {\n return \\HubSpot\\Factory::createWithAccessToken($this->accessToken);\n }\n\n /**\n * Secondly and daily limits for Hubspot API\n *\n * Product Tier: Free & Starter | Professional & Enterprise | API add-on (any tier)\n * Burst: 100/10 seconds | 150/10 seconds | 200/10 seconds\n * Daily: 250,000 | 500,000 | 1,000,000\n *\n * Official documentation states: The search endpoints are rate limited to five requests per second.\n * Since with 5 RPS were still hitting secondly rate limits we lowered it to 4\n */\n public function getPaginatedData(array $payload, string $type, int $offset = 0): array\n {\n $total = 0;\n $lastId = null;\n $rows = [];\n foreach ($this->getPaginatedDataGenerator($payload, $type, $offset, $total, $lastId) as $row) {\n $rows[] = $row;\n }\n\n return ['results' => $rows, 'total' => $total, 'last_record' => $lastId];\n }\n\n /**\n * @throws HubspotException\n * @throws SocialAccountTokenInvalidException\n * @throws BadRequest\n */\n public function getPaginatedDataGenerator(\n array $payload,\n string $type,\n int $offset = 0,\n int &$total = 0,\n ?string &$lastRecordId = null\n ): \\Generator {\n return $this->paginationService->getPaginatedDataGenerator(\n $this,\n $payload,\n $type,\n $offset,\n $total,\n $lastRecordId\n );\n }\n\n /**\n * @throws DealApiException\n * @throws CrmException\n */\n public function getOpportunityById(string $crmId, array $fields): array\n {\n try {\n $deal = $this->executeRequest(fn () => $this->getNewInstance()->crm()->deals()->basicApi()->getById(\n $crmId,\n implode(',', $fields),\n 'companies,contacts'\n ));\n } catch (DealApiException $e) {\n $this->log->info('[Hubspot] Failed to fetch opportunity', [\n 'crm_id' => $crmId,\n 'reason' => $e->getMessage(),\n ]);\n\n throw $e;\n }\n\n if (! $deal instanceof DealWithAssociations) {\n throw new CrmException('Deal not found');\n }\n\n return [\n 'id' => $deal->getId(),\n 'properties' => $deal->getProperties(),\n 'associations' => $deal->getAssociations(),\n ];\n }\n\n /**\n * Generic batch read method for HubSpot objects\n *\n * @param string $objectType The object type ('deals', 'companies', 'contacts')\n * @param array<string> $crmIds Array of HubSpot object IDs (max 100)\n * @param array<string> $fields Array of property names to fetch\n *\n * @return array<string, array> Array keyed by CRM ID with object data\n */\n private function batchReadObjects(string $objectType, array $crmIds, array $fields): array\n {\n if (empty($crmIds)) {\n return [];\n }\n\n $this->validateBatchSize($objectType, $crmIds);\n $this->ensureValidToken();\n\n try {\n $batchConfig = $this->createBatchConfiguration($objectType);\n $batchReadRequest = $this->prepareBatchRequest($batchConfig, $crmIds, $fields);\n $response = $batchConfig['api']->read($batchReadRequest);\n\n $this->validateApiResponse($response, $objectType);\n\n $results = $this->processApiResults($response);\n $this->logBatchResults($objectType, $crmIds, $results);\n\n return $results;\n } catch (\\Throwable $e) {\n $this->handleBatchError($e, $objectType, $crmIds);\n }\n }\n\n private function validateBatchSize(string $objectType, array $crmIds): void\n {\n if (count($crmIds) > 100) {\n throw new \\InvalidArgumentException(\"Batch size cannot exceed 100 {$objectType}\");\n }\n }\n\n private function createBatchConfiguration(string $objectType): array\n {\n $configurations = [\n 'deals' => [\n 'batchReadRequest' => new \\HubSpot\\Client\\Crm\\Deals\\Model\\BatchReadInputSimplePublicObjectId(),\n 'inputClass' => \\HubSpot\\Client\\Crm\\Deals\\Model\\SimplePublicObjectId::class,\n 'api' => $this->getNewInstance()->crm()->deals()->batchApi(),\n ],\n 'companies' => [\n 'batchReadRequest' => new \\HubSpot\\Client\\Crm\\Companies\\Model\\BatchReadInputSimplePublicObjectId(),\n 'inputClass' => \\HubSpot\\Client\\Crm\\Companies\\Model\\SimplePublicObjectId::class,\n 'api' => $this->getNewInstance()->crm()->companies()->batchApi(),\n ],\n 'contacts' => [\n 'batchReadRequest' => new \\HubSpot\\Client\\Crm\\Contacts\\Model\\BatchReadInputSimplePublicObjectId(),\n 'inputClass' => \\HubSpot\\Client\\Crm\\Contacts\\Model\\SimplePublicObjectId::class,\n 'api' => $this->getNewInstance()->crm()->contacts()->batchApi(),\n ],\n ];\n\n if (! isset($configurations[$objectType])) {\n throw new \\InvalidArgumentException(\"Unsupported object type: {$objectType}\");\n }\n\n return $configurations[$objectType];\n }\n\n private function prepareBatchRequest(array $batchConfig, array $crmIds, array $fields): object\n {\n $batchReadRequest = $batchConfig['batchReadRequest'];\n $inputClass = $batchConfig['inputClass'];\n\n $inputs = array_map(function ($crmId) use ($inputClass) {\n $input = new $inputClass();\n $input->setId($crmId);\n\n return $input;\n }, $crmIds);\n\n $batchReadRequest->setInputs($inputs);\n $batchReadRequest->setProperties($fields);\n\n return $batchReadRequest;\n }\n\n private function validateApiResponse($response, string $objectType): void\n {\n if (! $response) {\n throw new CrmException(\"HubSpot API returned null response for {$objectType} batch read\");\n }\n }\n\n private function processApiResults($response): array\n {\n $results = [];\n $responseResults = $response->getResults();\n\n if ($responseResults) {\n foreach ($responseResults as $object) {\n if ($object && $object->getId()) {\n $results[$object->getId()] = [\n 'id' => $object->getId(),\n 'properties' => $object->getProperties() ?: [],\n ];\n }\n }\n }\n\n return $results;\n }\n\n private function logBatchResults(string $objectType, array $crmIds, array $results): void\n {\n $this->log->info(\"[HubSpot] Batch fetched {$objectType}\", [\n 'requested_count' => count($crmIds),\n 'returned_count' => count($results),\n 'crm_ids' => $crmIds,\n ]);\n }\n\n private function handleBatchError(\\Throwable $e, string $objectType, array $crmIds): void\n {\n $errorMessage = $e->getMessage() ?: 'Unknown error';\n $errorTrace = $e->getTraceAsString() ?: 'No trace available';\n\n $this->log->error(\"[HubSpot] Failed to batch fetch {$objectType}\", [\n 'crm_ids' => $crmIds,\n 'error' => $errorMessage,\n 'trace' => $errorTrace,\n ]);\n\n throw new CrmException(\"Failed to batch fetch {$objectType}: \" . $errorMessage);\n }\n\n /**\n * Batch read multiple opportunities by their CRM IDs\n *\n * @param array<string> $crmIds Array of HubSpot deal IDs (max 100)\n * @param array<string> $fields Array of property names to fetch\n *\n * @return array<string, array> Array keyed by CRM ID with opportunity data\n */\n public function getOpportunitiesByIds(array $crmIds, array $fields): array\n {\n return $this->batchReadObjects('deals', $crmIds, $fields);\n }\n\n /**\n * Batch read multiple companies by their CRM IDs\n *\n * @param array<string> $crmIds Array of HubSpot company IDs (max 100)\n * @param array<string> $fields Array of property names to fetch\n *\n * @return array<string, array> Array keyed by CRM ID with company data\n */\n public function getCompaniesByIds(array $crmIds, array $fields): array\n {\n return $this->batchReadObjects('companies', $crmIds, $fields);\n }\n\n /**\n * Batch read multiple contacts by their CRM IDs\n *\n * @param array<string> $crmIds Array of HubSpot contact IDs (max 100)\n * @param array<string> $fields Array of property names to fetch\n *\n * @return array<string, array> Array keyed by CRM ID with contact data\n */\n public function getContactsByIds(array $crmIds, array $fields): array\n {\n return $this->batchReadObjects('contacts', $crmIds, $fields);\n }\n\n /**\n * @throws CompanyApiException\n * @throws CrmException\n */\n public function getAccountById(string $crmId, array $fields): array\n {\n try {\n $company = $this->getNewInstance()->crm()->companies()->basicApi()->getById(\n $crmId,\n implode(',', $fields),\n );\n } catch (CompanyApiException $e) {\n $this->log->info('[Hubspot] Failed to fetch account', [\n 'crm_id' => $crmId,\n 'reason' => $e->getMessage(),\n ]);\n\n throw $e;\n }\n\n if (! $company instanceof CompaniesWithAssociations) {\n throw new CrmException('Account not found');\n }\n\n return [\n 'id' => $company->getId(),\n 'properties' => $company->getProperties(),\n ];\n }\n\n /**\n * @throws ContactApiException\n * @throws CrmException\n */\n public function getContactById(string $crmId, array $fields): array\n {\n try {\n $contact = $this->getNewInstance()->crm()->contacts()->basicApi()->getById(\n $crmId,\n implode(',', $fields)\n );\n } catch (ContactApiException $e) {\n $this->log->info('[Hubspot] Failed to fetch contact', [\n 'crm_id' => $crmId,\n 'reason' => $e->getMessage(),\n ]);\n\n throw $e;\n }\n\n if (! $contact instanceof ContactsWithAssociations) {\n throw new CrmException('Contact not found');\n }\n\n return [\n 'id' => $contact->getId(),\n 'properties' => $contact->getProperties(),\n ];\n }\n\n /**\n * This is email search request that Hubspot offers as GET (more generous quota)\n */\n public function getContactByEmail(string $email, array $fields = []): array\n {\n try {\n $contact = $this->getNewInstance()->crm()->contacts()->basicApi()->getById(\n $email,\n implode(',', $fields),\n null,\n false,\n 'email'\n );\n\n return [\n 'id' => $contact->getId(),\n 'properties' => $contact->getProperties(),\n ];\n } catch (ContactApiException $e) {\n $this->log->info('[Hubspot] Failed to fetch contact', [\n 'email' => $email,\n 'reason' => $e->getMessage(),\n ]);\n\n return [];\n }\n }\n\n /**\n * @throws CrmException\n */\n public function fetchProperty(string $objectType, string $propertyId): Property\n {\n $result = $this->getNewInstance()->crm()->properties()->coreApi()->getByName($objectType, $propertyId);\n\n if (! $result instanceof Property) {\n $this->log->error('[Hubspot] Failed to fetch property', [\n 'object_type' => $objectType,\n 'property_id' => $propertyId,\n 'reason' => $result->getMessage(),\n ]);\n\n throw new CrmException('Failed to fetch property');\n }\n\n return $result;\n }\n\n /**\n * @return array<CrmFieldOption>\n */\n public function fetchPropertyOptions(string $objectType, string $propertyId): array\n {\n /** @var array<CrmFieldOption> */\n return $this->fetchProperty($objectType, $propertyId)->getOptions();\n }\n\n /**\n * @return array<array{id:string, label:string, deleted:bool}>\n */\n public function fetchCallDispositions(): array\n {\n /** @var Response $response */\n $response = $this->getInstance()->engagements()->getCallDispositions();\n\n /**\n * @var array<array{\n * id:string,\n * label:string,\n * deleted: bool\n * }>\n */\n return $response->toArray();\n }\n\n /**\n * @return array<CrmFieldOption>\n */\n public function fetchOpportunityPipelineStages(): array\n {\n $stages = [];\n $apiResponse = $this->getNewInstance()->crm()->pipelines()->pipelinesApi()->getAll('deals');\n\n if ($apiResponse instanceof Error) {\n $this->log->error('[Hubspot] Failed to fetch opportunity pipelines', [\n 'reason' => $apiResponse->getMessage(),\n ]);\n\n return [];\n }\n\n foreach ($apiResponse->getResults() as $pipeline) {\n $pipelineStages = array_map(\n static function (PipelineStage $stage) {\n return [\n 'id' => $stage->getId(),\n 'label' => $stage->getLabel(),\n ];\n },\n $pipeline->getStages()\n );\n\n $stages = array_merge($stages, $pipelineStages);\n }\n\n return $stages;\n }\n\n public function fetchOpportunityPipelines(): array\n {\n $pipelines = [];\n\n try {\n $apiResponse = $this->makeRequest('/crm/v3/pipelines/deals');\n } catch (\\Exception $e) {\n $this->log->info('[Hubspot] Failed to fetch opportunity pipelines', [\n 'reason' => $e->getMessage(),\n ]);\n\n return [];\n }\n\n $response = $apiResponse->toArray();\n\n foreach ($response['results'] as $pipeline) {\n $pipelines[] = [\n 'id' => $pipeline['id'],\n 'label' => $pipeline['label'],\n ];\n }\n\n return $pipelines;\n }\n\n /**\n * @return array<CrmFieldOption>\n */\n public function fetchMeetingOutcomeFieldOptions(Field $field): array\n {\n return $field->getCrmProviderId() === 'meetingOutcome'\n ? $this->fetchMeetingOutcomeTypes()\n : $this->fetchCallActivityTypes();\n }\n\n public function fetchMeetingOutcomeTypes(): array\n {\n return $this->extractMeetingTypeOptions(\n 'https://api.hubapi.com/crm/v3/properties/meeting/hs_meeting_outcome'\n );\n }\n\n public function fetchCallActivityTypes(): array\n {\n return $this->extractMeetingTypeOptions(\n 'https://api.hubapi.com/crm/v3/properties/call/hs_activity_type'\n );\n }\n\n private function extractMeetingTypeOptions(string $endpoint): array\n {\n /** @var Response $response */\n $response = $this->getInstance()\n ->getClient()\n ->request('GET', $endpoint);\n\n /**\n * @var array<array{\n * value: string,\n * label: string,\n * displayOrder: int\n * }> $optionData\n */\n $optionData = $response->toArray()['options'] ?? [];\n\n $options = [];\n foreach ($optionData as $item) {\n $options[] = [\n 'id' => $item['value'],\n 'value' => $item['value'],\n 'label' => $item['label'],\n 'display_order' => $item['displayOrder'],\n ];\n }\n\n return $options;\n }\n\n /**\n * @return array<CrmFieldOption>\n */\n public function fetchDispositionFieldOptions(): array\n {\n $options = [];\n\n $dispositions = $this->fetchCallDispositions();\n\n foreach ($dispositions as $disposition) {\n if ($disposition['deleted'] !== false) {\n continue;\n }\n\n $option['value'] = $disposition['id'];\n $option['id'] = $disposition['id'];\n $option['label'] = $disposition['label'];\n\n $options[] = $option;\n }\n\n return $options;\n }\n\n /**\n * @return array<CrmFieldOption>\n */\n public function fetchOpportunityFieldOptions(Field $field): array\n {\n if ($field->isStageField()) {\n return $this->fetchOpportunityPipelineStages();\n }\n\n if ($field->isPipelineField()) {\n return $this->fetchOpportunityPipelines();\n }\n\n return $this->fetchPropertyOptions('deals', $field->getCrmProviderId());\n }\n\n /**\n * @throws BadRequest\n * @throws HubspotException\n */\n public function makeRequest(string $endpoint, $method = 'GET', $payload = [], ?string $queryString = null)\n {\n $endpoint = self::BASE_URL . $endpoint;\n\n if ($method === 'GET') {\n $response = $this->getInstance()->getClient()?->request(\n method: $method,\n endpoint: $endpoint,\n query_string: $queryString\n );\n } else {\n $response = $this->getInstance()->getClient()->request($method, $endpoint, [\n 'json' => ($payload),\n ]);\n }\n\n $max = $response->getHeaderLine('X-HubSpot-RateLimit-Max'); // \"110\"\n $remaining = $response->getHeaderLine('X-HubSpot-RateLimit-Remaining'); // \"109\"\n $interval = $response->getHeaderLine('X-HubSpot-RateLimit-Interval-Milliseconds'); // \"10000\"\n $body = json_decode((string) $response->getBody(), true);\n\n \\Illuminate\\Support\\Facades\\Log::channel('custom_channel')->info('$max ' . PHP_EOL . print_r($max, true));\n \\Illuminate\\Support\\Facades\\Log::channel('custom_channel')->info('$remaining ' . PHP_EOL . print_r($remaining, true));\n \\Illuminate\\Support\\Facades\\Log::channel('custom_channel')->info('$interval ' . PHP_EOL . print_r($interval, true));\n \\Illuminate\\Support\\Facades\\Log::channel('custom_channel')->info('$body ' . PHP_EOL . print_r($body, true));\n\n return $response;\n }\n\n /**\n * @throws BadRequest\n * @throws HubspotException\n */\n public function createMeeting(array $payload): Response\n {\n $endpoint = '/crm/v3/objects/meetings';\n\n return $this->makeRequest($endpoint, 'POST', $payload);\n }\n\n /**\n * @throws BadRequest\n * @throws HubspotException\n */\n public function updateMeeting(string $meetingId, array $payload): Response\n {\n $endpoint = '/crm/v3/objects/meetings/' . $meetingId;\n\n return $this->makeRequest($endpoint, 'PATCH', $payload);\n }\n\n /**\n * @throws \\Exception\n */\n public function createNote(\n string $body,\n string $ownerId,\n int $timestamp,\n string $objectId,\n NoteObject $noteObject\n ): ?string {\n try {\n $noteInput = new SimplePublicObjectInput([\n 'properties' => [\n 'hs_note_body' => $body,\n 'hubspot_owner_id' => $ownerId,\n 'hs_timestamp' => $timestamp,\n ],\n ]);\n\n // Create note\n $note = $this->getNewInstance()->crm()->objects()->basicApi()->create('note', $noteInput);\n\n $this->getNewInstance()->crm()->objects()->associationsApi()->create(\n 'note',\n $note->getId(),\n $this->getNoteObject($noteObject),\n $objectId,\n $this->getNoteAssociationType($noteObject),\n );\n\n return $note->getId();\n } catch (\\Exception $e) {\n $this->log->error('[Hubspot] Failed to create note', [\n 'objectId' => $objectId,\n 'noteObject' => $noteObject->getObjectType(),\n 'reason' => $e->getMessage(),\n ]);\n\n \\Sentry::captureException($e);\n }\n\n return null;\n }\n\n public function updateEngagement(string $objectId, array $engagement, array $metadata): void\n {\n $this->getInstance()->engagements()->update($objectId, $engagement, $metadata);\n }\n\n public function getEngagementData(string $engagementId): array\n {\n $engagement = $this->getInstance()->engagements()->get($engagementId);\n\n return $engagement->toArray();\n }\n\n public function createEngagement(array $engagement, array $associations, array $metadata): Response\n {\n return $this->getInstance()\n ->engagements()\n ->create($engagement, $associations, $metadata);\n }\n\n public function isUnauthorizedException(\\Exception $e): bool\n {\n // Check for specific HubSpot API exception types first\n if ($e instanceof BadRequest) {\n // BadRequest can contain 401 status codes\n return $e->getCode() === 401;\n }\n\n // Check for HTTP client exceptions with status codes\n if ($e instanceof \\GuzzleHttp\\Exception\\RequestException && $e->hasResponse()) {\n $response = $e->getResponse();\n if ($response !== null) {\n return $response->getStatusCode() === 401;\n }\n }\n\n // Check for Guzzle HTTP exceptions\n if ($e instanceof \\GuzzleHttp\\Exception\\ClientException) {\n return $e->getCode() === 401;\n }\n\n // Fallback to string matching as last resort, but be more specific\n $message = strtolower($e->getMessage());\n\n return str_contains($message, '401 unauthorized') ||\n str_contains($message, 'http 401') ||\n str_contains($message, 'status code 401') ||\n (preg_match('/\\b401\\b/', $message) && str_contains($message, 'unauthorized'));\n }\n\n /**\n * Validates and refreshes the access token if needed before API requests.\n * This ensures long-running processes don't fail due to token expiration.\n *\n * @throws SocialAccountTokenInvalidException\n */\n public function ensureValidToken(): void\n {\n if ($this->oauthAccount === null) {\n return;\n }\n\n $newToken = $this->tokenManager->ensureValidToken($this->oauthAccount);\n if ($newToken !== null) {\n $this->accessToken = $newToken;\n }\n }\n\n public function getConfig()\n {\n return $this->config;\n }\n\n // returns only active (archived=false)\n public function getOwners(): array\n {\n return $this->getNewInstance()->crm()->owners()->getAll();\n }\n\n /**\n * @param bool $archived\n *\n * @return array<Owner>|[]\n */\n public function getOwnersArchived(bool $archived = true): array\n {\n $endpoint = '/crm/v3/owners';\n $queryParams = [\n 'archived' => $archived ? 'true' : 'false',\n ];\n $queryString = http_build_query($queryParams);\n\n $owners = [];\n\n try {\n $response = $this->makeRequest(endpoint: $endpoint, queryString: $queryString);\n $responseData = $response?->toArray();\n\n foreach ($responseData['results'] as $result) {\n try {\n $owners[] = Owner::create($result);\n } catch (Throwable $e) {\n $this->log->error('[HubSpot] Failed to process owner data', [\n 'result' => $result,\n 'error' => $e->getMessage(),\n ]);\n\n continue;\n }\n }\n } catch (Throwable $e) {\n $this->log->error('HubSpot] Failed to fetch owners', [\n 'archived' => $archived,\n 'error' => $e->getMessage(),\n ]);\n\n return [];\n }\n\n return $owners;\n }\n\n public function getMeeting(string $engagementId): ObjectWithAssociations\n {\n return $this->getNewInstance()->crm()->objects()->basicApi()\n ->getById('meeting', $engagementId, null, 'contact,company,deal');\n }\n\n public function deleteEngagement(string $engagementId): void\n {\n $this->getInstance()->engagements()->delete((int) $engagementId);\n }\n\n public function getAssociationsData(array $ids, string $fromObject, string $toObject): array\n {\n $associationData = [];\n $idChunks = array_chunk($ids, self::ASSOCIATIONS_BATCH_SIZE_LIMIT);\n\n foreach ($idChunks as $idChunk) {\n try {\n $batchInput = new \\HubSpot\\Client\\Crm\\Associations\\Model\\BatchInputPublicObjectId();\n $batchInput->setInputs(array_map(function ($id) {\n $publicObjectId = new \\HubSpot\\Client\\Crm\\Associations\\Model\\PublicObjectId();\n $publicObjectId->setId($id);\n\n return $publicObjectId;\n }, $idChunk));\n\n $associatedObjectsData = $this\n ->getNewInstance()\n ->crm()\n ->associations()\n ->batchApi()\n ->read($fromObject, $toObject, $batchInput);\n\n if ($associatedObjectsData instanceof \\HubSpot\\Client\\Crm\\Associations\\Model\\BatchResponsePublicAssociationMulti) {\n foreach ($associatedObjectsData->getResults() as $association) {\n $from = $association->getFrom()->getId();\n $toAssociations = $association->getTo();\n\n if (! empty($toAssociations)) {\n $associationData[$from] = array_map(function ($item) {\n return $item->getId();\n }, $toAssociations);\n }\n }\n }\n } catch (\\Exception $e) {\n $this->log->error('[Hubspot] Failed to fetch associations', [\n 'from_object' => $fromObject,\n 'to_object' => $toObject,\n 'reason' => $e->getMessage(),\n ]);\n }\n }\n\n return $associationData;\n }\n\n /**\n * @throws \\Exception\n */\n private function getNoteAssociationType(NoteObject $noteObject): string\n {\n return match($noteObject) {\n NoteObject::Opportunity => 'note_to_deal',\n NoteObject::Lead, NoteObject::Contact => 'note_to_contact', // or 'note_to_lead' if your portal supports it\n NoteObject::Account => 'note_to_company',\n NoteObject::Call, NoteObject::Event => throw new \\Exception('Not supported'),\n };\n }\n\n /**\n * @throws \\Exception\n */\n private function getNoteObject(NoteObject $noteObject): string\n {\n return match($noteObject) {\n NoteObject::Opportunity => 'deal',\n NoteObject::Lead, NoteObject::Contact => 'contact',\n NoteObject::Account => 'company',\n NoteObject::Call, NoteObject::Event => throw new \\Exception('Not supported'),\n };\n }\n\n public function addAssociations(string $objectType, string $associationType, array $payload): Response\n {\n $endpoint = \"/crm/v4/associations/$objectType/$associationType/batch/create\";\n\n return $this->makeRequest($endpoint, 'POST', $payload);\n }\n\n public function removeAssociations(string $objectType, string $associationType, array $payload): Response\n {\n $endpoint = \"/crm/v4/associations/$objectType/$associationType/batch/archive\";\n\n return $this->makeRequest($endpoint, 'POST', $payload);\n }\n}","depth":4,"on_screen":true,"value":"<?php\n\ndeclare(strict_types=1);\n\nnamespace Jiminny\\Services\\Crm\\Hubspot;\n\nuse HubSpot\\Client\\Crm\\Deals\\ApiException as DealApiException;\nuse HubSpot\\Client\\Crm\\Contacts\\ApiException as ContactApiException;\nuse HubSpot\\Client\\Crm\\Companies\\ApiException as CompanyApiException;\nuse HubSpot\\Client\\Crm\\Contacts\\Model\\SimplePublicObjectWithAssociations as ContactsWithAssociations;\nuse HubSpot\\Client\\Crm\\Companies\\Model\\SimplePublicObjectWithAssociations as CompaniesWithAssociations;\nuse HubSpot\\Client\\Crm\\Deals\\Model\\SimplePublicObjectWithAssociations as DealWithAssociations;\nuse HubSpot\\Client\\Crm\\Objects\\Model\\SimplePublicObjectInput;\nuse HubSpot\\Client\\Crm\\Objects\\Model\\SimplePublicObjectWithAssociations as ObjectWithAssociations;\nuse HubSpot\\Client\\Crm\\Pipelines\\Model\\Error;\nuse HubSpot\\Client\\Crm\\Pipelines\\Model\\PipelineStage;\nuse HubSpot\\Client\\Crm\\Properties\\Model\\Property;\nuse HubSpot\\Discovery\\Discovery;\nuse Jiminny\\Component\\Utility\\Service\\ProviderRateLimiter;\nuse Jiminny\\Exceptions\\CrmException;\nuse Jiminny\\Exceptions\\RateLimitException;\nuse Jiminny\\Exceptions\\SocialAccountTokenInvalidException;\nuse Jiminny\\Jobs\\Crm\\NoteObject;\nuse Jiminny\\Models\\Crm\\Field;\nuse Jiminny\\Services\\Crm\\BaseClient;\nuse Jiminny\\Services\\Crm\\Hubspot\\DTO\\Response\\Owner;\nuse Jiminny\\Services\\SocialAccountService;\nuse SevenShores\\Hubspot\\Exceptions\\BadRequest;\nuse SevenShores\\Hubspot\\Exceptions\\HubspotException;\nuse SevenShores\\Hubspot\\Factory;\nuse SevenShores\\Hubspot\\Http\\Response;\nuse Jiminny\\Services\\Crm\\Hubspot\\Pagination\\HubspotPaginationService;\nuse Throwable;\n\n/**\n * @phpstan-type CrmFieldOption array{id:string, label:string, value?:string}\n */\nclass Client extends BaseClient implements HubspotClientInterface\n{\n public const string MIN_API_VERSION = '2';\n\n public const string BASE_URL = 'https://api.hubapi.com';\n\n public const int ASSOCIATIONS_BATCH_SIZE_LIMIT = 1000;\n\n private HubspotPaginationService $paginationService;\n private HubspotTokenManager $tokenManager;\n private ProviderRateLimiter $rateLimiter;\n\n public function __construct(\n SocialAccountService $socialAccountService,\n HubspotPaginationService $paginationService,\n HubspotTokenManager $tokenManager,\n ProviderRateLimiter $rateLimiter,\n ) {\n parent::__construct($socialAccountService);\n $this->paginationService = $paginationService;\n $this->tokenManager = $tokenManager;\n $this->rateLimiter = $rateLimiter;\n\n $this->setBaseUrl(self::BASE_URL);\n $this->setVersion(self::MIN_API_VERSION);\n }\n\n /**\n * Single entry point for every HubSpot API call. Enforces the per-portal\n * rate limit configured in the rate_limits table (morphed to the current\n * Configuration) and reacts to a real 429 from HubSpot by translating it\n * into a RateLimitException carrying Retry-After.\n *\n * Wrap any outbound HubSpot call (SDK or raw HTTP) like:\n *\n * $this->executeRequest(fn () => $this->getNewInstance()->crm()->...);\n *\n * @template T\n * @param callable(): T $apiCall\n * @return T\n *\n * @throws RateLimitException\n */\n private function executeRequest(callable $apiCall)\n {\n if (! $this->rateLimiter->canMakeRequest($this->config)) {\n $retryAfter = $this->rateLimiter->requestAvailableIn($this->config);\n\n $this->log->warning('[Hubspot] Rate limit exceeded, deferring request', [\n 'team_id' => $this->config->team_id,\n 'config_id' => $this->config->getId(),\n 'retry_after' => $retryAfter,\n ]);\n\n throw new RateLimitException(\n 'Hubspot rate limit reached for configuration ' . $this->config->getId(),\n $retryAfter,\n );\n }\n\n $this->rateLimiter->incrementRequestCount($this->config);\n\n try {\n return $apiCall();\n } catch (Throwable $e) {\n if ($this->isHubspotRateLimit($e)) {\n $retryAfter = $this->parseRetryAfter($e);\n\n $this->log->warning('[Hubspot] Received 429 from API', [\n 'team_id' => $this->config->team_id,\n 'config_id' => $this->config->getId(),\n 'retry_after' => $retryAfter,\n 'reason' => $e->getMessage(),\n ]);\n\n throw new RateLimitException('Hubspot returned 429', $retryAfter, $e);\n }\n\n throw $e;\n }\n }\n\n private function isHubspotRateLimit(Throwable $e): bool\n {\n return method_exists($e, 'getCode') && (int) $e->getCode() === 429;\n }\n\n private function parseRetryAfter(Throwable $e): int\n {\n if (method_exists($e, 'getResponseHeaders')) {\n $headers = $e->getResponseHeaders() ?: [];\n $value = $headers['Retry-After'] ?? $headers['retry-after'] ?? null;\n if (is_array($value)) {\n $value = $value[0] ?? null;\n }\n if (is_numeric($value)) {\n return (int) $value;\n }\n }\n\n return 10;\n }\n\n public function getMinimumApiVersion(): string\n {\n return self::MIN_API_VERSION;\n }\n\n public function getInstance(): Factory\n {\n return new Factory([\n 'key' => $this->accessToken,\n 'oauth2' => true,\n 'base_url' => $this->baseUrl,\n ]);\n }\n\n public function getNewInstance(): Discovery\n {\n return \\HubSpot\\Factory::createWithAccessToken($this->accessToken);\n }\n\n /**\n * Secondly and daily limits for Hubspot API\n *\n * Product Tier: Free & Starter | Professional & Enterprise | API add-on (any tier)\n * Burst: 100/10 seconds | 150/10 seconds | 200/10 seconds\n * Daily: 250,000 | 500,000 | 1,000,000\n *\n * Official documentation states: The search endpoints are rate limited to five requests per second.\n * Since with 5 RPS were still hitting secondly rate limits we lowered it to 4\n */\n public function getPaginatedData(array $payload, string $type, int $offset = 0): array\n {\n $total = 0;\n $lastId = null;\n $rows = [];\n foreach ($this->getPaginatedDataGenerator($payload, $type, $offset, $total, $lastId) as $row) {\n $rows[] = $row;\n }\n\n return ['results' => $rows, 'total' => $total, 'last_record' => $lastId];\n }\n\n /**\n * @throws HubspotException\n * @throws SocialAccountTokenInvalidException\n * @throws BadRequest\n */\n public function getPaginatedDataGenerator(\n array $payload,\n string $type,\n int $offset = 0,\n int &$total = 0,\n ?string &$lastRecordId = null\n ): \\Generator {\n return $this->paginationService->getPaginatedDataGenerator(\n $this,\n $payload,\n $type,\n $offset,\n $total,\n $lastRecordId\n );\n }\n\n /**\n * @throws DealApiException\n * @throws CrmException\n */\n public function getOpportunityById(string $crmId, array $fields): array\n {\n try {\n $deal = $this->executeRequest(fn () => $this->getNewInstance()->crm()->deals()->basicApi()->getById(\n $crmId,\n implode(',', $fields),\n 'companies,contacts'\n ));\n } catch (DealApiException $e) {\n $this->log->info('[Hubspot] Failed to fetch opportunity', [\n 'crm_id' => $crmId,\n 'reason' => $e->getMessage(),\n ]);\n\n throw $e;\n }\n\n if (! $deal instanceof DealWithAssociations) {\n throw new CrmException('Deal not found');\n }\n\n return [\n 'id' => $deal->getId(),\n 'properties' => $deal->getProperties(),\n 'associations' => $deal->getAssociations(),\n ];\n }\n\n /**\n * Generic batch read method for HubSpot objects\n *\n * @param string $objectType The object type ('deals', 'companies', 'contacts')\n * @param array<string> $crmIds Array of HubSpot object IDs (max 100)\n * @param array<string> $fields Array of property names to fetch\n *\n * @return array<string, array> Array keyed by CRM ID with object data\n */\n private function batchReadObjects(string $objectType, array $crmIds, array $fields): array\n {\n if (empty($crmIds)) {\n return [];\n }\n\n $this->validateBatchSize($objectType, $crmIds);\n $this->ensureValidToken();\n\n try {\n $batchConfig = $this->createBatchConfiguration($objectType);\n $batchReadRequest = $this->prepareBatchRequest($batchConfig, $crmIds, $fields);\n $response = $batchConfig['api']->read($batchReadRequest);\n\n $this->validateApiResponse($response, $objectType);\n\n $results = $this->processApiResults($response);\n $this->logBatchResults($objectType, $crmIds, $results);\n\n return $results;\n } catch (\\Throwable $e) {\n $this->handleBatchError($e, $objectType, $crmIds);\n }\n }\n\n private function validateBatchSize(string $objectType, array $crmIds): void\n {\n if (count($crmIds) > 100) {\n throw new \\InvalidArgumentException(\"Batch size cannot exceed 100 {$objectType}\");\n }\n }\n\n private function createBatchConfiguration(string $objectType): array\n {\n $configurations = [\n 'deals' => [\n 'batchReadRequest' => new \\HubSpot\\Client\\Crm\\Deals\\Model\\BatchReadInputSimplePublicObjectId(),\n 'inputClass' => \\HubSpot\\Client\\Crm\\Deals\\Model\\SimplePublicObjectId::class,\n 'api' => $this->getNewInstance()->crm()->deals()->batchApi(),\n ],\n 'companies' => [\n 'batchReadRequest' => new \\HubSpot\\Client\\Crm\\Companies\\Model\\BatchReadInputSimplePublicObjectId(),\n 'inputClass' => \\HubSpot\\Client\\Crm\\Companies\\Model\\SimplePublicObjectId::class,\n 'api' => $this->getNewInstance()->crm()->companies()->batchApi(),\n ],\n 'contacts' => [\n 'batchReadRequest' => new \\HubSpot\\Client\\Crm\\Contacts\\Model\\BatchReadInputSimplePublicObjectId(),\n 'inputClass' => \\HubSpot\\Client\\Crm\\Contacts\\Model\\SimplePublicObjectId::class,\n 'api' => $this->getNewInstance()->crm()->contacts()->batchApi(),\n ],\n ];\n\n if (! isset($configurations[$objectType])) {\n throw new \\InvalidArgumentException(\"Unsupported object type: {$objectType}\");\n }\n\n return $configurations[$objectType];\n }\n\n private function prepareBatchRequest(array $batchConfig, array $crmIds, array $fields): object\n {\n $batchReadRequest = $batchConfig['batchReadRequest'];\n $inputClass = $batchConfig['inputClass'];\n\n $inputs = array_map(function ($crmId) use ($inputClass) {\n $input = new $inputClass();\n $input->setId($crmId);\n\n return $input;\n }, $crmIds);\n\n $batchReadRequest->setInputs($inputs);\n $batchReadRequest->setProperties($fields);\n\n return $batchReadRequest;\n }\n\n private function validateApiResponse($response, string $objectType): void\n {\n if (! $response) {\n throw new CrmException(\"HubSpot API returned null response for {$objectType} batch read\");\n }\n }\n\n private function processApiResults($response): array\n {\n $results = [];\n $responseResults = $response->getResults();\n\n if ($responseResults) {\n foreach ($responseResults as $object) {\n if ($object && $object->getId()) {\n $results[$object->getId()] = [\n 'id' => $object->getId(),\n 'properties' => $object->getProperties() ?: [],\n ];\n }\n }\n }\n\n return $results;\n }\n\n private function logBatchResults(string $objectType, array $crmIds, array $results): void\n {\n $this->log->info(\"[HubSpot] Batch fetched {$objectType}\", [\n 'requested_count' => count($crmIds),\n 'returned_count' => count($results),\n 'crm_ids' => $crmIds,\n ]);\n }\n\n private function handleBatchError(\\Throwable $e, string $objectType, array $crmIds): void\n {\n $errorMessage = $e->getMessage() ?: 'Unknown error';\n $errorTrace = $e->getTraceAsString() ?: 'No trace available';\n\n $this->log->error(\"[HubSpot] Failed to batch fetch {$objectType}\", [\n 'crm_ids' => $crmIds,\n 'error' => $errorMessage,\n 'trace' => $errorTrace,\n ]);\n\n throw new CrmException(\"Failed to batch fetch {$objectType}: \" . $errorMessage);\n }\n\n /**\n * Batch read multiple opportunities by their CRM IDs\n *\n * @param array<string> $crmIds Array of HubSpot deal IDs (max 100)\n * @param array<string> $fields Array of property names to fetch\n *\n * @return array<string, array> Array keyed by CRM ID with opportunity data\n */\n public function getOpportunitiesByIds(array $crmIds, array $fields): array\n {\n return $this->batchReadObjects('deals', $crmIds, $fields);\n }\n\n /**\n * Batch read multiple companies by their CRM IDs\n *\n * @param array<string> $crmIds Array of HubSpot company IDs (max 100)\n * @param array<string> $fields Array of property names to fetch\n *\n * @return array<string, array> Array keyed by CRM ID with company data\n */\n public function getCompaniesByIds(array $crmIds, array $fields): array\n {\n return $this->batchReadObjects('companies', $crmIds, $fields);\n }\n\n /**\n * Batch read multiple contacts by their CRM IDs\n *\n * @param array<string> $crmIds Array of HubSpot contact IDs (max 100)\n * @param array<string> $fields Array of property names to fetch\n *\n * @return array<string, array> Array keyed by CRM ID with contact data\n */\n public function getContactsByIds(array $crmIds, array $fields): array\n {\n return $this->batchReadObjects('contacts', $crmIds, $fields);\n }\n\n /**\n * @throws CompanyApiException\n * @throws CrmException\n */\n public function getAccountById(string $crmId, array $fields): array\n {\n try {\n $company = $this->getNewInstance()->crm()->companies()->basicApi()->getById(\n $crmId,\n implode(',', $fields),\n );\n } catch (CompanyApiException $e) {\n $this->log->info('[Hubspot] Failed to fetch account', [\n 'crm_id' => $crmId,\n 'reason' => $e->getMessage(),\n ]);\n\n throw $e;\n }\n\n if (! $company instanceof CompaniesWithAssociations) {\n throw new CrmException('Account not found');\n }\n\n return [\n 'id' => $company->getId(),\n 'properties' => $company->getProperties(),\n ];\n }\n\n /**\n * @throws ContactApiException\n * @throws CrmException\n */\n public function getContactById(string $crmId, array $fields): array\n {\n try {\n $contact = $this->getNewInstance()->crm()->contacts()->basicApi()->getById(\n $crmId,\n implode(',', $fields)\n );\n } catch (ContactApiException $e) {\n $this->log->info('[Hubspot] Failed to fetch contact', [\n 'crm_id' => $crmId,\n 'reason' => $e->getMessage(),\n ]);\n\n throw $e;\n }\n\n if (! $contact instanceof ContactsWithAssociations) {\n throw new CrmException('Contact not found');\n }\n\n return [\n 'id' => $contact->getId(),\n 'properties' => $contact->getProperties(),\n ];\n }\n\n /**\n * This is email search request that Hubspot offers as GET (more generous quota)\n */\n public function getContactByEmail(string $email, array $fields = []): array\n {\n try {\n $contact = $this->getNewInstance()->crm()->contacts()->basicApi()->getById(\n $email,\n implode(',', $fields),\n null,\n false,\n 'email'\n );\n\n return [\n 'id' => $contact->getId(),\n 'properties' => $contact->getProperties(),\n ];\n } catch (ContactApiException $e) {\n $this->log->info('[Hubspot] Failed to fetch contact', [\n 'email' => $email,\n 'reason' => $e->getMessage(),\n ]);\n\n return [];\n }\n }\n\n /**\n * @throws CrmException\n */\n public function fetchProperty(string $objectType, string $propertyId): Property\n {\n $result = $this->getNewInstance()->crm()->properties()->coreApi()->getByName($objectType, $propertyId);\n\n if (! $result instanceof Property) {\n $this->log->error('[Hubspot] Failed to fetch property', [\n 'object_type' => $objectType,\n 'property_id' => $propertyId,\n 'reason' => $result->getMessage(),\n ]);\n\n throw new CrmException('Failed to fetch property');\n }\n\n return $result;\n }\n\n /**\n * @return array<CrmFieldOption>\n */\n public function fetchPropertyOptions(string $objectType, string $propertyId): array\n {\n /** @var array<CrmFieldOption> */\n return $this->fetchProperty($objectType, $propertyId)->getOptions();\n }\n\n /**\n * @return array<array{id:string, label:string, deleted:bool}>\n */\n public function fetchCallDispositions(): array\n {\n /** @var Response $response */\n $response = $this->getInstance()->engagements()->getCallDispositions();\n\n /**\n * @var array<array{\n * id:string,\n * label:string,\n * deleted: bool\n * }>\n */\n return $response->toArray();\n }\n\n /**\n * @return array<CrmFieldOption>\n */\n public function fetchOpportunityPipelineStages(): array\n {\n $stages = [];\n $apiResponse = $this->getNewInstance()->crm()->pipelines()->pipelinesApi()->getAll('deals');\n\n if ($apiResponse instanceof Error) {\n $this->log->error('[Hubspot] Failed to fetch opportunity pipelines', [\n 'reason' => $apiResponse->getMessage(),\n ]);\n\n return [];\n }\n\n foreach ($apiResponse->getResults() as $pipeline) {\n $pipelineStages = array_map(\n static function (PipelineStage $stage) {\n return [\n 'id' => $stage->getId(),\n 'label' => $stage->getLabel(),\n ];\n },\n $pipeline->getStages()\n );\n\n $stages = array_merge($stages, $pipelineStages);\n }\n\n return $stages;\n }\n\n public function fetchOpportunityPipelines(): array\n {\n $pipelines = [];\n\n try {\n $apiResponse = $this->makeRequest('/crm/v3/pipelines/deals');\n } catch (\\Exception $e) {\n $this->log->info('[Hubspot] Failed to fetch opportunity pipelines', [\n 'reason' => $e->getMessage(),\n ]);\n\n return [];\n }\n\n $response = $apiResponse->toArray();\n\n foreach ($response['results'] as $pipeline) {\n $pipelines[] = [\n 'id' => $pipeline['id'],\n 'label' => $pipeline['label'],\n ];\n }\n\n return $pipelines;\n }\n\n /**\n * @return array<CrmFieldOption>\n */\n public function fetchMeetingOutcomeFieldOptions(Field $field): array\n {\n return $field->getCrmProviderId() === 'meetingOutcome'\n ? $this->fetchMeetingOutcomeTypes()\n : $this->fetchCallActivityTypes();\n }\n\n public function fetchMeetingOutcomeTypes(): array\n {\n return $this->extractMeetingTypeOptions(\n 'https://api.hubapi.com/crm/v3/properties/meeting/hs_meeting_outcome'\n );\n }\n\n public function fetchCallActivityTypes(): array\n {\n return $this->extractMeetingTypeOptions(\n 'https://api.hubapi.com/crm/v3/properties/call/hs_activity_type'\n );\n }\n\n private function extractMeetingTypeOptions(string $endpoint): array\n {\n /** @var Response $response */\n $response = $this->getInstance()\n ->getClient()\n ->request('GET', $endpoint);\n\n /**\n * @var array<array{\n * value: string,\n * label: string,\n * displayOrder: int\n * }> $optionData\n */\n $optionData = $response->toArray()['options'] ?? [];\n\n $options = [];\n foreach ($optionData as $item) {\n $options[] = [\n 'id' => $item['value'],\n 'value' => $item['value'],\n 'label' => $item['label'],\n 'display_order' => $item['displayOrder'],\n ];\n }\n\n return $options;\n }\n\n /**\n * @return array<CrmFieldOption>\n */\n public function fetchDispositionFieldOptions(): array\n {\n $options = [];\n\n $dispositions = $this->fetchCallDispositions();\n\n foreach ($dispositions as $disposition) {\n if ($disposition['deleted'] !== false) {\n continue;\n }\n\n $option['value'] = $disposition['id'];\n $option['id'] = $disposition['id'];\n $option['label'] = $disposition['label'];\n\n $options[] = $option;\n }\n\n return $options;\n }\n\n /**\n * @return array<CrmFieldOption>\n */\n public function fetchOpportunityFieldOptions(Field $field): array\n {\n if ($field->isStageField()) {\n return $this->fetchOpportunityPipelineStages();\n }\n\n if ($field->isPipelineField()) {\n return $this->fetchOpportunityPipelines();\n }\n\n return $this->fetchPropertyOptions('deals', $field->getCrmProviderId());\n }\n\n /**\n * @throws BadRequest\n * @throws HubspotException\n */\n public function makeRequest(string $endpoint, $method = 'GET', $payload = [], ?string $queryString = null)\n {\n $endpoint = self::BASE_URL . $endpoint;\n\n if ($method === 'GET') {\n $response = $this->getInstance()->getClient()?->request(\n method: $method,\n endpoint: $endpoint,\n query_string: $queryString\n );\n } else {\n $response = $this->getInstance()->getClient()->request($method, $endpoint, [\n 'json' => ($payload),\n ]);\n }\n\n $max = $response->getHeaderLine('X-HubSpot-RateLimit-Max'); // \"110\"\n $remaining = $response->getHeaderLine('X-HubSpot-RateLimit-Remaining'); // \"109\"\n $interval = $response->getHeaderLine('X-HubSpot-RateLimit-Interval-Milliseconds'); // \"10000\"\n $body = json_decode((string) $response->getBody(), true);\n\n \\Illuminate\\Support\\Facades\\Log::channel('custom_channel')->info('$max ' . PHP_EOL . print_r($max, true));\n \\Illuminate\\Support\\Facades\\Log::channel('custom_channel')->info('$remaining ' . PHP_EOL . print_r($remaining, true));\n \\Illuminate\\Support\\Facades\\Log::channel('custom_channel')->info('$interval ' . PHP_EOL . print_r($interval, true));\n \\Illuminate\\Support\\Facades\\Log::channel('custom_channel')->info('$body ' . PHP_EOL . print_r($body, true));\n\n return $response;\n }\n\n /**\n * @throws BadRequest\n * @throws HubspotException\n */\n public function createMeeting(array $payload): Response\n {\n $endpoint = '/crm/v3/objects/meetings';\n\n return $this->makeRequest($endpoint, 'POST', $payload);\n }\n\n /**\n * @throws BadRequest\n * @throws HubspotException\n */\n public function updateMeeting(string $meetingId, array $payload): Response\n {\n $endpoint = '/crm/v3/objects/meetings/' . $meetingId;\n\n return $this->makeRequest($endpoint, 'PATCH', $payload);\n }\n\n /**\n * @throws \\Exception\n */\n public function createNote(\n string $body,\n string $ownerId,\n int $timestamp,\n string $objectId,\n NoteObject $noteObject\n ): ?string {\n try {\n $noteInput = new SimplePublicObjectInput([\n 'properties' => [\n 'hs_note_body' => $body,\n 'hubspot_owner_id' => $ownerId,\n 'hs_timestamp' => $timestamp,\n ],\n ]);\n\n // Create note\n $note = $this->getNewInstance()->crm()->objects()->basicApi()->create('note', $noteInput);\n\n $this->getNewInstance()->crm()->objects()->associationsApi()->create(\n 'note',\n $note->getId(),\n $this->getNoteObject($noteObject),\n $objectId,\n $this->getNoteAssociationType($noteObject),\n );\n\n return $note->getId();\n } catch (\\Exception $e) {\n $this->log->error('[Hubspot] Failed to create note', [\n 'objectId' => $objectId,\n 'noteObject' => $noteObject->getObjectType(),\n 'reason' => $e->getMessage(),\n ]);\n\n \\Sentry::captureException($e);\n }\n\n return null;\n }\n\n public function updateEngagement(string $objectId, array $engagement, array $metadata): void\n {\n $this->getInstance()->engagements()->update($objectId, $engagement, $metadata);\n }\n\n public function getEngagementData(string $engagementId): array\n {\n $engagement = $this->getInstance()->engagements()->get($engagementId);\n\n return $engagement->toArray();\n }\n\n public function createEngagement(array $engagement, array $associations, array $metadata): Response\n {\n return $this->getInstance()\n ->engagements()\n ->create($engagement, $associations, $metadata);\n }\n\n public function isUnauthorizedException(\\Exception $e): bool\n {\n // Check for specific HubSpot API exception types first\n if ($e instanceof BadRequest) {\n // BadRequest can contain 401 status codes\n return $e->getCode() === 401;\n }\n\n // Check for HTTP client exceptions with status codes\n if ($e instanceof \\GuzzleHttp\\Exception\\RequestException && $e->hasResponse()) {\n $response = $e->getResponse();\n if ($response !== null) {\n return $response->getStatusCode() === 401;\n }\n }\n\n // Check for Guzzle HTTP exceptions\n if ($e instanceof \\GuzzleHttp\\Exception\\ClientException) {\n return $e->getCode() === 401;\n }\n\n // Fallback to string matching as last resort, but be more specific\n $message = strtolower($e->getMessage());\n\n return str_contains($message, '401 unauthorized') ||\n str_contains($message, 'http 401') ||\n str_contains($message, 'status code 401') ||\n (preg_match('/\\b401\\b/', $message) && str_contains($message, 'unauthorized'));\n }\n\n /**\n * Validates and refreshes the access token if needed before API requests.\n * This ensures long-running processes don't fail due to token expiration.\n *\n * @throws SocialAccountTokenInvalidException\n */\n public function ensureValidToken(): void\n {\n if ($this->oauthAccount === null) {\n return;\n }\n\n $newToken = $this->tokenManager->ensureValidToken($this->oauthAccount);\n if ($newToken !== null) {\n $this->accessToken = $newToken;\n }\n }\n\n public function getConfig()\n {\n return $this->config;\n }\n\n // returns only active (archived=false)\n public function getOwners(): array\n {\n return $this->getNewInstance()->crm()->owners()->getAll();\n }\n\n /**\n * @param bool $archived\n *\n * @return array<Owner>|[]\n */\n public function getOwnersArchived(bool $archived = true): array\n {\n $endpoint = '/crm/v3/owners';\n $queryParams = [\n 'archived' => $archived ? 'true' : 'false',\n ];\n $queryString = http_build_query($queryParams);\n\n $owners = [];\n\n try {\n $response = $this->makeRequest(endpoint: $endpoint, queryString: $queryString);\n $responseData = $response?->toArray();\n\n foreach ($responseData['results'] as $result) {\n try {\n $owners[] = Owner::create($result);\n } catch (Throwable $e) {\n $this->log->error('[HubSpot] Failed to process owner data', [\n 'result' => $result,\n 'error' => $e->getMessage(),\n ]);\n\n continue;\n }\n }\n } catch (Throwable $e) {\n $this->log->error('HubSpot] Failed to fetch owners', [\n 'archived' => $archived,\n 'error' => $e->getMessage(),\n ]);\n\n return [];\n }\n\n return $owners;\n }\n\n public function getMeeting(string $engagementId): ObjectWithAssociations\n {\n return $this->getNewInstance()->crm()->objects()->basicApi()\n ->getById('meeting', $engagementId, null, 'contact,company,deal');\n }\n\n public function deleteEngagement(string $engagementId): void\n {\n $this->getInstance()->engagements()->delete((int) $engagementId);\n }\n\n public function getAssociationsData(array $ids, string $fromObject, string $toObject): array\n {\n $associationData = [];\n $idChunks = array_chunk($ids, self::ASSOCIATIONS_BATCH_SIZE_LIMIT);\n\n foreach ($idChunks as $idChunk) {\n try {\n $batchInput = new \\HubSpot\\Client\\Crm\\Associations\\Model\\BatchInputPublicObjectId();\n $batchInput->setInputs(array_map(function ($id) {\n $publicObjectId = new \\HubSpot\\Client\\Crm\\Associations\\Model\\PublicObjectId();\n $publicObjectId->setId($id);\n\n return $publicObjectId;\n }, $idChunk));\n\n $associatedObjectsData = $this\n ->getNewInstance()\n ->crm()\n ->associations()\n ->batchApi()\n ->read($fromObject, $toObject, $batchInput);\n\n if ($associatedObjectsData instanceof \\HubSpot\\Client\\Crm\\Associations\\Model\\BatchResponsePublicAssociationMulti) {\n foreach ($associatedObjectsData->getResults() as $association) {\n $from = $association->getFrom()->getId();\n $toAssociations = $association->getTo();\n\n if (! empty($toAssociations)) {\n $associationData[$from] = array_map(function ($item) {\n return $item->getId();\n }, $toAssociations);\n }\n }\n }\n } catch (\\Exception $e) {\n $this->log->error('[Hubspot] Failed to fetch associations', [\n 'from_object' => $fromObject,\n 'to_object' => $toObject,\n 'reason' => $e->getMessage(),\n ]);\n }\n }\n\n return $associationData;\n }\n\n /**\n * @throws \\Exception\n */\n private function getNoteAssociationType(NoteObject $noteObject): string\n {\n return match($noteObject) {\n NoteObject::Opportunity => 'note_to_deal',\n NoteObject::Lead, NoteObject::Contact => 'note_to_contact', // or 'note_to_lead' if your portal supports it\n NoteObject::Account => 'note_to_company',\n NoteObject::Call, NoteObject::Event => throw new \\Exception('Not supported'),\n };\n }\n\n /**\n * @throws \\Exception\n */\n private function getNoteObject(NoteObject $noteObject): string\n {\n return match($noteObject) {\n NoteObject::Opportunity => 'deal',\n NoteObject::Lead, NoteObject::Contact => 'contact',\n NoteObject::Account => 'company',\n NoteObject::Call, NoteObject::Event => throw new \\Exception('Not supported'),\n };\n }\n\n public function addAssociations(string $objectType, string $associationType, array $payload): Response\n {\n $endpoint = \"/crm/v4/associations/$objectType/$associationType/batch/create\";\n\n return $this->makeRequest($endpoint, 'POST', $payload);\n }\n\n public function removeAssociations(string $objectType, string $associationType, array $payload): Response\n {\n $endpoint = \"/crm/v4/associations/$objectType/$associationType/batch/archive\";\n\n return $this->makeRequest($endpoint, 'POST', $payload);\n }\n}","role_description":"text entry area","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"Project","depth":3,"on_screen":false,"role_description":"text"},{"role":"AXButton","text":"Project","depth":3,"bounds":{"left":0.011968086,"top":0.047885075,"width":0.024268618,"height":0.024740623},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"New File or Directory…","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.008643617,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Expand Selected","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.008643617,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Collapse All","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.008643617,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Options","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.008643617,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Hide","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.008643617,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false}]...
|
-188086479456983350
|
6378757184196315236
|
click
|
accessibility
|
NULL
|
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
Editor for custom.log
Sync Changes
Hide This Notification
Code changed:
Hide
2
60
Previous Highlighted Error
Next Highlighted Error
<?php
declare(strict_types=1);
namespace Jiminny\Services\Crm\Hubspot;
use HubSpot\Client\Crm\Deals\ApiException as DealApiException;
use HubSpot\Client\Crm\Contacts\ApiException as ContactApiException;
use HubSpot\Client\Crm\Companies\ApiException as CompanyApiException;
use HubSpot\Client\Crm\Contacts\Model\SimplePublicObjectWithAssociations as ContactsWithAssociations;
use HubSpot\Client\Crm\Companies\Model\SimplePublicObjectWithAssociations as CompaniesWithAssociations;
use HubSpot\Client\Crm\Deals\Model\SimplePublicObjectWithAssociations as DealWithAssociations;
use HubSpot\Client\Crm\Objects\Model\SimplePublicObjectInput;
use HubSpot\Client\Crm\Objects\Model\SimplePublicObjectWithAssociations as ObjectWithAssociations;
use HubSpot\Client\Crm\Pipelines\Model\Error;
use HubSpot\Client\Crm\Pipelines\Model\PipelineStage;
use HubSpot\Client\Crm\Properties\Model\Property;
use HubSpot\Discovery\Discovery;
use Jiminny\Component\Utility\Service\ProviderRateLimiter;
use Jiminny\Exceptions\CrmException;
use Jiminny\Exceptions\RateLimitException;
use Jiminny\Exceptions\SocialAccountTokenInvalidException;
use Jiminny\Jobs\Crm\NoteObject;
use Jiminny\Models\Crm\Field;
use Jiminny\Services\Crm\BaseClient;
use Jiminny\Services\Crm\Hubspot\DTO\Response\Owner;
use Jiminny\Services\SocialAccountService;
use SevenShores\Hubspot\Exceptions\BadRequest;
use SevenShores\Hubspot\Exceptions\HubspotException;
use SevenShores\Hubspot\Factory;
use SevenShores\Hubspot\Http\Response;
use Jiminny\Services\Crm\Hubspot\Pagination\HubspotPaginationService;
use Throwable;
/**
* @phpstan-type CrmFieldOption array{id:string, label:string, value?:string}
*/
class Client extends BaseClient implements HubspotClientInterface
{
public const string MIN_API_VERSION = '2';
public const string BASE_URL = '[URL_WITH_CREDENTIALS] T
* @param callable(): T $apiCall
* @return T
*
* @throws RateLimitException
*/
private function executeRequest(callable $apiCall)
{
if (! $this->rateLimiter->canMakeRequest($this->config)) {
$retryAfter = $this->rateLimiter->requestAvailableIn($this->config);
$this->log->warning('[Hubspot] Rate limit exceeded, deferring request', [
'team_id' => $this->config->team_id,
'config_id' => $this->config->getId(),
'retry_after' => $retryAfter,
]);
throw new RateLimitException(
'Hubspot rate limit reached for configuration ' . $this->config->getId(),
$retryAfter,
);
}
$this->rateLimiter->incrementRequestCount($this->config);
try {
return $apiCall();
} catch (Throwable $e) {
if ($this->isHubspotRateLimit($e)) {
$retryAfter = $this->parseRetryAfter($e);
$this->log->warning('[Hubspot] Received 429 from API', [
'team_id' => $this->config->team_id,
'config_id' => $this->config->getId(),
'retry_after' => $retryAfter,
'reason' => $e->getMessage(),
]);
throw new RateLimitException('Hubspot returned 429', $retryAfter, $e);
}
throw $e;
}
}
private function isHubspotRateLimit(Throwable $e): bool
{
return method_exists($e, 'getCode') && (int) $e->getCode() === 429;
}
private function parseRetryAfter(Throwable $e): int
{
if (method_exists($e, 'getResponseHeaders')) {
$headers = $e->getResponseHeaders() ?: [];
$value = $headers['Retry-After'] ?? $headers['retry-after'] ?? null;
if (is_array($value)) {
$value = $value[0] ?? null;
}
if (is_numeric($value)) {
return (int) $value;
}
}
return 10;
}
public function getMinimumApiVersion(): string
{
return self::MIN_API_VERSION;
}
public function getInstance(): Factory
{
return new Factory([
'key' => $this->accessToken,
'oauth2' => true,
'base_url' => $this->baseUrl,
]);
}
public function getNewInstance(): Discovery
{
return \HubSpot\Factory::createWithAccessToken($this->accessToken);
}
/**
* Secondly and daily limits for Hubspot API
*
* Product Tier: Free & Starter | Professional & Enterprise | API add-on (any tier)
* Burst: 100/10 seconds | 150/10 seconds | 200/10 seconds
* Daily: 250,000 | 500,000 | 1,000,000
*
* Official documentation states: The search endpoints are rate limited to five requests per second.
* Since with 5 RPS were still hitting secondly rate limits we lowered it to 4
*/
public function getPaginatedData(array $payload, string $type, int $offset = 0): array
{
$total = 0;
$lastId = null;
$rows = [];
foreach ($this->getPaginatedDataGenerator($payload, $type, $offset, $total, $lastId) as $row) {
$rows[] = $row;
}
return ['results' => $rows, 'total' => $total, 'last_record' => $lastId];
}
/**
* @throws HubspotException
* @throws SocialAccountTokenInvalidException
* @throws BadRequest
*/
public function getPaginatedDataGenerator(
array $payload,
string $type,
int $offset = 0,
int &$total = 0,
?string &$lastRecordId = null
): \Generator {
return $this->paginationService->getPaginatedDataGenerator(
$this,
$payload,
$type,
$offset,
$total,
$lastRecordId
);
}
/**
* @throws DealApiException
* @throws CrmException
*/
public function getOpportunityById(string $crmId, array $fields): array
{
try {
$deal = $this->executeRequest(fn () => $this->getNewInstance()->crm()->deals()->basicApi()->getById(
$crmId,
implode(',', $fields),
'companies,contacts'
));
} catch (DealApiException $e) {
$this->log->info('[Hubspot] Failed to fetch opportunity', [
'crm_id' => $crmId,
'reason' => $e->getMessage(),
]);
throw $e;
}
if (! $deal instanceof DealWithAssociations) {
throw new CrmException('Deal not found');
}
return [
'id' => $deal->getId(),
'properties' => $deal->getProperties(),
'associations' => $deal->getAssociations(),
];
}
/**
* Generic batch read method for HubSpot objects
*
* @param string $objectType The object type ('deals', 'companies', 'contacts')
* @param array<string> $crmIds Array of HubSpot object IDs (max 100)
* @param array<string> $fields Array of property names to fetch
*
* @return array<string, array> Array keyed by CRM ID with object data
*/
private function batchReadObjects(string $objectType, array $crmIds, array $fields): array
{
if (empty($crmIds)) {
return [];
}
$this->validateBatchSize($objectType, $crmIds);
$this->ensureValidToken();
try {
$batchConfig = $this->createBatchConfiguration($objectType);
$batchReadRequest = $this->prepareBatchRequest($batchConfig, $crmIds, $fields);
$response = $batchConfig['api']->read($batchReadRequest);
$this->validateApiResponse($response, $objectType);
$results = $this->processApiResults($response);
$this->logBatchResults($objectType, $crmIds, $results);
return $results;
} catch (\Throwable $e) {
$this->handleBatchError($e, $objectType, $crmIds);
}
}
private function validateBatchSize(string $objectType, array $crmIds): void
{
if (count($crmIds) > 100) {
throw new \InvalidArgumentException("Batch size cannot exceed 100 {$objectType}");
}
}
private function createBatchConfiguration(string $objectType): array
{
$configurations = [
'deals' => [
'batchReadRequest' => new \HubSpot\Client\Crm\Deals\Model\BatchReadInputSimplePublicObjectId(),
'inputClass' => \HubSpot\Client\Crm\Deals\Model\SimplePublicObjectId::class,
'api' => $this->getNewInstance()->crm()->deals()->batchApi(),
],
'companies' => [
'batchReadRequest' => new \HubSpot\Client\Crm\Companies\Model\BatchReadInputSimplePublicObjectId(),
'inputClass' => \HubSpot\Client\Crm\Companies\Model\SimplePublicObjectId::class,
'api' => $this->getNewInstance()->crm()->companies()->batchApi(),
],
'contacts' => [
'batchReadRequest' => new \HubSpot\Client\Crm\Contacts\Model\BatchReadInputSimplePublicObjectId(),
'inputClass' => \HubSpot\Client\Crm\Contacts\Model\SimplePublicObjectId::class,
'api' => $this->getNewInstance()->crm()->contacts()->batchApi(),
],
];
if (! isset($configurations[$objectType])) {
throw new \InvalidArgumentException("Unsupported object type: {$objectType}");
}
return $configurations[$objectType];
}
private function prepareBatchRequest(array $batchConfig, array $crmIds, array $fields): object
{
$batchReadRequest = $batchConfig['batchReadRequest'];
$inputClass = $batchConfig['inputClass'];
$inputs = array_map(function ($crmId) use ($inputClass) {
$input = new $inputClass();
$input->setId($crmId);
return $input;
}, $crmIds);
$batchReadRequest->setInputs($inputs);
$batchReadRequest->setProperties($fields);
return $batchReadRequest;
}
private function validateApiResponse($response, string $objectType): void
{
if (! $response) {
throw new CrmException("HubSpot API returned null response for {$objectType} batch read");
}
}
private function processApiResults($response): array
{
$results = [];
$responseResults = $response->getResults();
if ($responseResults) {
foreach ($responseResults as $object) {
if ($object && $object->getId()) {
$results[$object->getId()] = [
'id' => $object->getId(),
'properties' => $object->getProperties() ?: [],
];
}
}
}
return $results;
}
private function logBatchResults(string $objectType, array $crmIds, array $results): void
{
$this->log->info("[HubSpot] Batch fetched {$objectType}", [
'requested_count' => count($crmIds),
'returned_count' => count($results),
'crm_ids' => $crmIds,
]);
}
private function handleBatchError(\Throwable $e, string $objectType, array $crmIds): void
{
$errorMessage = $e->getMessage() ?: 'Unknown error';
$errorTrace = $e->getTraceAsString() ?: 'No trace available';
$this->log->error("[HubSpot] Failed to batch fetch {$objectType}", [
'crm_ids' => $crmIds,
'error' => $errorMessage,
'trace' => $errorTrace,
]);
throw new CrmException("Failed to batch fetch {$objectType}: " . $errorMessage);
}
/**
* Batch read multiple opportunities by their CRM IDs
*
* @param array<string> $crmIds Array of HubSpot deal IDs (max 100)
* @param array<string> $fields Array of property names to fetch
*
* @return array<string, array> Array keyed by CRM ID with opportunity data
*/
public function getOpportunitiesByIds(array $crmIds, array $fields): array
{
return $this->batchReadObjects('deals', $crmIds, $fields);
}
/**
* Batch read multiple companies by their CRM IDs
*
* @param array<string> $crmIds Array of HubSpot company IDs (max 100)
* @param array<string> $fields Array of property names to fetch
*
* @return array<string, array> Array keyed by CRM ID with company data
*/
public function getCompaniesByIds(array $crmIds, array $fields): array
{
return $this->batchReadObjects('companies', $crmIds, $fields);
}
/**
* Batch read multiple contacts by their CRM IDs
*
* @param array<string> $crmIds Array of HubSpot contact IDs (max 100)
* @param array<string> $fields Array of property names to fetch
*
* @return array<string, array> Array keyed by CRM ID with contact data
*/
public function getContactsByIds(array $crmIds, array $fields): array
{
return $this->batchReadObjects('contacts', $crmIds, $fields);
}
/**
* @throws CompanyApiException
* @throws CrmException
*/
public function getAccountById(string $crmId, array $fields): array
{
try {
$company = $this->getNewInstance()->crm()->companies()->basicApi()->getById(
$crmId,
implode(',', $fields),
);
} catch (CompanyApiException $e) {
$this->log->info('[Hubspot] Failed to fetch account', [
'crm_id' => $crmId,
'reason' => $e->getMessage(),
]);
throw $e;
}
if (! $company instanceof CompaniesWithAssociations) {
throw new CrmException('Account not found');
}
return [
'id' => $company->getId(),
'properties' => $company->getProperties(),
];
}
/**
* @throws ContactApiException
* @throws CrmException
*/
public function getContactById(string $crmId, array $fields): array
{
try {
$contact = $this->getNewInstance()->crm()->contacts()->basicApi()->getById(
$crmId,
implode(',', $fields)
);
} catch (ContactApiException $e) {
$this->log->info('[Hubspot] Failed to fetch contact', [
'crm_id' => $crmId,
'reason' => $e->getMessage(),
]);
throw $e;
}
if (! $contact instanceof ContactsWithAssociations) {
throw new CrmException('Contact not found');
}
return [
'id' => $contact->getId(),
'properties' => $contact->getProperties(),
];
}
/**
* This is email search request that Hubspot offers as GET (more generous quota)
*/
public function getContactByEmail(string $email, array $fields = []): array
{
try {
$contact = $this->getNewInstance()->crm()->contacts()->basicApi()->getById(
$email,
implode(',', $fields),
null,
false,
'email'
);
return [
'id' => $contact->getId(),
'properties' => $contact->getProperties(),
];
} catch (ContactApiException $e) {
$this->log->info('[Hubspot] Failed to fetch contact', [
'email' => $email,
'reason' => $e->getMessage(),
]);
return [];
}
}
/**
* @throws CrmException
*/
public function fetchProperty(string $objectType, string $propertyId): Property
{
$result = $this->getNewInstance()->crm()->properties()->coreApi()->getByName($objectType, $propertyId);
if (! $result instanceof Property) {
$this->log->error('[Hubspot] Failed to fetch property', [
'object_type' => $objectType,
'property_id' => $propertyId,
'reason' => $result->getMessage(),
]);
throw new CrmException('Failed to fetch property');
}
return $result;
}
/**
* @return array<CrmFieldOption>
*/
public function fetchPropertyOptions(string $objectType, string $propertyId): array
{
/** @var array<CrmFieldOption> */
return $this->fetchProperty($objectType, $propertyId)->getOptions();
}
/**
* @return array<array{id:string, label:string, deleted:bool}>
*/
public function fetchCallDispositions(): array
{
/** @var Response $response */
$response = $this->getInstance()->engagements()->getCallDispositions();
/**
* @var array<array{
* id:string,
* label:string,
* deleted: bool
* }>
*/
return $response->toArray();
}
/**
* @return array<CrmFieldOption>
*/
public function fetchOpportunityPipelineStages(): array
{
$stages = [];
$apiResponse = $this->getNewInstance()->crm()->pipelines()->pipelinesApi()->getAll('deals');
if ($apiResponse instanceof Error) {
$this->log->error('[Hubspot] Failed to fetch opportunity pipelines', [
'reason' => $apiResponse->getMessage(),
]);
return [];
}
foreach ($apiResponse->getResults() as $pipeline) {
$pipelineStages = array_map(
static function (PipelineStage $stage) {
return [
'id' => $stage->getId(),
'label' => $stage->getLabel(),
];
},
$pipeline->getStages()
);
$stages = array_merge($stages, $pipelineStages);
}
return $stages;
}
public function fetchOpportunityPipelines(): array
{
$pipelines = [];
try {
$apiResponse = $this->makeRequest('/crm/v3/pipelines/deals');
} catch (\Exception $e) {
$this->log->info('[Hubspot] Failed to fetch opportunity pipelines', [
'reason' => $e->getMessage(),
]);
return [];
}
$response = $apiResponse->toArray();
foreach ($response['results'] as $pipeline) {
$pipelines[] = [
'id' => $pipeline['id'],
'label' => $pipeline['label'],
];
}
return $pipelines;
}
/**
* @return array<CrmFieldOption>
*/
public function fetchMeetingOutcomeFieldOptions(Field $field): array
{
return $field->getCrmProviderId() === 'meetingOutcome'
? $this->fetchMeetingOutcomeTypes()
: $this->fetchCallActivityTypes();
}
public function fetchMeetingOutcomeTypes(): array
{
return $this->extractMeetingTypeOptions(
'[URL_WITH_CREDENTIALS] Response $response */
$response = $this->getInstance()
->getClient()
->request('GET', $endpoint);
/**
* @var array<array{
* value: string,
* label: string,
* displayOrder: int
* }> $optionData
*/
$optionData = $response->toArray()['options'] ?? [];
$options = [];
foreach ($optionData as $item) {
$options[] = [
'id' => $item['value'],
'value' => $item['value'],
'label' => $item['label'],
'display_order' => $item['displayOrder'],
];
}
return $options;
}
/**
* @return array<CrmFieldOption>
*/
public function fetchDispositionFieldOptions(): array
{
$options = [];
$dispositions = $this->fetchCallDispositions();
foreach ($dispositions as $disposition) {
if ($disposition['deleted'] !== false) {
continue;
}
$option['value'] = $disposition['id'];
$option['id'] = $disposition['id'];
$option['label'] = $disposition['label'];
$options[] = $option;
}
return $options;
}
/**
* @return array<CrmFieldOption>
*/
public function fetchOpportunityFieldOptions(Field $field): array
{
if ($field->isStageField()) {
return $this->fetchOpportunityPipelineStages();
}
if ($field->isPipelineField()) {
return $this->fetchOpportunityPipelines();
}
return $this->fetchPropertyOptions('deals', $field->getCrmProviderId());
}
/**
* @throws BadRequest
* @throws HubspotException
*/
public function makeRequest(string $endpoint, $method = 'GET', $payload = [], ?string $queryString = null)
{
$endpoint = self::BASE_URL . $endpoint;
if ($method === 'GET') {
$response = $this->getInstance()->getClient()?->request(
method: $method,
endpoint: $endpoint,
query_string: $queryString
);
} else {
$response = $this->getInstance()->getClient()->request($method, $endpoint, [
'json' => ($payload),
]);
}
$max = $response->getHeaderLine('X-HubSpot-RateLimit-Max'); // "110"
$remaining = $response->getHeaderLine('X-HubSpot-RateLimit-Remaining'); // "109"
$interval = $response->getHeaderLine('X-HubSpot-RateLimit-Interval-Milliseconds'); // "10000"
$body = json_decode((string) $response->getBody(), true);
\Illuminate\Support\Facades\Log::channel('custom_channel')->info('$max ' . PHP_EOL . print_r($max, true));
\Illuminate\Support\Facades\Log::channel('custom_channel')->info('$remaining ' . PHP_EOL . print_r($remaining, true));
\Illuminate\Support\Facades\Log::channel('custom_channel')->info('$interval ' . PHP_EOL . print_r($interval, true));
\Illuminate\Support\Facades\Log::channel('custom_channel')->info('$body ' . PHP_EOL . print_r($body, true));
return $response;
}
/**
* @throws BadRequest
* @throws HubspotException
*/
public function createMeeting(array $payload): Response
{
$endpoint = '/crm/v3/objects/meetings';
return $this->makeRequest($endpoint, 'POST', $payload);
}
/**
* @throws BadRequest
* @throws HubspotException
*/
public function updateMeeting(string $meetingId, array $payload): Response
{
$endpoint = '/crm/v3/objects/meetings/' . $meetingId;
return $this->makeRequest($endpoint, 'PATCH', $payload);
}
/**
* @throws \Exception
*/
public function createNote(
string $body,
string $ownerId,
int $timestamp,
string $objectId,
NoteObject $noteObject
): ?string {
try {
$noteInput = new SimplePublicObjectInput([
'properties' => [
'hs_note_body' => $body,
'hubspot_owner_id' => $ownerId,
'hs_timestamp' => $timestamp,
],
]);
// Create note
$note = $this->getNewInstance()->crm()->objects()->basicApi()->create('note', $noteInput);
$this->getNewInstance()->crm()->objects()->associationsApi()->create(
'note',
$note->getId(),
$this->getNoteObject($noteObject),
$objectId,
$this->getNoteAssociationType($noteObject),
);
return $note->getId();
} catch (\Exception $e) {
$this->log->error('[Hubspot] Failed to create note', [
'objectId' => $objectId,
'noteObject' => $noteObject->getObjectType(),
'reason' => $e->getMessage(),
]);
\Sentry::captureException($e);
}
return null;
}
public function updateEngagement(string $objectId, array $engagement, array $metadata): void
{
$this->getInstance()->engagements()->update($objectId, $engagement, $metadata);
}
public function getEngagementData(string $engagementId): array
{
$engagement = $this->getInstance()->engagements()->get($engagementId);
return $engagement->toArray();
}
public function createEngagement(array $engagement, array $associations, array $metadata): Response
{
return $this->getInstance()
->engagements()
->create($engagement, $associations, $metadata);
}
public function isUnauthorizedException(\Exception $e): bool
{
// Check for specific HubSpot API exception types first
if ($e instanceof BadRequest) {
// BadRequest can contain 401 status codes
return $e->getCode() === 401;
}
// Check for HTTP client exceptions with status codes
if ($e instanceof \GuzzleHttp\Exception\RequestException && $e->hasResponse()) {
$response = $e->getResponse();
if ($response !== null) {
return $response->getStatusCode() === 401;
}
}
// Check for Guzzle HTTP exceptions
if ($e instanceof \GuzzleHttp\Exception\ClientException) {
return $e->getCode() === 401;
}
// Fallback to string matching as last resort, but be more specific
$message = strtolower($e->getMessage());
return str_contains($message, '401 unauthorized') ||
str_contains($message, 'http 401') ||
str_contains($message, 'status code 401') ||
(preg_match('/\b401\b/', $message) && str_contains($message, 'unauthorized'));
}
/**
* Validates and refreshes the access token if needed before API requests.
* This ensures long-running processes don't fail due to token expiration.
*
* @throws SocialAccountTokenInvalidException
*/
public function ensureValidToken(): void
{
if ($this->oauthAccount === null) {
return;
}
$newToken = $this->tokenManager->ensureValidToken($this->oauthAccount);
if ($newToken !== null) {
$this->accessToken = $newToken;
}
}
public function getConfig()
{
return $this->config;
}
// returns only active (archived=false)
public function getOwners(): array
{
return $this->getNewInstance()->crm()->owners()->getAll();
}
/**
* @param bool $archived
*
* @return array<Owner>|[]
*/
public function getOwnersArchived(bool $archived = true): array
{
$endpoint = '/crm/v3/owners';
$queryParams = [
'archived' => $archived ? 'true' : 'false',
];
$queryString = http_build_query($queryParams);
$owners = [];
try {
$response = $this->makeRequest(endpoint: $endpoint, queryString: $queryString);
$responseData = $response?->toArray();
foreach ($responseData['results'] as $result) {
try {
$owners[] = Owner::create($result);
} catch (Throwable $e) {
$this->log->error('[HubSpot] Failed to process owner data', [
'result' => $result,
'error' => $e->getMessage(),
]);
continue;
}
}
} catch (Throwable $e) {
$this->log->error('HubSpot] Failed to fetch owners', [
'archived' => $archived,
'error' => $e->getMessage(),
]);
return [];
}
return $owners;
}
public function getMeeting(string $engagementId): ObjectWithAssociations
{
return $this->getNewInstance()->crm()->objects()->basicApi()
->getById('meeting', $engagementId, null, 'contact,company,deal');
}
public function deleteEngagement(string $engagementId): void
{
$this->getInstance()->engagements()->delete((int) $engagementId);
}
public function getAssociationsData(array $ids, string $fromObject, string $toObject): array
{
$associationData = [];
$idChunks = array_chunk($ids, self::ASSOCIATIONS_BATCH_SIZE_LIMIT);
foreach ($idChunks as $idChunk) {
try {
$batchInput = new \HubSpot\Client\Crm\Associations\Model\BatchInputPublicObjectId();
$batchInput->setInputs(array_map(function ($id) {
$publicObjectId = new \HubSpot\Client\Crm\Associations\Model\PublicObjectId();
$publicObjectId->setId($id);
return $publicObjectId;
}, $idChunk));
$associatedObjectsData = $this
->getNewInstance()
->crm()
->associations()
->batchApi()
->read($fromObject, $toObject, $batchInput);
if ($associatedObjectsData instanceof \HubSpot\Client\Crm\Associations\Model\BatchResponsePublicAssociationMulti) {
foreach ($associatedObjectsData->getResults() as $association) {
$from = $association->getFrom()->getId();
$toAssociations = $association->getTo();
if (! empty($toAssociations)) {
$associationData[$from] = array_map(function ($item) {
return $item->getId();
}, $toAssociations);
}
}
}
} catch (\Exception $e) {
$this->log->error('[Hubspot] Failed to fetch associations', [
'from_object' => $fromObject,
'to_object' => $toObject,
'reason' => $e->getMessage(),
]);
}
}
return $associationData;
}
/**
* @throws \Exception
*/
private function getNoteAssociationType(NoteObject $noteObject): string
{
return match($noteObject) {
NoteObject::Opportunity => 'note_to_deal',
NoteObject::Lead, NoteObject::Contact => 'note_to_contact', // or 'note_to_lead' if your portal supports it
NoteObject::Account => 'note_to_company',
NoteObject::Call, NoteObject::Event => throw new \Exception('Not supported'),
};
}
/**
* @throws \Exception
*/
private function getNoteObject(NoteObject $noteObject): string
{
return match($noteObject) {
NoteObject::Opportunity => 'deal',
NoteObject::Lead, NoteObject::Contact => 'contact',
NoteObject::Account => 'company',
NoteObject::Call, NoteObject::Event => throw new \Exception('Not supported'),
};
}
public function addAssociations(string $objectType, string $associationType, array $payload): Response
{
$endpoint = "/crm/v4/associations/$objectType/$associationType/batch/create";
return $this->makeRequest($endpoint, 'POST', $payload);
}
public function removeAssociations(string $objectType, string $associationType, array $payload): Response
{
$endpoint = "/crm/v4/associations/$objectType/$associationType/batch/archive";
return $this->makeRequest($endpoint, 'POST', $payload);
}
}
Project
Project
New File or Directory…
Expand Selected
Collapse All
Options
Hide...
|
2723
|
NULL
|
NULL
|
NULL
|
|
2726
|
111
|
34
|
2026-05-07T11:37:43.668177+00:00
|
/Users/lukas/.screenpipe/data/data/2026-05-07/1778 /Users/lukas/.screenpipe/data/data/2026-05-07/1778153863668_m1.jpg...
|
iTerm2
|
DEV (docker)
|
True
|
NULL
|
monitor_1
|
NULL
|
NULL
|
NULL
|
NULL
|
Last login: Thu May 7 09:44:56 on ttys006
Poetry Last login: Thu May 7 09:44:56 on ttys006
Poetry could not find a pyproject.toml file in /Users/lukas/jiminny/app or its parents
Poetry could not find a pyproject.toml file in /Users/lukas/jiminny/app or its parents
lukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/jiminny/app (JY-20773-fix-automated-reports-user-pilot-tracking) $ dev
root@docker_lamp_1:/home/jiminny# php artisan crm:sync-opportunity --teamId=2 --from='2026-05-01 00:00:00'
Syncing opportunity for Hubspot
Your HubSpot account has become disconnected. Please login to Jiminny to reconnect. skipping...
root@docker_lamp_1:/home/jiminny# php artisan jiminny:token-info -A 1499 -R
----------------------------------------------------------------------------------------------------
access_token => CNeR-JHgMxIZQlNQMl8kQEwrAgwACAkUAhIJBB4BAQEDBxiCiYwCIN7Y_Qwo0qwCMhTnG549n-YtNuc1jgj-2AsLPSmw3DoyQlNQMl8kQEwrAiUACBkGawEFThwBARIBAQEEATEEAQEBAQEBAQEBAQUBEggBAQEBAYlCFPAsBNZxoDp5kAcRyeBlQoE5SM7DSgNuYTFSAFoAYABo3tj9DHAAeAA
----------------------------------------------------------------------------------------------------
access_token_expires_at => 2026-05-07 11:41:20
----------------------------------------------------------------------------------------------------
refresh_token => d5ab04e2-2109-4c0b-b513-8cba1dd54371
----------------------------------------------------------------------------------------------------
refresh_token_expires_at =>
----------------------------------------------------------------------------------------------------
root@docker_lamp_1:/home/jiminny# php artisan crm:sync-opportunity --teamId=2 --from='2026-05-01 00:00:00'
Syncing opportunity for Hubspot
Syncing opportunities modified since 2026-05-01 00:00:00...
Synced 6 opportunities.
root@docker_lamp_1:/home/jiminny# php artisan optimize:clear && supervisorctl restart all
INFO Clearing cached bootstrap files.
config [PASSWORD_DOTS] 7.40ms DONE
cache [PASSWORD_DOTS] 35.37ms DONE
compiled [PASSWORD_DOTS] 2.98ms DONE
events [PASSWORD_DOTS] 1.70ms DONE
routes [PASSWORD_DOTS] 1.64ms DONE
views [PASSWORD_DOTS] 6.48ms DONE
jiminny-worker-processing-delayed:jiminny-worker-processing-delayed_00: stopped
jiminny-worker-processing-2:jiminny-worker-processing-2_00: stopped
jiminny-worker-processing-3:jiminny-worker-processing-3_00: stopped
jiminny-worker-processing-4:jiminny-worker-processing-4_00: stopped
jiminny-worker-processing-5:jiminny-worker-processing-5_00: stopped
worker-analytics:worker-analytics_00: stopped
worker-crm-update:worker-crm-update_00: stopped
worker-download:worker-download_00: stopped
worker-nudges:worker-nudges_00: stopped
worker-crm-sync:worker-crm-sync_00: stopped
worker:worker_00: stopped
worker-calendar:worker-calendar_00: stopped
worker-emails:worker-emails_00: stopped
jiminny-worker-processing-1:jiminny-worker-processing-1_00: stopped
worker-audio:worker-audio_00: stopped
worker-conferences:worker-conferences_00: stopped
worker-es-update:worker-es-update_00: stopped
artisan-schedule:artisan-schedule_00: stopped
artisan-schedule:artisan-schedule_00: started
jiminny-worker-processing-1:jiminny-worker-processing-1_00: started
jiminny-worker-processing-2:jiminny-worker-processing-2_00: started
jiminny-worker-processing-3:jiminny-worker-processing-3_00: started
jiminny-worker-processing-4:jiminny-worker-processing-4_00: started
jiminny-worker-processing-5:jiminny-worker-processing-5_00: started
jiminny-worker-processing-delayed:jiminny-worker-processing-delayed_00: started
worker:worker_00: started
worker-analytics:worker-analytics_00: started
worker-audio:worker-audio_00: started
worker-calendar:worker-calendar_00: started
worker-conferences:worker-conferences_00: started
worker-crm-sync:worker-crm-sync_00: started
worker-crm-update:worker-crm-update_00: started
worker-download:worker-download_00: started
worker-emails:worker-emails_00: started
worker-es-update:worker-es-update_00: started
worker-nudges:worker-nudges_00: started
root@docker_lamp_1:/home/jiminny# php artisan optimize:clear && supervisorctl restart all
INFO Clearing cached bootstrap files.
config [PASSWORD_DOTS] 6.95ms DONE
cache [PASSWORD_DOTS] 9.00ms DONE
compiled [PASSWORD_DOTS] 2.63ms DONE
events [PASSWORD_DOTS] 2.35ms DONE
routes [PASSWORD_DOTS] 1.64ms DONE
views [PASSWORD_DOTS] 3.18ms DONE
DOCKER
Close Tab
DEV (docker)
Close Tab
APP (-zsh)
Close Tab
-zsh
Close Tab
screenpipe"
Close Tab
-zsh
Close Tab
⌥⌘1
DEV (docker)...
|
[{"role":"AXTextArea","text [{"role":"AXTextArea","text":"Last login: Thu May 7 09:44:56 on ttys006\n\nPoetry could not find a pyproject.toml file in /Users/lukas/jiminny/app or its parents\n\nPoetry could not find a pyproject.toml file in /Users/lukas/jiminny/app or its parents\nlukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/jiminny/app (JY-20773-fix-automated-reports-user-pilot-tracking) $ dev\nroot@docker_lamp_1:/home/jiminny# php artisan crm:sync-opportunity --teamId=2 --from='2026-05-01 00:00:00'\nSyncing opportunity for Hubspot\nYour HubSpot account has become disconnected. Please login to Jiminny to reconnect. skipping...\nroot@docker_lamp_1:/home/jiminny# php artisan jiminny:token-info -A 1499 -R\n----------------------------------------------------------------------------------------------------\naccess_token => CNeR-JHgMxIZQlNQMl8kQEwrAgwACAkUAhIJBB4BAQEDBxiCiYwCIN7Y_Qwo0qwCMhTnG549n-YtNuc1jgj-2AsLPSmw3DoyQlNQMl8kQEwrAiUACBkGawEFThwBARIBAQEEATEEAQEBAQEBAQEBAQUBEggBAQEBAYlCFPAsBNZxoDp5kAcRyeBlQoE5SM7DSgNuYTFSAFoAYABo3tj9DHAAeAA\n----------------------------------------------------------------------------------------------------\naccess_token_expires_at => 2026-05-07 11:41:20\n----------------------------------------------------------------------------------------------------\nrefresh_token => d5ab04e2-2109-4c0b-b513-8cba1dd54371\n----------------------------------------------------------------------------------------------------\nrefresh_token_expires_at => \n----------------------------------------------------------------------------------------------------\nroot@docker_lamp_1:/home/jiminny# php artisan crm:sync-opportunity --teamId=2 --from='2026-05-01 00:00:00'\nSyncing opportunity for Hubspot\nSyncing opportunities modified since 2026-05-01 00:00:00...\nSynced 6 opportunities.\nroot@docker_lamp_1:/home/jiminny# php artisan optimize:clear && supervisorctl restart all\n\n INFO Clearing cached bootstrap files. \n\n config ............................................................................................................................... 7.40ms DONE\n cache ............................................................................................................................... 35.37ms DONE\n compiled ............................................................................................................................. 2.98ms DONE\n events ............................................................................................................................... 1.70ms DONE\n routes ............................................................................................................................... 1.64ms DONE\n views ................................................................................................................................ 6.48ms DONE\n\njiminny-worker-processing-delayed:jiminny-worker-processing-delayed_00: stopped\njiminny-worker-processing-2:jiminny-worker-processing-2_00: stopped\njiminny-worker-processing-3:jiminny-worker-processing-3_00: stopped\njiminny-worker-processing-4:jiminny-worker-processing-4_00: stopped\njiminny-worker-processing-5:jiminny-worker-processing-5_00: stopped\nworker-analytics:worker-analytics_00: stopped\nworker-crm-update:worker-crm-update_00: stopped\nworker-download:worker-download_00: stopped\nworker-nudges:worker-nudges_00: stopped\nworker-crm-sync:worker-crm-sync_00: stopped\nworker:worker_00: stopped\nworker-calendar:worker-calendar_00: stopped\nworker-emails:worker-emails_00: stopped\njiminny-worker-processing-1:jiminny-worker-processing-1_00: stopped\nworker-audio:worker-audio_00: stopped\nworker-conferences:worker-conferences_00: stopped\nworker-es-update:worker-es-update_00: stopped\nartisan-schedule:artisan-schedule_00: stopped\nartisan-schedule:artisan-schedule_00: started\njiminny-worker-processing-1:jiminny-worker-processing-1_00: started\njiminny-worker-processing-2:jiminny-worker-processing-2_00: started\njiminny-worker-processing-3:jiminny-worker-processing-3_00: started\njiminny-worker-processing-4:jiminny-worker-processing-4_00: started\njiminny-worker-processing-5:jiminny-worker-processing-5_00: started\njiminny-worker-processing-delayed:jiminny-worker-processing-delayed_00: started\nworker:worker_00: started\nworker-analytics:worker-analytics_00: started\nworker-audio:worker-audio_00: started\nworker-calendar:worker-calendar_00: started\nworker-conferences:worker-conferences_00: started\nworker-crm-sync:worker-crm-sync_00: started\nworker-crm-update:worker-crm-update_00: started\nworker-download:worker-download_00: started\nworker-emails:worker-emails_00: started\nworker-es-update:worker-es-update_00: started\nworker-nudges:worker-nudges_00: started\nroot@docker_lamp_1:/home/jiminny# php artisan optimize:clear && supervisorctl restart all\n\n INFO Clearing cached bootstrap files. \n\n config ............................................................................................................................... 6.95ms DONE\n cache ................................................................................................................................ 9.00ms DONE\n compiled ............................................................................................................................. 2.63ms DONE\n events ............................................................................................................................... 2.35ms DONE\n routes ............................................................................................................................... 1.64ms DONE\n views ................................................................................................................................ 3.18ms DONE","depth":4,"on_screen":true,"value":"Last login: Thu May 7 09:44:56 on ttys006\n\nPoetry could not find a pyproject.toml file in /Users/lukas/jiminny/app or its parents\n\nPoetry could not find a pyproject.toml file in /Users/lukas/jiminny/app or its parents\nlukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/jiminny/app (JY-20773-fix-automated-reports-user-pilot-tracking) $ dev\nroot@docker_lamp_1:/home/jiminny# php artisan crm:sync-opportunity --teamId=2 --from='2026-05-01 00:00:00'\nSyncing opportunity for Hubspot\nYour HubSpot account has become disconnected. Please login to Jiminny to reconnect. skipping...\nroot@docker_lamp_1:/home/jiminny# php artisan jiminny:token-info -A 1499 -R\n----------------------------------------------------------------------------------------------------\naccess_token => CNeR-JHgMxIZQlNQMl8kQEwrAgwACAkUAhIJBB4BAQEDBxiCiYwCIN7Y_Qwo0qwCMhTnG549n-YtNuc1jgj-2AsLPSmw3DoyQlNQMl8kQEwrAiUACBkGawEFThwBARIBAQEEATEEAQEBAQEBAQEBAQUBEggBAQEBAYlCFPAsBNZxoDp5kAcRyeBlQoE5SM7DSgNuYTFSAFoAYABo3tj9DHAAeAA\n----------------------------------------------------------------------------------------------------\naccess_token_expires_at => 2026-05-07 11:41:20\n----------------------------------------------------------------------------------------------------\nrefresh_token => d5ab04e2-2109-4c0b-b513-8cba1dd54371\n----------------------------------------------------------------------------------------------------\nrefresh_token_expires_at => \n----------------------------------------------------------------------------------------------------\nroot@docker_lamp_1:/home/jiminny# php artisan crm:sync-opportunity --teamId=2 --from='2026-05-01 00:00:00'\nSyncing opportunity for Hubspot\nSyncing opportunities modified since 2026-05-01 00:00:00...\nSynced 6 opportunities.\nroot@docker_lamp_1:/home/jiminny# php artisan optimize:clear && supervisorctl restart all\n\n INFO Clearing cached bootstrap files. \n\n config ............................................................................................................................... 7.40ms DONE\n cache ............................................................................................................................... 35.37ms DONE\n compiled ............................................................................................................................. 2.98ms DONE\n events ............................................................................................................................... 1.70ms DONE\n routes ............................................................................................................................... 1.64ms DONE\n views ................................................................................................................................ 6.48ms DONE\n\njiminny-worker-processing-delayed:jiminny-worker-processing-delayed_00: stopped\njiminny-worker-processing-2:jiminny-worker-processing-2_00: stopped\njiminny-worker-processing-3:jiminny-worker-processing-3_00: stopped\njiminny-worker-processing-4:jiminny-worker-processing-4_00: stopped\njiminny-worker-processing-5:jiminny-worker-processing-5_00: stopped\nworker-analytics:worker-analytics_00: stopped\nworker-crm-update:worker-crm-update_00: stopped\nworker-download:worker-download_00: stopped\nworker-nudges:worker-nudges_00: stopped\nworker-crm-sync:worker-crm-sync_00: stopped\nworker:worker_00: stopped\nworker-calendar:worker-calendar_00: stopped\nworker-emails:worker-emails_00: stopped\njiminny-worker-processing-1:jiminny-worker-processing-1_00: stopped\nworker-audio:worker-audio_00: stopped\nworker-conferences:worker-conferences_00: stopped\nworker-es-update:worker-es-update_00: stopped\nartisan-schedule:artisan-schedule_00: stopped\nartisan-schedule:artisan-schedule_00: started\njiminny-worker-processing-1:jiminny-worker-processing-1_00: started\njiminny-worker-processing-2:jiminny-worker-processing-2_00: started\njiminny-worker-processing-3:jiminny-worker-processing-3_00: started\njiminny-worker-processing-4:jiminny-worker-processing-4_00: started\njiminny-worker-processing-5:jiminny-worker-processing-5_00: started\njiminny-worker-processing-delayed:jiminny-worker-processing-delayed_00: started\nworker:worker_00: started\nworker-analytics:worker-analytics_00: started\nworker-audio:worker-audio_00: started\nworker-calendar:worker-calendar_00: started\nworker-conferences:worker-conferences_00: started\nworker-crm-sync:worker-crm-sync_00: started\nworker-crm-update:worker-crm-update_00: started\nworker-download:worker-download_00: started\nworker-emails:worker-emails_00: started\nworker-es-update:worker-es-update_00: started\nworker-nudges:worker-nudges_00: started\nroot@docker_lamp_1:/home/jiminny# php artisan optimize:clear && supervisorctl restart all\n\n INFO Clearing cached bootstrap files. \n\n config ............................................................................................................................... 6.95ms DONE\n cache ................................................................................................................................ 9.00ms DONE\n compiled ............................................................................................................................. 2.63ms DONE\n events ............................................................................................................................... 2.35ms DONE\n routes ............................................................................................................................... 1.64ms DONE\n views ................................................................................................................................ 3.18ms DONE","is_focused":true},{"role":"AXRadioButton","text":"DOCKER","depth":2,"bounds":{"left":0.0,"top":0.05888889,"width":0.16458334,"height":0.026666667},"on_screen":true,"role_description":"radio button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Close Tab","depth":3,"bounds":{"left":0.004166667,"top":0.06333333,"width":0.011111111,"height":0.017777778},"on_screen":true,"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXRadioButton","text":"DEV (docker)","depth":2,"bounds":{"left":0.16458334,"top":0.05888889,"width":0.16458334,"height":0.026666667},"on_screen":true,"role_description":"radio button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Close Tab","depth":3,"bounds":{"left":0.16875,"top":0.06333333,"width":0.011111111,"height":0.017777778},"on_screen":true,"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXRadioButton","text":"APP (-zsh)","depth":2,"bounds":{"left":0.32916668,"top":0.05888889,"width":0.16423611,"height":0.026666667},"on_screen":true,"role_description":"radio button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Close Tab","depth":3,"bounds":{"left":0.33333334,"top":0.06333333,"width":0.011111111,"height":0.017777778},"on_screen":true,"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXRadioButton","text":"-zsh","depth":2,"bounds":{"left":0.49340278,"top":0.05888889,"width":0.16423611,"height":0.026666667},"on_screen":true,"role_description":"radio button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Close Tab","depth":3,"bounds":{"left":0.49756944,"top":0.06333333,"width":0.011111111,"height":0.017777778},"on_screen":true,"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXRadioButton","text":"screenpipe\"","depth":2,"bounds":{"left":0.6576389,"top":0.05888889,"width":0.16423611,"height":0.026666667},"on_screen":true,"role_description":"radio button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Close Tab","depth":3,"bounds":{"left":0.66180557,"top":0.06333333,"width":0.011111111,"height":0.017777778},"on_screen":true,"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXRadioButton","text":"-zsh","depth":2,"bounds":{"left":0.821875,"top":0.05888889,"width":0.16423611,"height":0.026666667},"on_screen":true,"role_description":"radio button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Close Tab","depth":3,"bounds":{"left":0.82604164,"top":0.06333333,"width":0.011111111,"height":0.017777778},"on_screen":true,"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"⌥⌘1","depth":1,"bounds":{"left":0.95763886,"top":0.032222223,"width":0.03888889,"height":0.018888889},"on_screen":true,"automation_id":"_NS:8","role_description":"text"},{"role":"AXStaticText","text":"DEV (docker)","depth":1,"bounds":{"left":0.47013888,"top":0.033333335,"width":0.0625,"height":0.017777778},"on_screen":true,"role_description":"text"}]...
|
-8630923114717171666
|
1549507351543982356
|
visual_change
|
accessibility
|
NULL
|
Last login: Thu May 7 09:44:56 on ttys006
Poetry Last login: Thu May 7 09:44:56 on ttys006
Poetry could not find a pyproject.toml file in /Users/lukas/jiminny/app or its parents
Poetry could not find a pyproject.toml file in /Users/lukas/jiminny/app or its parents
lukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/jiminny/app (JY-20773-fix-automated-reports-user-pilot-tracking) $ dev
root@docker_lamp_1:/home/jiminny# php artisan crm:sync-opportunity --teamId=2 --from='2026-05-01 00:00:00'
Syncing opportunity for Hubspot
Your HubSpot account has become disconnected. Please login to Jiminny to reconnect. skipping...
root@docker_lamp_1:/home/jiminny# php artisan jiminny:token-info -A 1499 -R
----------------------------------------------------------------------------------------------------
access_token => CNeR-JHgMxIZQlNQMl8kQEwrAgwACAkUAhIJBB4BAQEDBxiCiYwCIN7Y_Qwo0qwCMhTnG549n-YtNuc1jgj-2AsLPSmw3DoyQlNQMl8kQEwrAiUACBkGawEFThwBARIBAQEEATEEAQEBAQEBAQEBAQUBEggBAQEBAYlCFPAsBNZxoDp5kAcRyeBlQoE5SM7DSgNuYTFSAFoAYABo3tj9DHAAeAA
----------------------------------------------------------------------------------------------------
access_token_expires_at => 2026-05-07 11:41:20
----------------------------------------------------------------------------------------------------
refresh_token => d5ab04e2-2109-4c0b-b513-8cba1dd54371
----------------------------------------------------------------------------------------------------
refresh_token_expires_at =>
----------------------------------------------------------------------------------------------------
root@docker_lamp_1:/home/jiminny# php artisan crm:sync-opportunity --teamId=2 --from='2026-05-01 00:00:00'
Syncing opportunity for Hubspot
Syncing opportunities modified since 2026-05-01 00:00:00...
Synced 6 opportunities.
root@docker_lamp_1:/home/jiminny# php artisan optimize:clear && supervisorctl restart all
INFO Clearing cached bootstrap files.
config [PASSWORD_DOTS] 7.40ms DONE
cache [PASSWORD_DOTS] 35.37ms DONE
compiled [PASSWORD_DOTS] 2.98ms DONE
events [PASSWORD_DOTS] 1.70ms DONE
routes [PASSWORD_DOTS] 1.64ms DONE
views [PASSWORD_DOTS] 6.48ms DONE
jiminny-worker-processing-delayed:jiminny-worker-processing-delayed_00: stopped
jiminny-worker-processing-2:jiminny-worker-processing-2_00: stopped
jiminny-worker-processing-3:jiminny-worker-processing-3_00: stopped
jiminny-worker-processing-4:jiminny-worker-processing-4_00: stopped
jiminny-worker-processing-5:jiminny-worker-processing-5_00: stopped
worker-analytics:worker-analytics_00: stopped
worker-crm-update:worker-crm-update_00: stopped
worker-download:worker-download_00: stopped
worker-nudges:worker-nudges_00: stopped
worker-crm-sync:worker-crm-sync_00: stopped
worker:worker_00: stopped
worker-calendar:worker-calendar_00: stopped
worker-emails:worker-emails_00: stopped
jiminny-worker-processing-1:jiminny-worker-processing-1_00: stopped
worker-audio:worker-audio_00: stopped
worker-conferences:worker-conferences_00: stopped
worker-es-update:worker-es-update_00: stopped
artisan-schedule:artisan-schedule_00: stopped
artisan-schedule:artisan-schedule_00: started
jiminny-worker-processing-1:jiminny-worker-processing-1_00: started
jiminny-worker-processing-2:jiminny-worker-processing-2_00: started
jiminny-worker-processing-3:jiminny-worker-processing-3_00: started
jiminny-worker-processing-4:jiminny-worker-processing-4_00: started
jiminny-worker-processing-5:jiminny-worker-processing-5_00: started
jiminny-worker-processing-delayed:jiminny-worker-processing-delayed_00: started
worker:worker_00: started
worker-analytics:worker-analytics_00: started
worker-audio:worker-audio_00: started
worker-calendar:worker-calendar_00: started
worker-conferences:worker-conferences_00: started
worker-crm-sync:worker-crm-sync_00: started
worker-crm-update:worker-crm-update_00: started
worker-download:worker-download_00: started
worker-emails:worker-emails_00: started
worker-es-update:worker-es-update_00: started
worker-nudges:worker-nudges_00: started
root@docker_lamp_1:/home/jiminny# php artisan optimize:clear && supervisorctl restart all
INFO Clearing cached bootstrap files.
config [PASSWORD_DOTS] 6.95ms DONE
cache [PASSWORD_DOTS] 9.00ms DONE
compiled [PASSWORD_DOTS] 2.63ms DONE
events [PASSWORD_DOTS] 2.35ms DONE
routes [PASSWORD_DOTS] 1.64ms DONE
views [PASSWORD_DOTS] 3.18ms DONE
DOCKER
Close Tab
DEV (docker)
Close Tab
APP (-zsh)
Close Tab
-zsh
Close Tab
screenpipe"
Close Tab
-zsh
Close Tab
⌥⌘1
DEV (docker)...
|
2724
|
NULL
|
NULL
|
NULL
|
|
2733
|
112
|
38
|
2026-05-07T11:37:57.040595+00:00
|
/Users/lukas/.screenpipe/data/data/2026-05-07/1778 /Users/lukas/.screenpipe/data/data/2026-05-07/1778153877040_m2.jpg...
|
iTerm2
|
DEV (docker)
|
True
|
NULL
|
monitor_2
|
NULL
|
NULL
|
NULL
|
NULL
|
Last login: Thu May 7 09:44:56 on ttys006
Poetry Last login: Thu May 7 09:44:56 on ttys006
Poetry could not find a pyproject.toml file in /Users/lukas/jiminny/app or its parents
Poetry could not find a pyproject.toml file in /Users/lukas/jiminny/app or its parents
lukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/jiminny/app (JY-20773-fix-automated-reports-user-pilot-tracking) $ dev
root@docker_lamp_1:/home/jiminny# php artisan crm:sync-opportunity --teamId=2 --from='2026-05-01 00:00:00'
Syncing opportunity for Hubspot
Your HubSpot account has become disconnected. Please login to Jiminny to reconnect. skipping...
root@docker_lamp_1:/home/jiminny# php artisan jiminny:token-info -A 1499 -R
----------------------------------------------------------------------------------------------------
access_token => CNeR-JHgMxIZQlNQMl8kQEwrAgwACAkUAhIJBB4BAQEDBxiCiYwCIN7Y_Qwo0qwCMhTnG549n-YtNuc1jgj-2AsLPSmw3DoyQlNQMl8kQEwrAiUACBkGawEFThwBARIBAQEEATEEAQEBAQEBAQEBAQUBEggBAQEBAYlCFPAsBNZxoDp5kAcRyeBlQoE5SM7DSgNuYTFSAFoAYABo3tj9DHAAeAA
----------------------------------------------------------------------------------------------------
access_token_expires_at => 2026-05-07 11:41:20
----------------------------------------------------------------------------------------------------
refresh_token => d5ab04e2-2109-4c0b-b513-8cba1dd54371
----------------------------------------------------------------------------------------------------
refresh_token_expires_at =>
----------------------------------------------------------------------------------------------------
root@docker_lamp_1:/home/jiminny# php artisan crm:sync-opportunity --teamId=2 --from='2026-05-01 00:00:00'
Syncing opportunity for Hubspot
Syncing opportunities modified since 2026-05-01 00:00:00...
Synced 6 opportunities.
root@docker_lamp_1:/home/jiminny# php artisan optimize:clear && supervisorctl restart all
INFO Clearing cached bootstrap files.
config [PASSWORD_DOTS] 7.40ms DONE
cache [PASSWORD_DOTS] 35.37ms DONE
compiled [PASSWORD_DOTS] 2.98ms DONE
events [PASSWORD_DOTS] 1.70ms DONE
routes [PASSWORD_DOTS] 1.64ms DONE
views [PASSWORD_DOTS] 6.48ms DONE
jiminny-worker-processing-delayed:jiminny-worker-processing-delayed_00: stopped
jiminny-worker-processing-2:jiminny-worker-processing-2_00: stopped
jiminny-worker-processing-3:jiminny-worker-processing-3_00: stopped
jiminny-worker-processing-4:jiminny-worker-processing-4_00: stopped
jiminny-worker-processing-5:jiminny-worker-processing-5_00: stopped
worker-analytics:worker-analytics_00: stopped
worker-crm-update:worker-crm-update_00: stopped
worker-download:worker-download_00: stopped
worker-nudges:worker-nudges_00: stopped
worker-crm-sync:worker-crm-sync_00: stopped
worker:worker_00: stopped
worker-calendar:worker-calendar_00: stopped
worker-emails:worker-emails_00: stopped
jiminny-worker-processing-1:jiminny-worker-processing-1_00: stopped
worker-audio:worker-audio_00: stopped
worker-conferences:worker-conferences_00: stopped
worker-es-update:worker-es-update_00: stopped
artisan-schedule:artisan-schedule_00: stopped
artisan-schedule:artisan-schedule_00: started
jiminny-worker-processing-1:jiminny-worker-processing-1_00: started
jiminny-worker-processing-2:jiminny-worker-processing-2_00: started
jiminny-worker-processing-3:jiminny-worker-processing-3_00: started
jiminny-worker-processing-4:jiminny-worker-processing-4_00: started
jiminny-worker-processing-5:jiminny-worker-processing-5_00: started
jiminny-worker-processing-delayed:jiminny-worker-processing-delayed_00: started
worker:worker_00: started
worker-analytics:worker-analytics_00: started
worker-audio:worker-audio_00: started
worker-calendar:worker-calendar_00: started
worker-conferences:worker-conferences_00: started
worker-crm-sync:worker-crm-sync_00: started
worker-crm-update:worker-crm-update_00: started
worker-download:worker-download_00: started
worker-emails:worker-emails_00: started
worker-es-update:worker-es-update_00: started
worker-nudges:worker-nudges_00: started
root@docker_lamp_1:/home/jiminny# php artisan optimize:clear && supervisorctl restart all
INFO Clearing cached bootstrap files.
config [PASSWORD_DOTS] 6.95ms DONE
cache [PASSWORD_DOTS] 9.00ms DONE
compiled [PASSWORD_DOTS] 2.63ms DONE
events [PASSWORD_DOTS] 2.35ms DONE
routes [PASSWORD_DOTS] 1.64ms DONE
views [PASSWORD_DOTS] 3.18ms DONE
jiminny-worker-processing-delayed:jiminny-worker-processing-delayed_00: stopped
worker-download:worker-download_00: stopped
jiminny-worker-processing-2:jiminny-worker-processing-2_00: stopped
jiminny-worker-processing-3:jiminny-worker-processing-3_00: stopped
jiminny-worker-processing-4:jiminny-worker-processing-4_00: stopped
jiminny-worker-processing-5:jiminny-worker-processing-5_00: stopped
worker-analytics:worker-analytics_00: stopped
worker-crm-update:worker-crm-update_00: stopped
artisan-schedule:artisan-schedule_00: stopped
worker-nudges:worker-nudges_00: stopped
worker:worker_00: stopped
jiminny-worker-processing-1:jiminny-worker-processing-1_00: stopped
worker-audio:worker-audio_00: stopped
worker-calendar:worker-calendar_00: stopped
worker-conferences:worker-conferences_00: stopped
worker-crm-sync:worker-crm-sync_00: stopped
worker-emails:worker-emails_00: stopped
worker-es-update:worker-es-update_00: stopped
artisan-schedule:artisan-schedule_00: started
jiminny-worker-processing-1:jiminny-worker-processing-1_00: started
jiminny-worker-processing-2:jiminny-worker-processing-2_00: started
jiminny-worker-processing-3:jiminny-worker-processing-3_00: started
jiminny-worker-processing-4:jiminny-worker-processing-4_00: started
jiminny-worker-processing-5:jiminny-worker-processing-5_00: started
jiminny-worker-processing-delayed:jiminny-worker-processing-delayed_00: started
worker:worker_00: started
worker-analytics:worker-analytics_00: started
worker-audio:worker-audio_00: started
worker-calendar:worker-calendar_00: started
worker-conferences:worker-conferences_00: started
worker-crm-sync:worker-crm-sync_00: started
worker-crm-update:worker-crm-update_00: started
worker-download:worker-download_00: started
worker-emails:worker-emails_00: started
worker-es-update:worker-es-update_00: started
worker-nudges:worker-nudges_00: started
root@docker_lamp_1:/home/jiminny#
DOCKER
Close Tab
DEV (docker)
Close Tab
APP (-zsh)
Close Tab
-zsh
Close Tab
screenpipe"
Close Tab
-zsh
Close Tab
⌥⌘1
DEV (docker)...
|
[{"role":"AXTextArea","text [{"role":"AXTextArea","text":"Last login: Thu May 7 09:44:56 on ttys006\n\nPoetry could not find a pyproject.toml file in /Users/lukas/jiminny/app or its parents\n\nPoetry could not find a pyproject.toml file in /Users/lukas/jiminny/app or its parents\nlukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/jiminny/app (JY-20773-fix-automated-reports-user-pilot-tracking) $ dev\nroot@docker_lamp_1:/home/jiminny# php artisan crm:sync-opportunity --teamId=2 --from='2026-05-01 00:00:00'\nSyncing opportunity for Hubspot\nYour HubSpot account has become disconnected. Please login to Jiminny to reconnect. skipping...\nroot@docker_lamp_1:/home/jiminny# php artisan jiminny:token-info -A 1499 -R\n----------------------------------------------------------------------------------------------------\naccess_token => CNeR-JHgMxIZQlNQMl8kQEwrAgwACAkUAhIJBB4BAQEDBxiCiYwCIN7Y_Qwo0qwCMhTnG549n-YtNuc1jgj-2AsLPSmw3DoyQlNQMl8kQEwrAiUACBkGawEFThwBARIBAQEEATEEAQEBAQEBAQEBAQUBEggBAQEBAYlCFPAsBNZxoDp5kAcRyeBlQoE5SM7DSgNuYTFSAFoAYABo3tj9DHAAeAA\n----------------------------------------------------------------------------------------------------\naccess_token_expires_at => 2026-05-07 11:41:20\n----------------------------------------------------------------------------------------------------\nrefresh_token => d5ab04e2-2109-4c0b-b513-8cba1dd54371\n----------------------------------------------------------------------------------------------------\nrefresh_token_expires_at => \n----------------------------------------------------------------------------------------------------\nroot@docker_lamp_1:/home/jiminny# php artisan crm:sync-opportunity --teamId=2 --from='2026-05-01 00:00:00'\nSyncing opportunity for Hubspot\nSyncing opportunities modified since 2026-05-01 00:00:00...\nSynced 6 opportunities.\nroot@docker_lamp_1:/home/jiminny# php artisan optimize:clear && supervisorctl restart all\n\n INFO Clearing cached bootstrap files. \n\n config ............................................................................................................................... 7.40ms DONE\n cache ............................................................................................................................... 35.37ms DONE\n compiled ............................................................................................................................. 2.98ms DONE\n events ............................................................................................................................... 1.70ms DONE\n routes ............................................................................................................................... 1.64ms DONE\n views ................................................................................................................................ 6.48ms DONE\n\njiminny-worker-processing-delayed:jiminny-worker-processing-delayed_00: stopped\njiminny-worker-processing-2:jiminny-worker-processing-2_00: stopped\njiminny-worker-processing-3:jiminny-worker-processing-3_00: stopped\njiminny-worker-processing-4:jiminny-worker-processing-4_00: stopped\njiminny-worker-processing-5:jiminny-worker-processing-5_00: stopped\nworker-analytics:worker-analytics_00: stopped\nworker-crm-update:worker-crm-update_00: stopped\nworker-download:worker-download_00: stopped\nworker-nudges:worker-nudges_00: stopped\nworker-crm-sync:worker-crm-sync_00: stopped\nworker:worker_00: stopped\nworker-calendar:worker-calendar_00: stopped\nworker-emails:worker-emails_00: stopped\njiminny-worker-processing-1:jiminny-worker-processing-1_00: stopped\nworker-audio:worker-audio_00: stopped\nworker-conferences:worker-conferences_00: stopped\nworker-es-update:worker-es-update_00: stopped\nartisan-schedule:artisan-schedule_00: stopped\nartisan-schedule:artisan-schedule_00: started\njiminny-worker-processing-1:jiminny-worker-processing-1_00: started\njiminny-worker-processing-2:jiminny-worker-processing-2_00: started\njiminny-worker-processing-3:jiminny-worker-processing-3_00: started\njiminny-worker-processing-4:jiminny-worker-processing-4_00: started\njiminny-worker-processing-5:jiminny-worker-processing-5_00: started\njiminny-worker-processing-delayed:jiminny-worker-processing-delayed_00: started\nworker:worker_00: started\nworker-analytics:worker-analytics_00: started\nworker-audio:worker-audio_00: started\nworker-calendar:worker-calendar_00: started\nworker-conferences:worker-conferences_00: started\nworker-crm-sync:worker-crm-sync_00: started\nworker-crm-update:worker-crm-update_00: started\nworker-download:worker-download_00: started\nworker-emails:worker-emails_00: started\nworker-es-update:worker-es-update_00: started\nworker-nudges:worker-nudges_00: started\nroot@docker_lamp_1:/home/jiminny# php artisan optimize:clear && supervisorctl restart all\n\n INFO Clearing cached bootstrap files. \n\n config ............................................................................................................................... 6.95ms DONE\n cache ................................................................................................................................ 9.00ms DONE\n compiled ............................................................................................................................. 2.63ms DONE\n events ............................................................................................................................... 2.35ms DONE\n routes ............................................................................................................................... 1.64ms DONE\n views ................................................................................................................................ 3.18ms DONE\n\njiminny-worker-processing-delayed:jiminny-worker-processing-delayed_00: stopped\nworker-download:worker-download_00: stopped\njiminny-worker-processing-2:jiminny-worker-processing-2_00: stopped\njiminny-worker-processing-3:jiminny-worker-processing-3_00: stopped\njiminny-worker-processing-4:jiminny-worker-processing-4_00: stopped\njiminny-worker-processing-5:jiminny-worker-processing-5_00: stopped\nworker-analytics:worker-analytics_00: stopped\nworker-crm-update:worker-crm-update_00: stopped\nartisan-schedule:artisan-schedule_00: stopped\nworker-nudges:worker-nudges_00: stopped\nworker:worker_00: stopped\njiminny-worker-processing-1:jiminny-worker-processing-1_00: stopped\nworker-audio:worker-audio_00: stopped\nworker-calendar:worker-calendar_00: stopped\nworker-conferences:worker-conferences_00: stopped\nworker-crm-sync:worker-crm-sync_00: stopped\nworker-emails:worker-emails_00: stopped\nworker-es-update:worker-es-update_00: stopped\nartisan-schedule:artisan-schedule_00: started\njiminny-worker-processing-1:jiminny-worker-processing-1_00: started\njiminny-worker-processing-2:jiminny-worker-processing-2_00: started\njiminny-worker-processing-3:jiminny-worker-processing-3_00: started\njiminny-worker-processing-4:jiminny-worker-processing-4_00: started\njiminny-worker-processing-5:jiminny-worker-processing-5_00: started\njiminny-worker-processing-delayed:jiminny-worker-processing-delayed_00: started\nworker:worker_00: started\nworker-analytics:worker-analytics_00: started\nworker-audio:worker-audio_00: started\nworker-calendar:worker-calendar_00: started\nworker-conferences:worker-conferences_00: started\nworker-crm-sync:worker-crm-sync_00: started\nworker-crm-update:worker-crm-update_00: started\nworker-download:worker-download_00: started\nworker-emails:worker-emails_00: started\nworker-es-update:worker-es-update_00: started\nworker-nudges:worker-nudges_00: started\nroot@docker_lamp_1:/home/jiminny#","depth":4,"bounds":{"left":0.27027926,"top":0.0,"width":0.4800532,"height":1.0},"on_screen":true,"value":"Last login: Thu May 7 09:44:56 on ttys006\n\nPoetry could not find a pyproject.toml file in /Users/lukas/jiminny/app or its parents\n\nPoetry could not find a pyproject.toml file in /Users/lukas/jiminny/app or its parents\nlukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/jiminny/app (JY-20773-fix-automated-reports-user-pilot-tracking) $ dev\nroot@docker_lamp_1:/home/jiminny# php artisan crm:sync-opportunity --teamId=2 --from='2026-05-01 00:00:00'\nSyncing opportunity for Hubspot\nYour HubSpot account has become disconnected. Please login to Jiminny to reconnect. skipping...\nroot@docker_lamp_1:/home/jiminny# php artisan jiminny:token-info -A 1499 -R\n----------------------------------------------------------------------------------------------------\naccess_token => CNeR-JHgMxIZQlNQMl8kQEwrAgwACAkUAhIJBB4BAQEDBxiCiYwCIN7Y_Qwo0qwCMhTnG549n-YtNuc1jgj-2AsLPSmw3DoyQlNQMl8kQEwrAiUACBkGawEFThwBARIBAQEEATEEAQEBAQEBAQEBAQUBEggBAQEBAYlCFPAsBNZxoDp5kAcRyeBlQoE5SM7DSgNuYTFSAFoAYABo3tj9DHAAeAA\n----------------------------------------------------------------------------------------------------\naccess_token_expires_at => 2026-05-07 11:41:20\n----------------------------------------------------------------------------------------------------\nrefresh_token => d5ab04e2-2109-4c0b-b513-8cba1dd54371\n----------------------------------------------------------------------------------------------------\nrefresh_token_expires_at => \n----------------------------------------------------------------------------------------------------\nroot@docker_lamp_1:/home/jiminny# php artisan crm:sync-opportunity --teamId=2 --from='2026-05-01 00:00:00'\nSyncing opportunity for Hubspot\nSyncing opportunities modified since 2026-05-01 00:00:00...\nSynced 6 opportunities.\nroot@docker_lamp_1:/home/jiminny# php artisan optimize:clear && supervisorctl restart all\n\n INFO Clearing cached bootstrap files. \n\n config ............................................................................................................................... 7.40ms DONE\n cache ............................................................................................................................... 35.37ms DONE\n compiled ............................................................................................................................. 2.98ms DONE\n events ............................................................................................................................... 1.70ms DONE\n routes ............................................................................................................................... 1.64ms DONE\n views ................................................................................................................................ 6.48ms DONE\n\njiminny-worker-processing-delayed:jiminny-worker-processing-delayed_00: stopped\njiminny-worker-processing-2:jiminny-worker-processing-2_00: stopped\njiminny-worker-processing-3:jiminny-worker-processing-3_00: stopped\njiminny-worker-processing-4:jiminny-worker-processing-4_00: stopped\njiminny-worker-processing-5:jiminny-worker-processing-5_00: stopped\nworker-analytics:worker-analytics_00: stopped\nworker-crm-update:worker-crm-update_00: stopped\nworker-download:worker-download_00: stopped\nworker-nudges:worker-nudges_00: stopped\nworker-crm-sync:worker-crm-sync_00: stopped\nworker:worker_00: stopped\nworker-calendar:worker-calendar_00: stopped\nworker-emails:worker-emails_00: stopped\njiminny-worker-processing-1:jiminny-worker-processing-1_00: stopped\nworker-audio:worker-audio_00: stopped\nworker-conferences:worker-conferences_00: stopped\nworker-es-update:worker-es-update_00: stopped\nartisan-schedule:artisan-schedule_00: stopped\nartisan-schedule:artisan-schedule_00: started\njiminny-worker-processing-1:jiminny-worker-processing-1_00: started\njiminny-worker-processing-2:jiminny-worker-processing-2_00: started\njiminny-worker-processing-3:jiminny-worker-processing-3_00: started\njiminny-worker-processing-4:jiminny-worker-processing-4_00: started\njiminny-worker-processing-5:jiminny-worker-processing-5_00: started\njiminny-worker-processing-delayed:jiminny-worker-processing-delayed_00: started\nworker:worker_00: started\nworker-analytics:worker-analytics_00: started\nworker-audio:worker-audio_00: started\nworker-calendar:worker-calendar_00: started\nworker-conferences:worker-conferences_00: started\nworker-crm-sync:worker-crm-sync_00: started\nworker-crm-update:worker-crm-update_00: started\nworker-download:worker-download_00: started\nworker-emails:worker-emails_00: started\nworker-es-update:worker-es-update_00: started\nworker-nudges:worker-nudges_00: started\nroot@docker_lamp_1:/home/jiminny# php artisan optimize:clear && supervisorctl restart all\n\n INFO Clearing cached bootstrap files. \n\n config ............................................................................................................................... 6.95ms DONE\n cache ................................................................................................................................ 9.00ms DONE\n compiled ............................................................................................................................. 2.63ms DONE\n events ............................................................................................................................... 2.35ms DONE\n routes ............................................................................................................................... 1.64ms DONE\n views ................................................................................................................................ 3.18ms DONE\n\njiminny-worker-processing-delayed:jiminny-worker-processing-delayed_00: stopped\nworker-download:worker-download_00: stopped\njiminny-worker-processing-2:jiminny-worker-processing-2_00: stopped\njiminny-worker-processing-3:jiminny-worker-processing-3_00: stopped\njiminny-worker-processing-4:jiminny-worker-processing-4_00: stopped\njiminny-worker-processing-5:jiminny-worker-processing-5_00: stopped\nworker-analytics:worker-analytics_00: stopped\nworker-crm-update:worker-crm-update_00: stopped\nartisan-schedule:artisan-schedule_00: stopped\nworker-nudges:worker-nudges_00: stopped\nworker:worker_00: stopped\njiminny-worker-processing-1:jiminny-worker-processing-1_00: stopped\nworker-audio:worker-audio_00: stopped\nworker-calendar:worker-calendar_00: stopped\nworker-conferences:worker-conferences_00: stopped\nworker-crm-sync:worker-crm-sync_00: stopped\nworker-emails:worker-emails_00: stopped\nworker-es-update:worker-es-update_00: stopped\nartisan-schedule:artisan-schedule_00: started\njiminny-worker-processing-1:jiminny-worker-processing-1_00: started\njiminny-worker-processing-2:jiminny-worker-processing-2_00: started\njiminny-worker-processing-3:jiminny-worker-processing-3_00: started\njiminny-worker-processing-4:jiminny-worker-processing-4_00: started\njiminny-worker-processing-5:jiminny-worker-processing-5_00: started\njiminny-worker-processing-delayed:jiminny-worker-processing-delayed_00: started\nworker:worker_00: started\nworker-analytics:worker-analytics_00: started\nworker-audio:worker-audio_00: started\nworker-calendar:worker-calendar_00: started\nworker-conferences:worker-conferences_00: started\nworker-crm-sync:worker-crm-sync_00: started\nworker-crm-update:worker-crm-update_00: started\nworker-download:worker-download_00: started\nworker-emails:worker-emails_00: started\nworker-es-update:worker-es-update_00: started\nworker-nudges:worker-nudges_00: started\nroot@docker_lamp_1:/home/jiminny#","is_focused":true},{"role":"AXRadioButton","text":"DOCKER","depth":2,"bounds":{"left":0.27027926,"top":1.0,"width":0.0787899,"height":-0.042298436},"on_screen":true,"role_description":"radio button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Close Tab","depth":3,"bounds":{"left":0.27227393,"top":1.0,"width":0.005319149,"height":-0.04549086},"on_screen":true,"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXRadioButton","text":"DEV (docker)","depth":2,"bounds":{"left":0.34906915,"top":1.0,"width":0.0787899,"height":-0.042298436},"on_screen":true,"role_description":"radio button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Close Tab","depth":3,"bounds":{"left":0.35106382,"top":1.0,"width":0.005319149,"height":-0.04549086},"on_screen":true,"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXRadioButton","text":"APP (-zsh)","depth":2,"bounds":{"left":0.42785904,"top":1.0,"width":0.07862367,"height":-0.042298436},"on_screen":true,"role_description":"radio button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Close Tab","depth":3,"bounds":{"left":0.42985374,"top":1.0,"width":0.005319149,"height":-0.04549086},"on_screen":true,"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXRadioButton","text":"-zsh","depth":2,"bounds":{"left":0.5064827,"top":1.0,"width":0.07862367,"height":-0.042298436},"on_screen":true,"role_description":"radio button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Close Tab","depth":3,"bounds":{"left":0.5084774,"top":1.0,"width":0.005319149,"height":-0.04549086},"on_screen":true,"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXRadioButton","text":"screenpipe\"","depth":2,"bounds":{"left":0.5851064,"top":1.0,"width":0.07862367,"height":-0.042298436},"on_screen":true,"role_description":"radio button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Close Tab","depth":3,"bounds":{"left":0.58710104,"top":1.0,"width":0.005319149,"height":-0.04549086},"on_screen":true,"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXRadioButton","text":"-zsh","depth":2,"bounds":{"left":0.66373,"top":1.0,"width":0.07862367,"height":-0.042298436},"on_screen":true,"role_description":"radio button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Close Tab","depth":3,"bounds":{"left":0.66572475,"top":1.0,"width":0.005319149,"height":-0.04549086},"on_screen":true,"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"⌥⌘1","depth":1,"bounds":{"left":0.7287234,"top":1.0,"width":0.01861702,"height":-0.023144484},"on_screen":true,"automation_id":"_NS:8","role_description":"text"},{"role":"AXStaticText","text":"DEV (docker)","depth":1,"bounds":{"left":0.49534574,"top":1.0,"width":0.029920213,"height":-0.02394259},"on_screen":true,"role_description":"text"}]...
|
4477091458398049585
|
3855350223320295684
|
click
|
accessibility
|
NULL
|
Last login: Thu May 7 09:44:56 on ttys006
Poetry Last login: Thu May 7 09:44:56 on ttys006
Poetry could not find a pyproject.toml file in /Users/lukas/jiminny/app or its parents
Poetry could not find a pyproject.toml file in /Users/lukas/jiminny/app or its parents
lukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/jiminny/app (JY-20773-fix-automated-reports-user-pilot-tracking) $ dev
root@docker_lamp_1:/home/jiminny# php artisan crm:sync-opportunity --teamId=2 --from='2026-05-01 00:00:00'
Syncing opportunity for Hubspot
Your HubSpot account has become disconnected. Please login to Jiminny to reconnect. skipping...
root@docker_lamp_1:/home/jiminny# php artisan jiminny:token-info -A 1499 -R
----------------------------------------------------------------------------------------------------
access_token => CNeR-JHgMxIZQlNQMl8kQEwrAgwACAkUAhIJBB4BAQEDBxiCiYwCIN7Y_Qwo0qwCMhTnG549n-YtNuc1jgj-2AsLPSmw3DoyQlNQMl8kQEwrAiUACBkGawEFThwBARIBAQEEATEEAQEBAQEBAQEBAQUBEggBAQEBAYlCFPAsBNZxoDp5kAcRyeBlQoE5SM7DSgNuYTFSAFoAYABo3tj9DHAAeAA
----------------------------------------------------------------------------------------------------
access_token_expires_at => 2026-05-07 11:41:20
----------------------------------------------------------------------------------------------------
refresh_token => d5ab04e2-2109-4c0b-b513-8cba1dd54371
----------------------------------------------------------------------------------------------------
refresh_token_expires_at =>
----------------------------------------------------------------------------------------------------
root@docker_lamp_1:/home/jiminny# php artisan crm:sync-opportunity --teamId=2 --from='2026-05-01 00:00:00'
Syncing opportunity for Hubspot
Syncing opportunities modified since 2026-05-01 00:00:00...
Synced 6 opportunities.
root@docker_lamp_1:/home/jiminny# php artisan optimize:clear && supervisorctl restart all
INFO Clearing cached bootstrap files.
config [PASSWORD_DOTS] 7.40ms DONE
cache [PASSWORD_DOTS] 35.37ms DONE
compiled [PASSWORD_DOTS] 2.98ms DONE
events [PASSWORD_DOTS] 1.70ms DONE
routes [PASSWORD_DOTS] 1.64ms DONE
views [PASSWORD_DOTS] 6.48ms DONE
jiminny-worker-processing-delayed:jiminny-worker-processing-delayed_00: stopped
jiminny-worker-processing-2:jiminny-worker-processing-2_00: stopped
jiminny-worker-processing-3:jiminny-worker-processing-3_00: stopped
jiminny-worker-processing-4:jiminny-worker-processing-4_00: stopped
jiminny-worker-processing-5:jiminny-worker-processing-5_00: stopped
worker-analytics:worker-analytics_00: stopped
worker-crm-update:worker-crm-update_00: stopped
worker-download:worker-download_00: stopped
worker-nudges:worker-nudges_00: stopped
worker-crm-sync:worker-crm-sync_00: stopped
worker:worker_00: stopped
worker-calendar:worker-calendar_00: stopped
worker-emails:worker-emails_00: stopped
jiminny-worker-processing-1:jiminny-worker-processing-1_00: stopped
worker-audio:worker-audio_00: stopped
worker-conferences:worker-conferences_00: stopped
worker-es-update:worker-es-update_00: stopped
artisan-schedule:artisan-schedule_00: stopped
artisan-schedule:artisan-schedule_00: started
jiminny-worker-processing-1:jiminny-worker-processing-1_00: started
jiminny-worker-processing-2:jiminny-worker-processing-2_00: started
jiminny-worker-processing-3:jiminny-worker-processing-3_00: started
jiminny-worker-processing-4:jiminny-worker-processing-4_00: started
jiminny-worker-processing-5:jiminny-worker-processing-5_00: started
jiminny-worker-processing-delayed:jiminny-worker-processing-delayed_00: started
worker:worker_00: started
worker-analytics:worker-analytics_00: started
worker-audio:worker-audio_00: started
worker-calendar:worker-calendar_00: started
worker-conferences:worker-conferences_00: started
worker-crm-sync:worker-crm-sync_00: started
worker-crm-update:worker-crm-update_00: started
worker-download:worker-download_00: started
worker-emails:worker-emails_00: started
worker-es-update:worker-es-update_00: started
worker-nudges:worker-nudges_00: started
root@docker_lamp_1:/home/jiminny# php artisan optimize:clear && supervisorctl restart all
INFO Clearing cached bootstrap files.
config [PASSWORD_DOTS] 6.95ms DONE
cache [PASSWORD_DOTS] 9.00ms DONE
compiled [PASSWORD_DOTS] 2.63ms DONE
events [PASSWORD_DOTS] 2.35ms DONE
routes [PASSWORD_DOTS] 1.64ms DONE
views [PASSWORD_DOTS] 3.18ms DONE
jiminny-worker-processing-delayed:jiminny-worker-processing-delayed_00: stopped
worker-download:worker-download_00: stopped
jiminny-worker-processing-2:jiminny-worker-processing-2_00: stopped
jiminny-worker-processing-3:jiminny-worker-processing-3_00: stopped
jiminny-worker-processing-4:jiminny-worker-processing-4_00: stopped
jiminny-worker-processing-5:jiminny-worker-processing-5_00: stopped
worker-analytics:worker-analytics_00: stopped
worker-crm-update:worker-crm-update_00: stopped
artisan-schedule:artisan-schedule_00: stopped
worker-nudges:worker-nudges_00: stopped
worker:worker_00: stopped
jiminny-worker-processing-1:jiminny-worker-processing-1_00: stopped
worker-audio:worker-audio_00: stopped
worker-calendar:worker-calendar_00: stopped
worker-conferences:worker-conferences_00: stopped
worker-crm-sync:worker-crm-sync_00: stopped
worker-emails:worker-emails_00: stopped
worker-es-update:worker-es-update_00: stopped
artisan-schedule:artisan-schedule_00: started
jiminny-worker-processing-1:jiminny-worker-processing-1_00: started
jiminny-worker-processing-2:jiminny-worker-processing-2_00: started
jiminny-worker-processing-3:jiminny-worker-processing-3_00: started
jiminny-worker-processing-4:jiminny-worker-processing-4_00: started
jiminny-worker-processing-5:jiminny-worker-processing-5_00: started
jiminny-worker-processing-delayed:jiminny-worker-processing-delayed_00: started
worker:worker_00: started
worker-analytics:worker-analytics_00: started
worker-audio:worker-audio_00: started
worker-calendar:worker-calendar_00: started
worker-conferences:worker-conferences_00: started
worker-crm-sync:worker-crm-sync_00: started
worker-crm-update:worker-crm-update_00: started
worker-download:worker-download_00: started
worker-emails:worker-emails_00: started
worker-es-update:worker-es-update_00: started
worker-nudges:worker-nudges_00: started
root@docker_lamp_1:/home/jiminny#
DOCKER
Close Tab
DEV (docker)
Close Tab
APP (-zsh)
Close Tab
-zsh
Close Tab
screenpipe"
Close Tab
-zsh
Close Tab
⌥⌘1
DEV (docker)...
|
2731
|
NULL
|
NULL
|
NULL
|
|
2734
|
111
|
38
|
2026-05-07T11:37:58.914078+00:00
|
/Users/lukas/.screenpipe/data/data/2026-05-07/1778 /Users/lukas/.screenpipe/data/data/2026-05-07/1778153878914_m1.jpg...
|
iTerm2
|
DEV (docker)
|
True
|
NULL
|
monitor_1
|
NULL
|
NULL
|
NULL
|
NULL
|
Last login: Thu May 7 09:44:56 on ttys006
Poetry Last login: Thu May 7 09:44:56 on ttys006
Poetry could not find a pyproject.toml file in /Users/lukas/jiminny/app or its parents
Poetry could not find a pyproject.toml file in /Users/lukas/jiminny/app or its parents
lukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/jiminny/app (JY-20773-fix-automated-reports-user-pilot-tracking) $ dev
root@docker_lamp_1:/home/jiminny# php artisan crm:sync-opportunity --teamId=2 --from='2026-05-01 00:00:00'
Syncing opportunity for Hubspot
Your HubSpot account has become disconnected. Please login to Jiminny to reconnect. skipping...
root@docker_lamp_1:/home/jiminny# php artisan jiminny:token-info -A 1499 -R
----------------------------------------------------------------------------------------------------
access_token => CNeR-JHgMxIZQlNQMl8kQEwrAgwACAkUAhIJBB4BAQEDBxiCiYwCIN7Y_Qwo0qwCMhTnG549n-YtNuc1jgj-2AsLPSmw3DoyQlNQMl8kQEwrAiUACBkGawEFThwBARIBAQEEATEEAQEBAQEBAQEBAQUBEggBAQEBAYlCFPAsBNZxoDp5kAcRyeBlQoE5SM7DSgNuYTFSAFoAYABo3tj9DHAAeAA
----------------------------------------------------------------------------------------------------
access_token_expires_at => 2026-05-07 11:41:20
----------------------------------------------------------------------------------------------------
refresh_token => d5ab04e2-2109-4c0b-b513-8cba1dd54371
----------------------------------------------------------------------------------------------------
refresh_token_expires_at =>
----------------------------------------------------------------------------------------------------
root@docker_lamp_1:/home/jiminny# php artisan crm:sync-opportunity --teamId=2 --from='2026-05-01 00:00:00'
Syncing opportunity for Hubspot
Syncing opportunities modified since 2026-05-01 00:00:00...
Synced 6 opportunities.
root@docker_lamp_1:/home/jiminny# php artisan optimize:clear && supervisorctl restart all
INFO Clearing cached bootstrap files.
config [PASSWORD_DOTS] 7.40ms DONE
cache [PASSWORD_DOTS] 35.37ms DONE
compiled [PASSWORD_DOTS] 2.98ms DONE
events [PASSWORD_DOTS] 1.70ms DONE
routes [PASSWORD_DOTS] 1.64ms DONE
views [PASSWORD_DOTS] 6.48ms DONE
jiminny-worker-processing-delayed:jiminny-worker-processing-delayed_00: stopped
jiminny-worker-processing-2:jiminny-worker-processing-2_00: stopped
jiminny-worker-processing-3:jiminny-worker-processing-3_00: stopped
jiminny-worker-processing-4:jiminny-worker-processing-4_00: stopped
jiminny-worker-processing-5:jiminny-worker-processing-5_00: stopped
worker-analytics:worker-analytics_00: stopped
worker-crm-update:worker-crm-update_00: stopped
worker-download:worker-download_00: stopped
worker-nudges:worker-nudges_00: stopped
worker-crm-sync:worker-crm-sync_00: stopped
worker:worker_00: stopped
worker-calendar:worker-calendar_00: stopped
worker-emails:worker-emails_00: stopped
jiminny-worker-processing-1:jiminny-worker-processing-1_00: stopped
worker-audio:worker-audio_00: stopped
worker-conferences:worker-conferences_00: stopped
worker-es-update:worker-es-update_00: stopped
artisan-schedule:artisan-schedule_00: stopped
artisan-schedule:artisan-schedule_00: started
jiminny-worker-processing-1:jiminny-worker-processing-1_00: started
jiminny-worker-processing-2:jiminny-worker-processing-2_00: started
jiminny-worker-processing-3:jiminny-worker-processing-3_00: started
jiminny-worker-processing-4:jiminny-worker-processing-4_00: started
jiminny-worker-processing-5:jiminny-worker-processing-5_00: started
jiminny-worker-processing-delayed:jiminny-worker-processing-delayed_00: started
worker:worker_00: started
worker-analytics:worker-analytics_00: started
worker-audio:worker-audio_00: started
worker-calendar:worker-calendar_00: started
worker-conferences:worker-conferences_00: started
worker-crm-sync:worker-crm-sync_00: started
worker-crm-update:worker-crm-update_00: started
worker-download:worker-download_00: started
worker-emails:worker-emails_00: started
worker-es-update:worker-es-update_00: started
worker-nudges:worker-nudges_00: started
root@docker_lamp_1:/home/jiminny# php artisan optimize:clear && supervisorctl restart all
INFO Clearing cached bootstrap files.
config [PASSWORD_DOTS] 6.95ms DONE
cache [PASSWORD_DOTS] 9.00ms DONE
compiled [PASSWORD_DOTS] 2.63ms DONE
events [PASSWORD_DOTS] 2.35ms DONE
routes [PASSWORD_DOTS] 1.64ms DONE
views [PASSWORD_DOTS] 3.18ms DONE
jiminny-worker-processing-delayed:jiminny-worker-processing-delayed_00: stopped
worker-download:worker-download_00: stopped
jiminny-worker-processing-2:jiminny-worker-processing-2_00: stopped
jiminny-worker-processing-3:jiminny-worker-processing-3_00: stopped
jiminny-worker-processing-4:jiminny-worker-processing-4_00: stopped
jiminny-worker-processing-5:jiminny-worker-processing-5_00: stopped
worker-analytics:worker-analytics_00: stopped
worker-crm-update:worker-crm-update_00: stopped
artisan-schedule:artisan-schedule_00: stopped
worker-nudges:worker-nudges_00: stopped
worker:worker_00: stopped
jiminny-worker-processing-1:jiminny-worker-processing-1_00: stopped
worker-audio:worker-audio_00: stopped
worker-calendar:worker-calendar_00: stopped
worker-conferences:worker-conferences_00: stopped
worker-crm-sync:worker-crm-sync_00: stopped
worker-emails:worker-emails_00: stopped
worker-es-update:worker-es-update_00: stopped
artisan-schedule:artisan-schedule_00: started
jiminny-worker-processing-1:jiminny-worker-processing-1_00: started
jiminny-worker-processing-2:jiminny-worker-processing-2_00: started
jiminny-worker-processing-3:jiminny-worker-processing-3_00: started
jiminny-worker-processing-4:jiminny-worker-processing-4_00: started
jiminny-worker-processing-5:jiminny-worker-processing-5_00: started
jiminny-worker-processing-delayed:jiminny-worker-processing-delayed_00: started
worker:worker_00: started
worker-analytics:worker-analytics_00: started
worker-audio:worker-audio_00: started
worker-calendar:worker-calendar_00: started
worker-conferences:worker-conferences_00: started
worker-crm-sync:worker-crm-sync_00: started
worker-crm-update:worker-crm-update_00: started
worker-download:worker-download_00: started
worker-emails:worker-emails_00: started
worker-es-update:worker-es-update_00: started
worker-nudges:worker-nudges_00: started
root@docker_lamp_1:/home/jiminny# php artisan crm:sync-opportunity --teamId=2 --from='2026-05-01 00:00:00'
DOCKER
Close Tab
DEV (docker)
Close Tab
APP (-zsh)
Close Tab
-zsh
Close Tab
screenpipe"
Close Tab
-zsh
Close Tab
⌥⌘1
DEV (docker)...
|
[{"role":"AXTextArea","text [{"role":"AXTextArea","text":"Last login: Thu May 7 09:44:56 on ttys006\n\nPoetry could not find a pyproject.toml file in /Users/lukas/jiminny/app or its parents\n\nPoetry could not find a pyproject.toml file in /Users/lukas/jiminny/app or its parents\nlukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/jiminny/app (JY-20773-fix-automated-reports-user-pilot-tracking) $ dev\nroot@docker_lamp_1:/home/jiminny# php artisan crm:sync-opportunity --teamId=2 --from='2026-05-01 00:00:00'\nSyncing opportunity for Hubspot\nYour HubSpot account has become disconnected. Please login to Jiminny to reconnect. skipping...\nroot@docker_lamp_1:/home/jiminny# php artisan jiminny:token-info -A 1499 -R\n----------------------------------------------------------------------------------------------------\naccess_token => CNeR-JHgMxIZQlNQMl8kQEwrAgwACAkUAhIJBB4BAQEDBxiCiYwCIN7Y_Qwo0qwCMhTnG549n-YtNuc1jgj-2AsLPSmw3DoyQlNQMl8kQEwrAiUACBkGawEFThwBARIBAQEEATEEAQEBAQEBAQEBAQUBEggBAQEBAYlCFPAsBNZxoDp5kAcRyeBlQoE5SM7DSgNuYTFSAFoAYABo3tj9DHAAeAA\n----------------------------------------------------------------------------------------------------\naccess_token_expires_at => 2026-05-07 11:41:20\n----------------------------------------------------------------------------------------------------\nrefresh_token => d5ab04e2-2109-4c0b-b513-8cba1dd54371\n----------------------------------------------------------------------------------------------------\nrefresh_token_expires_at => \n----------------------------------------------------------------------------------------------------\nroot@docker_lamp_1:/home/jiminny# php artisan crm:sync-opportunity --teamId=2 --from='2026-05-01 00:00:00'\nSyncing opportunity for Hubspot\nSyncing opportunities modified since 2026-05-01 00:00:00...\nSynced 6 opportunities.\nroot@docker_lamp_1:/home/jiminny# php artisan optimize:clear && supervisorctl restart all\n\n INFO Clearing cached bootstrap files. \n\n config ............................................................................................................................... 7.40ms DONE\n cache ............................................................................................................................... 35.37ms DONE\n compiled ............................................................................................................................. 2.98ms DONE\n events ............................................................................................................................... 1.70ms DONE\n routes ............................................................................................................................... 1.64ms DONE\n views ................................................................................................................................ 6.48ms DONE\n\njiminny-worker-processing-delayed:jiminny-worker-processing-delayed_00: stopped\njiminny-worker-processing-2:jiminny-worker-processing-2_00: stopped\njiminny-worker-processing-3:jiminny-worker-processing-3_00: stopped\njiminny-worker-processing-4:jiminny-worker-processing-4_00: stopped\njiminny-worker-processing-5:jiminny-worker-processing-5_00: stopped\nworker-analytics:worker-analytics_00: stopped\nworker-crm-update:worker-crm-update_00: stopped\nworker-download:worker-download_00: stopped\nworker-nudges:worker-nudges_00: stopped\nworker-crm-sync:worker-crm-sync_00: stopped\nworker:worker_00: stopped\nworker-calendar:worker-calendar_00: stopped\nworker-emails:worker-emails_00: stopped\njiminny-worker-processing-1:jiminny-worker-processing-1_00: stopped\nworker-audio:worker-audio_00: stopped\nworker-conferences:worker-conferences_00: stopped\nworker-es-update:worker-es-update_00: stopped\nartisan-schedule:artisan-schedule_00: stopped\nartisan-schedule:artisan-schedule_00: started\njiminny-worker-processing-1:jiminny-worker-processing-1_00: started\njiminny-worker-processing-2:jiminny-worker-processing-2_00: started\njiminny-worker-processing-3:jiminny-worker-processing-3_00: started\njiminny-worker-processing-4:jiminny-worker-processing-4_00: started\njiminny-worker-processing-5:jiminny-worker-processing-5_00: started\njiminny-worker-processing-delayed:jiminny-worker-processing-delayed_00: started\nworker:worker_00: started\nworker-analytics:worker-analytics_00: started\nworker-audio:worker-audio_00: started\nworker-calendar:worker-calendar_00: started\nworker-conferences:worker-conferences_00: started\nworker-crm-sync:worker-crm-sync_00: started\nworker-crm-update:worker-crm-update_00: started\nworker-download:worker-download_00: started\nworker-emails:worker-emails_00: started\nworker-es-update:worker-es-update_00: started\nworker-nudges:worker-nudges_00: started\nroot@docker_lamp_1:/home/jiminny# php artisan optimize:clear && supervisorctl restart all\n\n INFO Clearing cached bootstrap files. \n\n config ............................................................................................................................... 6.95ms DONE\n cache ................................................................................................................................ 9.00ms DONE\n compiled ............................................................................................................................. 2.63ms DONE\n events ............................................................................................................................... 2.35ms DONE\n routes ............................................................................................................................... 1.64ms DONE\n views ................................................................................................................................ 3.18ms DONE\n\njiminny-worker-processing-delayed:jiminny-worker-processing-delayed_00: stopped\nworker-download:worker-download_00: stopped\njiminny-worker-processing-2:jiminny-worker-processing-2_00: stopped\njiminny-worker-processing-3:jiminny-worker-processing-3_00: stopped\njiminny-worker-processing-4:jiminny-worker-processing-4_00: stopped\njiminny-worker-processing-5:jiminny-worker-processing-5_00: stopped\nworker-analytics:worker-analytics_00: stopped\nworker-crm-update:worker-crm-update_00: stopped\nartisan-schedule:artisan-schedule_00: stopped\nworker-nudges:worker-nudges_00: stopped\nworker:worker_00: stopped\njiminny-worker-processing-1:jiminny-worker-processing-1_00: stopped\nworker-audio:worker-audio_00: stopped\nworker-calendar:worker-calendar_00: stopped\nworker-conferences:worker-conferences_00: stopped\nworker-crm-sync:worker-crm-sync_00: stopped\nworker-emails:worker-emails_00: stopped\nworker-es-update:worker-es-update_00: stopped\nartisan-schedule:artisan-schedule_00: started\njiminny-worker-processing-1:jiminny-worker-processing-1_00: started\njiminny-worker-processing-2:jiminny-worker-processing-2_00: started\njiminny-worker-processing-3:jiminny-worker-processing-3_00: started\njiminny-worker-processing-4:jiminny-worker-processing-4_00: started\njiminny-worker-processing-5:jiminny-worker-processing-5_00: started\njiminny-worker-processing-delayed:jiminny-worker-processing-delayed_00: started\nworker:worker_00: started\nworker-analytics:worker-analytics_00: started\nworker-audio:worker-audio_00: started\nworker-calendar:worker-calendar_00: started\nworker-conferences:worker-conferences_00: started\nworker-crm-sync:worker-crm-sync_00: started\nworker-crm-update:worker-crm-update_00: started\nworker-download:worker-download_00: started\nworker-emails:worker-emails_00: started\nworker-es-update:worker-es-update_00: started\nworker-nudges:worker-nudges_00: started\nroot@docker_lamp_1:/home/jiminny# php artisan crm:sync-opportunity --teamId=2 --from='2026-05-01 00:00:00'","depth":4,"on_screen":true,"value":"Last login: Thu May 7 09:44:56 on ttys006\n\nPoetry could not find a pyproject.toml file in /Users/lukas/jiminny/app or its parents\n\nPoetry could not find a pyproject.toml file in /Users/lukas/jiminny/app or its parents\nlukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/jiminny/app (JY-20773-fix-automated-reports-user-pilot-tracking) $ dev\nroot@docker_lamp_1:/home/jiminny# php artisan crm:sync-opportunity --teamId=2 --from='2026-05-01 00:00:00'\nSyncing opportunity for Hubspot\nYour HubSpot account has become disconnected. Please login to Jiminny to reconnect. skipping...\nroot@docker_lamp_1:/home/jiminny# php artisan jiminny:token-info -A 1499 -R\n----------------------------------------------------------------------------------------------------\naccess_token => CNeR-JHgMxIZQlNQMl8kQEwrAgwACAkUAhIJBB4BAQEDBxiCiYwCIN7Y_Qwo0qwCMhTnG549n-YtNuc1jgj-2AsLPSmw3DoyQlNQMl8kQEwrAiUACBkGawEFThwBARIBAQEEATEEAQEBAQEBAQEBAQUBEggBAQEBAYlCFPAsBNZxoDp5kAcRyeBlQoE5SM7DSgNuYTFSAFoAYABo3tj9DHAAeAA\n----------------------------------------------------------------------------------------------------\naccess_token_expires_at => 2026-05-07 11:41:20\n----------------------------------------------------------------------------------------------------\nrefresh_token => d5ab04e2-2109-4c0b-b513-8cba1dd54371\n----------------------------------------------------------------------------------------------------\nrefresh_token_expires_at => \n----------------------------------------------------------------------------------------------------\nroot@docker_lamp_1:/home/jiminny# php artisan crm:sync-opportunity --teamId=2 --from='2026-05-01 00:00:00'\nSyncing opportunity for Hubspot\nSyncing opportunities modified since 2026-05-01 00:00:00...\nSynced 6 opportunities.\nroot@docker_lamp_1:/home/jiminny# php artisan optimize:clear && supervisorctl restart all\n\n INFO Clearing cached bootstrap files. \n\n config ............................................................................................................................... 7.40ms DONE\n cache ............................................................................................................................... 35.37ms DONE\n compiled ............................................................................................................................. 2.98ms DONE\n events ............................................................................................................................... 1.70ms DONE\n routes ............................................................................................................................... 1.64ms DONE\n views ................................................................................................................................ 6.48ms DONE\n\njiminny-worker-processing-delayed:jiminny-worker-processing-delayed_00: stopped\njiminny-worker-processing-2:jiminny-worker-processing-2_00: stopped\njiminny-worker-processing-3:jiminny-worker-processing-3_00: stopped\njiminny-worker-processing-4:jiminny-worker-processing-4_00: stopped\njiminny-worker-processing-5:jiminny-worker-processing-5_00: stopped\nworker-analytics:worker-analytics_00: stopped\nworker-crm-update:worker-crm-update_00: stopped\nworker-download:worker-download_00: stopped\nworker-nudges:worker-nudges_00: stopped\nworker-crm-sync:worker-crm-sync_00: stopped\nworker:worker_00: stopped\nworker-calendar:worker-calendar_00: stopped\nworker-emails:worker-emails_00: stopped\njiminny-worker-processing-1:jiminny-worker-processing-1_00: stopped\nworker-audio:worker-audio_00: stopped\nworker-conferences:worker-conferences_00: stopped\nworker-es-update:worker-es-update_00: stopped\nartisan-schedule:artisan-schedule_00: stopped\nartisan-schedule:artisan-schedule_00: started\njiminny-worker-processing-1:jiminny-worker-processing-1_00: started\njiminny-worker-processing-2:jiminny-worker-processing-2_00: started\njiminny-worker-processing-3:jiminny-worker-processing-3_00: started\njiminny-worker-processing-4:jiminny-worker-processing-4_00: started\njiminny-worker-processing-5:jiminny-worker-processing-5_00: started\njiminny-worker-processing-delayed:jiminny-worker-processing-delayed_00: started\nworker:worker_00: started\nworker-analytics:worker-analytics_00: started\nworker-audio:worker-audio_00: started\nworker-calendar:worker-calendar_00: started\nworker-conferences:worker-conferences_00: started\nworker-crm-sync:worker-crm-sync_00: started\nworker-crm-update:worker-crm-update_00: started\nworker-download:worker-download_00: started\nworker-emails:worker-emails_00: started\nworker-es-update:worker-es-update_00: started\nworker-nudges:worker-nudges_00: started\nroot@docker_lamp_1:/home/jiminny# php artisan optimize:clear && supervisorctl restart all\n\n INFO Clearing cached bootstrap files. \n\n config ............................................................................................................................... 6.95ms DONE\n cache ................................................................................................................................ 9.00ms DONE\n compiled ............................................................................................................................. 2.63ms DONE\n events ............................................................................................................................... 2.35ms DONE\n routes ............................................................................................................................... 1.64ms DONE\n views ................................................................................................................................ 3.18ms DONE\n\njiminny-worker-processing-delayed:jiminny-worker-processing-delayed_00: stopped\nworker-download:worker-download_00: stopped\njiminny-worker-processing-2:jiminny-worker-processing-2_00: stopped\njiminny-worker-processing-3:jiminny-worker-processing-3_00: stopped\njiminny-worker-processing-4:jiminny-worker-processing-4_00: stopped\njiminny-worker-processing-5:jiminny-worker-processing-5_00: stopped\nworker-analytics:worker-analytics_00: stopped\nworker-crm-update:worker-crm-update_00: stopped\nartisan-schedule:artisan-schedule_00: stopped\nworker-nudges:worker-nudges_00: stopped\nworker:worker_00: stopped\njiminny-worker-processing-1:jiminny-worker-processing-1_00: stopped\nworker-audio:worker-audio_00: stopped\nworker-calendar:worker-calendar_00: stopped\nworker-conferences:worker-conferences_00: stopped\nworker-crm-sync:worker-crm-sync_00: stopped\nworker-emails:worker-emails_00: stopped\nworker-es-update:worker-es-update_00: stopped\nartisan-schedule:artisan-schedule_00: started\njiminny-worker-processing-1:jiminny-worker-processing-1_00: started\njiminny-worker-processing-2:jiminny-worker-processing-2_00: started\njiminny-worker-processing-3:jiminny-worker-processing-3_00: started\njiminny-worker-processing-4:jiminny-worker-processing-4_00: started\njiminny-worker-processing-5:jiminny-worker-processing-5_00: started\njiminny-worker-processing-delayed:jiminny-worker-processing-delayed_00: started\nworker:worker_00: started\nworker-analytics:worker-analytics_00: started\nworker-audio:worker-audio_00: started\nworker-calendar:worker-calendar_00: started\nworker-conferences:worker-conferences_00: started\nworker-crm-sync:worker-crm-sync_00: started\nworker-crm-update:worker-crm-update_00: started\nworker-download:worker-download_00: started\nworker-emails:worker-emails_00: started\nworker-es-update:worker-es-update_00: started\nworker-nudges:worker-nudges_00: started\nroot@docker_lamp_1:/home/jiminny# php artisan crm:sync-opportunity --teamId=2 --from='2026-05-01 00:00:00'","is_focused":true},{"role":"AXRadioButton","text":"DOCKER","depth":2,"bounds":{"left":0.0,"top":0.05888889,"width":0.16458334,"height":0.026666667},"on_screen":true,"role_description":"radio button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Close Tab","depth":3,"bounds":{"left":0.004166667,"top":0.06333333,"width":0.011111111,"height":0.017777778},"on_screen":true,"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXRadioButton","text":"DEV (docker)","depth":2,"bounds":{"left":0.16458334,"top":0.05888889,"width":0.16458334,"height":0.026666667},"on_screen":true,"role_description":"radio button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Close Tab","depth":3,"bounds":{"left":0.16875,"top":0.06333333,"width":0.011111111,"height":0.017777778},"on_screen":true,"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXRadioButton","text":"APP (-zsh)","depth":2,"bounds":{"left":0.32916668,"top":0.05888889,"width":0.16423611,"height":0.026666667},"on_screen":true,"role_description":"radio button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Close Tab","depth":3,"bounds":{"left":0.33333334,"top":0.06333333,"width":0.011111111,"height":0.017777778},"on_screen":true,"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXRadioButton","text":"-zsh","depth":2,"bounds":{"left":0.49340278,"top":0.05888889,"width":0.16423611,"height":0.026666667},"on_screen":true,"role_description":"radio button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Close Tab","depth":3,"bounds":{"left":0.49756944,"top":0.06333333,"width":0.011111111,"height":0.017777778},"on_screen":true,"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXRadioButton","text":"screenpipe\"","depth":2,"bounds":{"left":0.6576389,"top":0.05888889,"width":0.16423611,"height":0.026666667},"on_screen":true,"role_description":"radio button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Close Tab","depth":3,"bounds":{"left":0.66180557,"top":0.06333333,"width":0.011111111,"height":0.017777778},"on_screen":true,"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXRadioButton","text":"-zsh","depth":2,"bounds":{"left":0.821875,"top":0.05888889,"width":0.16423611,"height":0.026666667},"on_screen":true,"role_description":"radio button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Close Tab","depth":3,"bounds":{"left":0.82604164,"top":0.06333333,"width":0.011111111,"height":0.017777778},"on_screen":true,"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"⌥⌘1","depth":1,"bounds":{"left":0.95763886,"top":0.032222223,"width":0.03888889,"height":0.018888889},"on_screen":true,"automation_id":"_NS:8","role_description":"text"},{"role":"AXStaticText","text":"DEV (docker)","depth":1,"bounds":{"left":0.47013888,"top":0.033333335,"width":0.0625,"height":0.017777778},"on_screen":true,"role_description":"text"}]...
|
-7105059826052405930
|
3855350360759249156
|
visual_change
|
accessibility
|
NULL
|
Last login: Thu May 7 09:44:56 on ttys006
Poetry Last login: Thu May 7 09:44:56 on ttys006
Poetry could not find a pyproject.toml file in /Users/lukas/jiminny/app or its parents
Poetry could not find a pyproject.toml file in /Users/lukas/jiminny/app or its parents
lukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/jiminny/app (JY-20773-fix-automated-reports-user-pilot-tracking) $ dev
root@docker_lamp_1:/home/jiminny# php artisan crm:sync-opportunity --teamId=2 --from='2026-05-01 00:00:00'
Syncing opportunity for Hubspot
Your HubSpot account has become disconnected. Please login to Jiminny to reconnect. skipping...
root@docker_lamp_1:/home/jiminny# php artisan jiminny:token-info -A 1499 -R
----------------------------------------------------------------------------------------------------
access_token => CNeR-JHgMxIZQlNQMl8kQEwrAgwACAkUAhIJBB4BAQEDBxiCiYwCIN7Y_Qwo0qwCMhTnG549n-YtNuc1jgj-2AsLPSmw3DoyQlNQMl8kQEwrAiUACBkGawEFThwBARIBAQEEATEEAQEBAQEBAQEBAQUBEggBAQEBAYlCFPAsBNZxoDp5kAcRyeBlQoE5SM7DSgNuYTFSAFoAYABo3tj9DHAAeAA
----------------------------------------------------------------------------------------------------
access_token_expires_at => 2026-05-07 11:41:20
----------------------------------------------------------------------------------------------------
refresh_token => d5ab04e2-2109-4c0b-b513-8cba1dd54371
----------------------------------------------------------------------------------------------------
refresh_token_expires_at =>
----------------------------------------------------------------------------------------------------
root@docker_lamp_1:/home/jiminny# php artisan crm:sync-opportunity --teamId=2 --from='2026-05-01 00:00:00'
Syncing opportunity for Hubspot
Syncing opportunities modified since 2026-05-01 00:00:00...
Synced 6 opportunities.
root@docker_lamp_1:/home/jiminny# php artisan optimize:clear && supervisorctl restart all
INFO Clearing cached bootstrap files.
config [PASSWORD_DOTS] 7.40ms DONE
cache [PASSWORD_DOTS] 35.37ms DONE
compiled [PASSWORD_DOTS] 2.98ms DONE
events [PASSWORD_DOTS] 1.70ms DONE
routes [PASSWORD_DOTS] 1.64ms DONE
views [PASSWORD_DOTS] 6.48ms DONE
jiminny-worker-processing-delayed:jiminny-worker-processing-delayed_00: stopped
jiminny-worker-processing-2:jiminny-worker-processing-2_00: stopped
jiminny-worker-processing-3:jiminny-worker-processing-3_00: stopped
jiminny-worker-processing-4:jiminny-worker-processing-4_00: stopped
jiminny-worker-processing-5:jiminny-worker-processing-5_00: stopped
worker-analytics:worker-analytics_00: stopped
worker-crm-update:worker-crm-update_00: stopped
worker-download:worker-download_00: stopped
worker-nudges:worker-nudges_00: stopped
worker-crm-sync:worker-crm-sync_00: stopped
worker:worker_00: stopped
worker-calendar:worker-calendar_00: stopped
worker-emails:worker-emails_00: stopped
jiminny-worker-processing-1:jiminny-worker-processing-1_00: stopped
worker-audio:worker-audio_00: stopped
worker-conferences:worker-conferences_00: stopped
worker-es-update:worker-es-update_00: stopped
artisan-schedule:artisan-schedule_00: stopped
artisan-schedule:artisan-schedule_00: started
jiminny-worker-processing-1:jiminny-worker-processing-1_00: started
jiminny-worker-processing-2:jiminny-worker-processing-2_00: started
jiminny-worker-processing-3:jiminny-worker-processing-3_00: started
jiminny-worker-processing-4:jiminny-worker-processing-4_00: started
jiminny-worker-processing-5:jiminny-worker-processing-5_00: started
jiminny-worker-processing-delayed:jiminny-worker-processing-delayed_00: started
worker:worker_00: started
worker-analytics:worker-analytics_00: started
worker-audio:worker-audio_00: started
worker-calendar:worker-calendar_00: started
worker-conferences:worker-conferences_00: started
worker-crm-sync:worker-crm-sync_00: started
worker-crm-update:worker-crm-update_00: started
worker-download:worker-download_00: started
worker-emails:worker-emails_00: started
worker-es-update:worker-es-update_00: started
worker-nudges:worker-nudges_00: started
root@docker_lamp_1:/home/jiminny# php artisan optimize:clear && supervisorctl restart all
INFO Clearing cached bootstrap files.
config [PASSWORD_DOTS] 6.95ms DONE
cache [PASSWORD_DOTS] 9.00ms DONE
compiled [PASSWORD_DOTS] 2.63ms DONE
events [PASSWORD_DOTS] 2.35ms DONE
routes [PASSWORD_DOTS] 1.64ms DONE
views [PASSWORD_DOTS] 3.18ms DONE
jiminny-worker-processing-delayed:jiminny-worker-processing-delayed_00: stopped
worker-download:worker-download_00: stopped
jiminny-worker-processing-2:jiminny-worker-processing-2_00: stopped
jiminny-worker-processing-3:jiminny-worker-processing-3_00: stopped
jiminny-worker-processing-4:jiminny-worker-processing-4_00: stopped
jiminny-worker-processing-5:jiminny-worker-processing-5_00: stopped
worker-analytics:worker-analytics_00: stopped
worker-crm-update:worker-crm-update_00: stopped
artisan-schedule:artisan-schedule_00: stopped
worker-nudges:worker-nudges_00: stopped
worker:worker_00: stopped
jiminny-worker-processing-1:jiminny-worker-processing-1_00: stopped
worker-audio:worker-audio_00: stopped
worker-calendar:worker-calendar_00: stopped
worker-conferences:worker-conferences_00: stopped
worker-crm-sync:worker-crm-sync_00: stopped
worker-emails:worker-emails_00: stopped
worker-es-update:worker-es-update_00: stopped
artisan-schedule:artisan-schedule_00: started
jiminny-worker-processing-1:jiminny-worker-processing-1_00: started
jiminny-worker-processing-2:jiminny-worker-processing-2_00: started
jiminny-worker-processing-3:jiminny-worker-processing-3_00: started
jiminny-worker-processing-4:jiminny-worker-processing-4_00: started
jiminny-worker-processing-5:jiminny-worker-processing-5_00: started
jiminny-worker-processing-delayed:jiminny-worker-processing-delayed_00: started
worker:worker_00: started
worker-analytics:worker-analytics_00: started
worker-audio:worker-audio_00: started
worker-calendar:worker-calendar_00: started
worker-conferences:worker-conferences_00: started
worker-crm-sync:worker-crm-sync_00: started
worker-crm-update:worker-crm-update_00: started
worker-download:worker-download_00: started
worker-emails:worker-emails_00: started
worker-es-update:worker-es-update_00: started
worker-nudges:worker-nudges_00: started
root@docker_lamp_1:/home/jiminny# php artisan crm:sync-opportunity --teamId=2 --from='2026-05-01 00:00:00'
DOCKER
Close Tab
DEV (docker)
Close Tab
APP (-zsh)
Close Tab
-zsh
Close Tab
screenpipe"
Close Tab
-zsh
Close Tab
⌥⌘1
DEV (docker)...
|
2732
|
NULL
|
NULL
|
NULL
|
|
2735
|
112
|
39
|
2026-05-07T11:37:59.750502+00:00
|
/Users/lukas/.screenpipe/data/data/2026-05-07/1778 /Users/lukas/.screenpipe/data/data/2026-05-07/1778153879750_m2.jpg...
|
iTerm2
|
DEV (docker)
|
True
|
NULL
|
monitor_2
|
NULL
|
NULL
|
NULL
|
NULL
|
Last login: Thu May 7 09:44:56 on ttys006
Poetry Last login: Thu May 7 09:44:56 on ttys006
Poetry could not find a pyproject.toml file in /Users/lukas/jiminny/app or its parents
Poetry could not find a pyproject.toml file in /Users/lukas/jiminny/app or its parents
lukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/jiminny/app (JY-20773-fix-automated-reports-user-pilot-tracking) $ dev
root@docker_lamp_1:/home/jiminny# php artisan crm:sync-opportunity --teamId=2 --from='2026-05-01 00:00:00'
Syncing opportunity for Hubspot
Your HubSpot account has become disconnected. Please login to Jiminny to reconnect. skipping...
root@docker_lamp_1:/home/jiminny# php artisan jiminny:token-info -A 1499 -R
----------------------------------------------------------------------------------------------------
access_token => CNeR-JHgMxIZQlNQMl8kQEwrAgwACAkUAhIJBB4BAQEDBxiCiYwCIN7Y_Qwo0qwCMhTnG549n-YtNuc1jgj-2AsLPSmw3DoyQlNQMl8kQEwrAiUACBkGawEFThwBARIBAQEEATEEAQEBAQEBAQEBAQUBEggBAQEBAYlCFPAsBNZxoDp5kAcRyeBlQoE5SM7DSgNuYTFSAFoAYABo3tj9DHAAeAA
----------------------------------------------------------------------------------------------------
access_token_expires_at => 2026-05-07 11:41:20
----------------------------------------------------------------------------------------------------
refresh_token => d5ab04e2-2109-4c0b-b513-8cba1dd54371
----------------------------------------------------------------------------------------------------
refresh_token_expires_at =>
----------------------------------------------------------------------------------------------------
root@docker_lamp_1:/home/jiminny# php artisan crm:sync-opportunity --teamId=2 --from='2026-05-01 00:00:00'
Syncing opportunity for Hubspot
Syncing opportunities modified since 2026-05-01 00:00:00...
Synced 6 opportunities.
root@docker_lamp_1:/home/jiminny# php artisan optimize:clear && supervisorctl restart all
INFO Clearing cached bootstrap files.
config [PASSWORD_DOTS] 7.40ms DONE
cache [PASSWORD_DOTS] 35.37ms DONE
compiled [PASSWORD_DOTS] 2.98ms DONE
events [PASSWORD_DOTS] 1.70ms DONE
routes [PASSWORD_DOTS] 1.64ms DONE
views [PASSWORD_DOTS] 6.48ms DONE
jiminny-worker-processing-delayed:jiminny-worker-processing-delayed_00: stopped
jiminny-worker-processing-2:jiminny-worker-processing-2_00: stopped
jiminny-worker-processing-3:jiminny-worker-processing-3_00: stopped
jiminny-worker-processing-4:jiminny-worker-processing-4_00: stopped
jiminny-worker-processing-5:jiminny-worker-processing-5_00: stopped
worker-analytics:worker-analytics_00: stopped
worker-crm-update:worker-crm-update_00: stopped
worker-download:worker-download_00: stopped
worker-nudges:worker-nudges_00: stopped
worker-crm-sync:worker-crm-sync_00: stopped
worker:worker_00: stopped
worker-calendar:worker-calendar_00: stopped
worker-emails:worker-emails_00: stopped
jiminny-worker-processing-1:jiminny-worker-processing-1_00: stopped
worker-audio:worker-audio_00: stopped
worker-conferences:worker-conferences_00: stopped
worker-es-update:worker-es-update_00: stopped
artisan-schedule:artisan-schedule_00: stopped
artisan-schedule:artisan-schedule_00: started
jiminny-worker-processing-1:jiminny-worker-processing-1_00: started
jiminny-worker-processing-2:jiminny-worker-processing-2_00: started
jiminny-worker-processing-3:jiminny-worker-processing-3_00: started
jiminny-worker-processing-4:jiminny-worker-processing-4_00: started
jiminny-worker-processing-5:jiminny-worker-processing-5_00: started
jiminny-worker-processing-delayed:jiminny-worker-processing-delayed_00: started
worker:worker_00: started
worker-analytics:worker-analytics_00: started
worker-audio:worker-audio_00: started
worker-calendar:worker-calendar_00: started
worker-conferences:worker-conferences_00: started
worker-crm-sync:worker-crm-sync_00: started
worker-crm-update:worker-crm-update_00: started
worker-download:worker-download_00: started
worker-emails:worker-emails_00: started
worker-es-update:worker-es-update_00: started
worker-nudges:worker-nudges_00: started
root@docker_lamp_1:/home/jiminny# php artisan optimize:clear && supervisorctl restart all
INFO Clearing cached bootstrap files.
config [PASSWORD_DOTS] 6.95ms DONE
cache [PASSWORD_DOTS] 9.00ms DONE
compiled [PASSWORD_DOTS] 2.63ms DONE
events [PASSWORD_DOTS] 2.35ms DONE
routes [PASSWORD_DOTS] 1.64ms DONE
views [PASSWORD_DOTS] 3.18ms DONE
jiminny-worker-processing-delayed:jiminny-worker-processing-delayed_00: stopped
worker-download:worker-download_00: stopped
jiminny-worker-processing-2:jiminny-worker-processing-2_00: stopped
jiminny-worker-processing-3:jiminny-worker-processing-3_00: stopped
jiminny-worker-processing-4:jiminny-worker-processing-4_00: stopped
jiminny-worker-processing-5:jiminny-worker-processing-5_00: stopped
worker-analytics:worker-analytics_00: stopped
worker-crm-update:worker-crm-update_00: stopped
artisan-schedule:artisan-schedule_00: stopped
worker-nudges:worker-nudges_00: stopped
worker:worker_00: stopped
jiminny-worker-processing-1:jiminny-worker-processing-1_00: stopped
worker-audio:worker-audio_00: stopped
worker-calendar:worker-calendar_00: stopped
worker-conferences:worker-conferences_00: stopped
worker-crm-sync:worker-crm-sync_00: stopped
worker-emails:worker-emails_00: stopped
worker-es-update:worker-es-update_00: stopped
artisan-schedule:artisan-schedule_00: started
jiminny-worker-processing-1:jiminny-worker-processing-1_00: started
jiminny-worker-processing-2:jiminny-worker-processing-2_00: started
jiminny-worker-processing-3:jiminny-worker-processing-3_00: started
jiminny-worker-processing-4:jiminny-worker-processing-4_00: started
jiminny-worker-processing-5:jiminny-worker-processing-5_00: started
jiminny-worker-processing-delayed:jiminny-worker-processing-delayed_00: started
worker:worker_00: started
worker-analytics:worker-analytics_00: started
worker-audio:worker-audio_00: started
worker-calendar:worker-calendar_00: started
worker-conferences:worker-conferences_00: started
worker-crm-sync:worker-crm-sync_00: started
worker-crm-update:worker-crm-update_00: started
worker-download:worker-download_00: started
worker-emails:worker-emails_00: started
worker-es-update:worker-es-update_00: started
worker-nudges:worker-nudges_00: started
root@docker_lamp_1:/home/jiminny# php artisan crm:sync-opportunity --teamId=2 --from='2026-05-01 00:00:00'
DOCKER
Close Tab
DEV (docker)
Close Tab
APP (-zsh)
Close Tab
-zsh
Close Tab
screenpipe"
Close Tab
-zsh
Close Tab
⌥⌘1
DEV (docker)...
|
[{"role":"AXTextArea","text [{"role":"AXTextArea","text":"Last login: Thu May 7 09:44:56 on ttys006\n\nPoetry could not find a pyproject.toml file in /Users/lukas/jiminny/app or its parents\n\nPoetry could not find a pyproject.toml file in /Users/lukas/jiminny/app or its parents\nlukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/jiminny/app (JY-20773-fix-automated-reports-user-pilot-tracking) $ dev\nroot@docker_lamp_1:/home/jiminny# php artisan crm:sync-opportunity --teamId=2 --from='2026-05-01 00:00:00'\nSyncing opportunity for Hubspot\nYour HubSpot account has become disconnected. Please login to Jiminny to reconnect. skipping...\nroot@docker_lamp_1:/home/jiminny# php artisan jiminny:token-info -A 1499 -R\n----------------------------------------------------------------------------------------------------\naccess_token => CNeR-JHgMxIZQlNQMl8kQEwrAgwACAkUAhIJBB4BAQEDBxiCiYwCIN7Y_Qwo0qwCMhTnG549n-YtNuc1jgj-2AsLPSmw3DoyQlNQMl8kQEwrAiUACBkGawEFThwBARIBAQEEATEEAQEBAQEBAQEBAQUBEggBAQEBAYlCFPAsBNZxoDp5kAcRyeBlQoE5SM7DSgNuYTFSAFoAYABo3tj9DHAAeAA\n----------------------------------------------------------------------------------------------------\naccess_token_expires_at => 2026-05-07 11:41:20\n----------------------------------------------------------------------------------------------------\nrefresh_token => d5ab04e2-2109-4c0b-b513-8cba1dd54371\n----------------------------------------------------------------------------------------------------\nrefresh_token_expires_at => \n----------------------------------------------------------------------------------------------------\nroot@docker_lamp_1:/home/jiminny# php artisan crm:sync-opportunity --teamId=2 --from='2026-05-01 00:00:00'\nSyncing opportunity for Hubspot\nSyncing opportunities modified since 2026-05-01 00:00:00...\nSynced 6 opportunities.\nroot@docker_lamp_1:/home/jiminny# php artisan optimize:clear && supervisorctl restart all\n\n INFO Clearing cached bootstrap files. \n\n config ............................................................................................................................... 7.40ms DONE\n cache ............................................................................................................................... 35.37ms DONE\n compiled ............................................................................................................................. 2.98ms DONE\n events ............................................................................................................................... 1.70ms DONE\n routes ............................................................................................................................... 1.64ms DONE\n views ................................................................................................................................ 6.48ms DONE\n\njiminny-worker-processing-delayed:jiminny-worker-processing-delayed_00: stopped\njiminny-worker-processing-2:jiminny-worker-processing-2_00: stopped\njiminny-worker-processing-3:jiminny-worker-processing-3_00: stopped\njiminny-worker-processing-4:jiminny-worker-processing-4_00: stopped\njiminny-worker-processing-5:jiminny-worker-processing-5_00: stopped\nworker-analytics:worker-analytics_00: stopped\nworker-crm-update:worker-crm-update_00: stopped\nworker-download:worker-download_00: stopped\nworker-nudges:worker-nudges_00: stopped\nworker-crm-sync:worker-crm-sync_00: stopped\nworker:worker_00: stopped\nworker-calendar:worker-calendar_00: stopped\nworker-emails:worker-emails_00: stopped\njiminny-worker-processing-1:jiminny-worker-processing-1_00: stopped\nworker-audio:worker-audio_00: stopped\nworker-conferences:worker-conferences_00: stopped\nworker-es-update:worker-es-update_00: stopped\nartisan-schedule:artisan-schedule_00: stopped\nartisan-schedule:artisan-schedule_00: started\njiminny-worker-processing-1:jiminny-worker-processing-1_00: started\njiminny-worker-processing-2:jiminny-worker-processing-2_00: started\njiminny-worker-processing-3:jiminny-worker-processing-3_00: started\njiminny-worker-processing-4:jiminny-worker-processing-4_00: started\njiminny-worker-processing-5:jiminny-worker-processing-5_00: started\njiminny-worker-processing-delayed:jiminny-worker-processing-delayed_00: started\nworker:worker_00: started\nworker-analytics:worker-analytics_00: started\nworker-audio:worker-audio_00: started\nworker-calendar:worker-calendar_00: started\nworker-conferences:worker-conferences_00: started\nworker-crm-sync:worker-crm-sync_00: started\nworker-crm-update:worker-crm-update_00: started\nworker-download:worker-download_00: started\nworker-emails:worker-emails_00: started\nworker-es-update:worker-es-update_00: started\nworker-nudges:worker-nudges_00: started\nroot@docker_lamp_1:/home/jiminny# php artisan optimize:clear && supervisorctl restart all\n\n INFO Clearing cached bootstrap files. \n\n config ............................................................................................................................... 6.95ms DONE\n cache ................................................................................................................................ 9.00ms DONE\n compiled ............................................................................................................................. 2.63ms DONE\n events ............................................................................................................................... 2.35ms DONE\n routes ............................................................................................................................... 1.64ms DONE\n views ................................................................................................................................ 3.18ms DONE\n\njiminny-worker-processing-delayed:jiminny-worker-processing-delayed_00: stopped\nworker-download:worker-download_00: stopped\njiminny-worker-processing-2:jiminny-worker-processing-2_00: stopped\njiminny-worker-processing-3:jiminny-worker-processing-3_00: stopped\njiminny-worker-processing-4:jiminny-worker-processing-4_00: stopped\njiminny-worker-processing-5:jiminny-worker-processing-5_00: stopped\nworker-analytics:worker-analytics_00: stopped\nworker-crm-update:worker-crm-update_00: stopped\nartisan-schedule:artisan-schedule_00: stopped\nworker-nudges:worker-nudges_00: stopped\nworker:worker_00: stopped\njiminny-worker-processing-1:jiminny-worker-processing-1_00: stopped\nworker-audio:worker-audio_00: stopped\nworker-calendar:worker-calendar_00: stopped\nworker-conferences:worker-conferences_00: stopped\nworker-crm-sync:worker-crm-sync_00: stopped\nworker-emails:worker-emails_00: stopped\nworker-es-update:worker-es-update_00: stopped\nartisan-schedule:artisan-schedule_00: started\njiminny-worker-processing-1:jiminny-worker-processing-1_00: started\njiminny-worker-processing-2:jiminny-worker-processing-2_00: started\njiminny-worker-processing-3:jiminny-worker-processing-3_00: started\njiminny-worker-processing-4:jiminny-worker-processing-4_00: started\njiminny-worker-processing-5:jiminny-worker-processing-5_00: started\njiminny-worker-processing-delayed:jiminny-worker-processing-delayed_00: started\nworker:worker_00: started\nworker-analytics:worker-analytics_00: started\nworker-audio:worker-audio_00: started\nworker-calendar:worker-calendar_00: started\nworker-conferences:worker-conferences_00: started\nworker-crm-sync:worker-crm-sync_00: started\nworker-crm-update:worker-crm-update_00: started\nworker-download:worker-download_00: started\nworker-emails:worker-emails_00: started\nworker-es-update:worker-es-update_00: started\nworker-nudges:worker-nudges_00: started\nroot@docker_lamp_1:/home/jiminny# php artisan crm:sync-opportunity --teamId=2 --from='2026-05-01 00:00:00'","depth":4,"bounds":{"left":0.27027926,"top":0.0,"width":0.4800532,"height":1.0},"on_screen":true,"value":"Last login: Thu May 7 09:44:56 on ttys006\n\nPoetry could not find a pyproject.toml file in /Users/lukas/jiminny/app or its parents\n\nPoetry could not find a pyproject.toml file in /Users/lukas/jiminny/app or its parents\nlukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/jiminny/app (JY-20773-fix-automated-reports-user-pilot-tracking) $ dev\nroot@docker_lamp_1:/home/jiminny# php artisan crm:sync-opportunity --teamId=2 --from='2026-05-01 00:00:00'\nSyncing opportunity for Hubspot\nYour HubSpot account has become disconnected. Please login to Jiminny to reconnect. skipping...\nroot@docker_lamp_1:/home/jiminny# php artisan jiminny:token-info -A 1499 -R\n----------------------------------------------------------------------------------------------------\naccess_token => CNeR-JHgMxIZQlNQMl8kQEwrAgwACAkUAhIJBB4BAQEDBxiCiYwCIN7Y_Qwo0qwCMhTnG549n-YtNuc1jgj-2AsLPSmw3DoyQlNQMl8kQEwrAiUACBkGawEFThwBARIBAQEEATEEAQEBAQEBAQEBAQUBEggBAQEBAYlCFPAsBNZxoDp5kAcRyeBlQoE5SM7DSgNuYTFSAFoAYABo3tj9DHAAeAA\n----------------------------------------------------------------------------------------------------\naccess_token_expires_at => 2026-05-07 11:41:20\n----------------------------------------------------------------------------------------------------\nrefresh_token => d5ab04e2-2109-4c0b-b513-8cba1dd54371\n----------------------------------------------------------------------------------------------------\nrefresh_token_expires_at => \n----------------------------------------------------------------------------------------------------\nroot@docker_lamp_1:/home/jiminny# php artisan crm:sync-opportunity --teamId=2 --from='2026-05-01 00:00:00'\nSyncing opportunity for Hubspot\nSyncing opportunities modified since 2026-05-01 00:00:00...\nSynced 6 opportunities.\nroot@docker_lamp_1:/home/jiminny# php artisan optimize:clear && supervisorctl restart all\n\n INFO Clearing cached bootstrap files. \n\n config ............................................................................................................................... 7.40ms DONE\n cache ............................................................................................................................... 35.37ms DONE\n compiled ............................................................................................................................. 2.98ms DONE\n events ............................................................................................................................... 1.70ms DONE\n routes ............................................................................................................................... 1.64ms DONE\n views ................................................................................................................................ 6.48ms DONE\n\njiminny-worker-processing-delayed:jiminny-worker-processing-delayed_00: stopped\njiminny-worker-processing-2:jiminny-worker-processing-2_00: stopped\njiminny-worker-processing-3:jiminny-worker-processing-3_00: stopped\njiminny-worker-processing-4:jiminny-worker-processing-4_00: stopped\njiminny-worker-processing-5:jiminny-worker-processing-5_00: stopped\nworker-analytics:worker-analytics_00: stopped\nworker-crm-update:worker-crm-update_00: stopped\nworker-download:worker-download_00: stopped\nworker-nudges:worker-nudges_00: stopped\nworker-crm-sync:worker-crm-sync_00: stopped\nworker:worker_00: stopped\nworker-calendar:worker-calendar_00: stopped\nworker-emails:worker-emails_00: stopped\njiminny-worker-processing-1:jiminny-worker-processing-1_00: stopped\nworker-audio:worker-audio_00: stopped\nworker-conferences:worker-conferences_00: stopped\nworker-es-update:worker-es-update_00: stopped\nartisan-schedule:artisan-schedule_00: stopped\nartisan-schedule:artisan-schedule_00: started\njiminny-worker-processing-1:jiminny-worker-processing-1_00: started\njiminny-worker-processing-2:jiminny-worker-processing-2_00: started\njiminny-worker-processing-3:jiminny-worker-processing-3_00: started\njiminny-worker-processing-4:jiminny-worker-processing-4_00: started\njiminny-worker-processing-5:jiminny-worker-processing-5_00: started\njiminny-worker-processing-delayed:jiminny-worker-processing-delayed_00: started\nworker:worker_00: started\nworker-analytics:worker-analytics_00: started\nworker-audio:worker-audio_00: started\nworker-calendar:worker-calendar_00: started\nworker-conferences:worker-conferences_00: started\nworker-crm-sync:worker-crm-sync_00: started\nworker-crm-update:worker-crm-update_00: started\nworker-download:worker-download_00: started\nworker-emails:worker-emails_00: started\nworker-es-update:worker-es-update_00: started\nworker-nudges:worker-nudges_00: started\nroot@docker_lamp_1:/home/jiminny# php artisan optimize:clear && supervisorctl restart all\n\n INFO Clearing cached bootstrap files. \n\n config ............................................................................................................................... 6.95ms DONE\n cache ................................................................................................................................ 9.00ms DONE\n compiled ............................................................................................................................. 2.63ms DONE\n events ............................................................................................................................... 2.35ms DONE\n routes ............................................................................................................................... 1.64ms DONE\n views ................................................................................................................................ 3.18ms DONE\n\njiminny-worker-processing-delayed:jiminny-worker-processing-delayed_00: stopped\nworker-download:worker-download_00: stopped\njiminny-worker-processing-2:jiminny-worker-processing-2_00: stopped\njiminny-worker-processing-3:jiminny-worker-processing-3_00: stopped\njiminny-worker-processing-4:jiminny-worker-processing-4_00: stopped\njiminny-worker-processing-5:jiminny-worker-processing-5_00: stopped\nworker-analytics:worker-analytics_00: stopped\nworker-crm-update:worker-crm-update_00: stopped\nartisan-schedule:artisan-schedule_00: stopped\nworker-nudges:worker-nudges_00: stopped\nworker:worker_00: stopped\njiminny-worker-processing-1:jiminny-worker-processing-1_00: stopped\nworker-audio:worker-audio_00: stopped\nworker-calendar:worker-calendar_00: stopped\nworker-conferences:worker-conferences_00: stopped\nworker-crm-sync:worker-crm-sync_00: stopped\nworker-emails:worker-emails_00: stopped\nworker-es-update:worker-es-update_00: stopped\nartisan-schedule:artisan-schedule_00: started\njiminny-worker-processing-1:jiminny-worker-processing-1_00: started\njiminny-worker-processing-2:jiminny-worker-processing-2_00: started\njiminny-worker-processing-3:jiminny-worker-processing-3_00: started\njiminny-worker-processing-4:jiminny-worker-processing-4_00: started\njiminny-worker-processing-5:jiminny-worker-processing-5_00: started\njiminny-worker-processing-delayed:jiminny-worker-processing-delayed_00: started\nworker:worker_00: started\nworker-analytics:worker-analytics_00: started\nworker-audio:worker-audio_00: started\nworker-calendar:worker-calendar_00: started\nworker-conferences:worker-conferences_00: started\nworker-crm-sync:worker-crm-sync_00: started\nworker-crm-update:worker-crm-update_00: started\nworker-download:worker-download_00: started\nworker-emails:worker-emails_00: started\nworker-es-update:worker-es-update_00: started\nworker-nudges:worker-nudges_00: started\nroot@docker_lamp_1:/home/jiminny# php artisan crm:sync-opportunity --teamId=2 --from='2026-05-01 00:00:00'","is_focused":true},{"role":"AXRadioButton","text":"DOCKER","depth":2,"bounds":{"left":0.27027926,"top":1.0,"width":0.0787899,"height":-0.042298436},"on_screen":true,"role_description":"radio button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Close Tab","depth":3,"bounds":{"left":0.27227393,"top":1.0,"width":0.005319149,"height":-0.04549086},"on_screen":true,"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXRadioButton","text":"DEV (docker)","depth":2,"bounds":{"left":0.34906915,"top":1.0,"width":0.0787899,"height":-0.042298436},"on_screen":true,"role_description":"radio button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Close Tab","depth":3,"bounds":{"left":0.35106382,"top":1.0,"width":0.005319149,"height":-0.04549086},"on_screen":true,"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXRadioButton","text":"APP (-zsh)","depth":2,"bounds":{"left":0.42785904,"top":1.0,"width":0.07862367,"height":-0.042298436},"on_screen":true,"role_description":"radio button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Close Tab","depth":3,"bounds":{"left":0.42985374,"top":1.0,"width":0.005319149,"height":-0.04549086},"on_screen":true,"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXRadioButton","text":"-zsh","depth":2,"bounds":{"left":0.5064827,"top":1.0,"width":0.07862367,"height":-0.042298436},"on_screen":true,"role_description":"radio button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Close Tab","depth":3,"bounds":{"left":0.5084774,"top":1.0,"width":0.005319149,"height":-0.04549086},"on_screen":true,"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXRadioButton","text":"screenpipe\"","depth":2,"bounds":{"left":0.5851064,"top":1.0,"width":0.07862367,"height":-0.042298436},"on_screen":true,"role_description":"radio button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Close Tab","depth":3,"bounds":{"left":0.58710104,"top":1.0,"width":0.005319149,"height":-0.04549086},"on_screen":true,"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXRadioButton","text":"-zsh","depth":2,"bounds":{"left":0.66373,"top":1.0,"width":0.07862367,"height":-0.042298436},"on_screen":true,"role_description":"radio button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Close Tab","depth":3,"bounds":{"left":0.66572475,"top":1.0,"width":0.005319149,"height":-0.04549086},"on_screen":true,"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"⌥⌘1","depth":1,"bounds":{"left":0.7287234,"top":1.0,"width":0.01861702,"height":-0.023144484},"on_screen":true,"automation_id":"_NS:8","role_description":"text"},{"role":"AXStaticText","text":"DEV (docker)","depth":1,"bounds":{"left":0.49534574,"top":1.0,"width":0.029920213,"height":-0.02394259},"on_screen":true,"role_description":"text"}]...
|
-7105059826052405930
|
3855350360759249156
|
visual_change
|
accessibility
|
NULL
|
Last login: Thu May 7 09:44:56 on ttys006
Poetry Last login: Thu May 7 09:44:56 on ttys006
Poetry could not find a pyproject.toml file in /Users/lukas/jiminny/app or its parents
Poetry could not find a pyproject.toml file in /Users/lukas/jiminny/app or its parents
lukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/jiminny/app (JY-20773-fix-automated-reports-user-pilot-tracking) $ dev
root@docker_lamp_1:/home/jiminny# php artisan crm:sync-opportunity --teamId=2 --from='2026-05-01 00:00:00'
Syncing opportunity for Hubspot
Your HubSpot account has become disconnected. Please login to Jiminny to reconnect. skipping...
root@docker_lamp_1:/home/jiminny# php artisan jiminny:token-info -A 1499 -R
----------------------------------------------------------------------------------------------------
access_token => CNeR-JHgMxIZQlNQMl8kQEwrAgwACAkUAhIJBB4BAQEDBxiCiYwCIN7Y_Qwo0qwCMhTnG549n-YtNuc1jgj-2AsLPSmw3DoyQlNQMl8kQEwrAiUACBkGawEFThwBARIBAQEEATEEAQEBAQEBAQEBAQUBEggBAQEBAYlCFPAsBNZxoDp5kAcRyeBlQoE5SM7DSgNuYTFSAFoAYABo3tj9DHAAeAA
----------------------------------------------------------------------------------------------------
access_token_expires_at => 2026-05-07 11:41:20
----------------------------------------------------------------------------------------------------
refresh_token => d5ab04e2-2109-4c0b-b513-8cba1dd54371
----------------------------------------------------------------------------------------------------
refresh_token_expires_at =>
----------------------------------------------------------------------------------------------------
root@docker_lamp_1:/home/jiminny# php artisan crm:sync-opportunity --teamId=2 --from='2026-05-01 00:00:00'
Syncing opportunity for Hubspot
Syncing opportunities modified since 2026-05-01 00:00:00...
Synced 6 opportunities.
root@docker_lamp_1:/home/jiminny# php artisan optimize:clear && supervisorctl restart all
INFO Clearing cached bootstrap files.
config [PASSWORD_DOTS] 7.40ms DONE
cache [PASSWORD_DOTS] 35.37ms DONE
compiled [PASSWORD_DOTS] 2.98ms DONE
events [PASSWORD_DOTS] 1.70ms DONE
routes [PASSWORD_DOTS] 1.64ms DONE
views [PASSWORD_DOTS] 6.48ms DONE
jiminny-worker-processing-delayed:jiminny-worker-processing-delayed_00: stopped
jiminny-worker-processing-2:jiminny-worker-processing-2_00: stopped
jiminny-worker-processing-3:jiminny-worker-processing-3_00: stopped
jiminny-worker-processing-4:jiminny-worker-processing-4_00: stopped
jiminny-worker-processing-5:jiminny-worker-processing-5_00: stopped
worker-analytics:worker-analytics_00: stopped
worker-crm-update:worker-crm-update_00: stopped
worker-download:worker-download_00: stopped
worker-nudges:worker-nudges_00: stopped
worker-crm-sync:worker-crm-sync_00: stopped
worker:worker_00: stopped
worker-calendar:worker-calendar_00: stopped
worker-emails:worker-emails_00: stopped
jiminny-worker-processing-1:jiminny-worker-processing-1_00: stopped
worker-audio:worker-audio_00: stopped
worker-conferences:worker-conferences_00: stopped
worker-es-update:worker-es-update_00: stopped
artisan-schedule:artisan-schedule_00: stopped
artisan-schedule:artisan-schedule_00: started
jiminny-worker-processing-1:jiminny-worker-processing-1_00: started
jiminny-worker-processing-2:jiminny-worker-processing-2_00: started
jiminny-worker-processing-3:jiminny-worker-processing-3_00: started
jiminny-worker-processing-4:jiminny-worker-processing-4_00: started
jiminny-worker-processing-5:jiminny-worker-processing-5_00: started
jiminny-worker-processing-delayed:jiminny-worker-processing-delayed_00: started
worker:worker_00: started
worker-analytics:worker-analytics_00: started
worker-audio:worker-audio_00: started
worker-calendar:worker-calendar_00: started
worker-conferences:worker-conferences_00: started
worker-crm-sync:worker-crm-sync_00: started
worker-crm-update:worker-crm-update_00: started
worker-download:worker-download_00: started
worker-emails:worker-emails_00: started
worker-es-update:worker-es-update_00: started
worker-nudges:worker-nudges_00: started
root@docker_lamp_1:/home/jiminny# php artisan optimize:clear && supervisorctl restart all
INFO Clearing cached bootstrap files.
config [PASSWORD_DOTS] 6.95ms DONE
cache [PASSWORD_DOTS] 9.00ms DONE
compiled [PASSWORD_DOTS] 2.63ms DONE
events [PASSWORD_DOTS] 2.35ms DONE
routes [PASSWORD_DOTS] 1.64ms DONE
views [PASSWORD_DOTS] 3.18ms DONE
jiminny-worker-processing-delayed:jiminny-worker-processing-delayed_00: stopped
worker-download:worker-download_00: stopped
jiminny-worker-processing-2:jiminny-worker-processing-2_00: stopped
jiminny-worker-processing-3:jiminny-worker-processing-3_00: stopped
jiminny-worker-processing-4:jiminny-worker-processing-4_00: stopped
jiminny-worker-processing-5:jiminny-worker-processing-5_00: stopped
worker-analytics:worker-analytics_00: stopped
worker-crm-update:worker-crm-update_00: stopped
artisan-schedule:artisan-schedule_00: stopped
worker-nudges:worker-nudges_00: stopped
worker:worker_00: stopped
jiminny-worker-processing-1:jiminny-worker-processing-1_00: stopped
worker-audio:worker-audio_00: stopped
worker-calendar:worker-calendar_00: stopped
worker-conferences:worker-conferences_00: stopped
worker-crm-sync:worker-crm-sync_00: stopped
worker-emails:worker-emails_00: stopped
worker-es-update:worker-es-update_00: stopped
artisan-schedule:artisan-schedule_00: started
jiminny-worker-processing-1:jiminny-worker-processing-1_00: started
jiminny-worker-processing-2:jiminny-worker-processing-2_00: started
jiminny-worker-processing-3:jiminny-worker-processing-3_00: started
jiminny-worker-processing-4:jiminny-worker-processing-4_00: started
jiminny-worker-processing-5:jiminny-worker-processing-5_00: started
jiminny-worker-processing-delayed:jiminny-worker-processing-delayed_00: started
worker:worker_00: started
worker-analytics:worker-analytics_00: started
worker-audio:worker-audio_00: started
worker-calendar:worker-calendar_00: started
worker-conferences:worker-conferences_00: started
worker-crm-sync:worker-crm-sync_00: started
worker-crm-update:worker-crm-update_00: started
worker-download:worker-download_00: started
worker-emails:worker-emails_00: started
worker-es-update:worker-es-update_00: started
worker-nudges:worker-nudges_00: started
root@docker_lamp_1:/home/jiminny# php artisan crm:sync-opportunity --teamId=2 --from='2026-05-01 00:00:00'
DOCKER
Close Tab
DEV (docker)
Close Tab
APP (-zsh)
Close Tab
-zsh
Close Tab
screenpipe"
Close Tab
-zsh
Close Tab
⌥⌘1
DEV (docker)...
|
NULL
|
NULL
|
NULL
|
NULL
|
|
2738
|
111
|
40
|
2026-05-07T11:38:04.213613+00:00
|
/Users/lukas/.screenpipe/data/data/2026-05-07/1778 /Users/lukas/.screenpipe/data/data/2026-05-07/1778153884213_m1.jpg...
|
PhpStorm
|
faVsco.js – laravel.log
|
True
|
NULL
|
monitor_1
|
NULL
|
NULL
|
NULL
|
NULL
|
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
[2026-05-07 11:36:44] local.INFO: Jiminny\Console\Commands\Command::run Memory usage before starting command {"command":"crm:sync-opportunity","memoryBeforeCommandInMb":60.0,"memoryPeakBeforeCommandInMb":99.727} {"correlation_id":"462baf35-d312-4641-9896-e8b15e8fdb82","trace_id":"aeb070fe-166a-48bf-99c0-acfcbfb40c1b"}
[2026-05-07 11:36:44] local.INFO: [SocialAccountService] Fetching token {"socialAccountId":1499,"provider":"hubspot"} {"correlation_id":"462baf35-d312-4641-9896-e8b15e8fdb82","trace_id":"aeb070fe-166a-48bf-99c0-acfcbfb40c1b"}
[2026-05-07 11:36:44] local.INFO: [SocialAccountService] Token retrieved {"socialAccountId":1499,"provider":"hubspot"} {"correlation_id":"462baf35-d312-4641-9896-e8b15e8fdb82","trace_id":"aeb070fe-166a-48bf-99c0-acfcbfb40c1b"}
[2026-05-07 11:36:44] local.INFO: [EncryptedTokenManager] Generating access token. {"mode":"legacy"} {"correlation_id":"462baf35-d312-4641-9896-e8b15e8fdb82","trace_id":"aeb070fe-166a-48bf-99c0-acfcbfb40c1b"}
[2026-05-07 11:36:44] local.INFO: [HubSpot] Syncing opportunities using strategy: lastModified {"team":2} {"correlation_id":"462baf35-d312-4641-9896-e8b15e8fdb82","trace_id":"aeb070fe-166a-48bf-99c0-acfcbfb40c1b"}
[2026-05-07 11:36:45] local.INFO: [Hubspot] Pagination completed {"team_id":2,"endpoint":"[URL_WITH_CREDENTIALS] CrmFieldOption array{id:string, label:string, value?:string}
*/
class Client extends BaseClient implements HubspotClientInterface
{
public const string MIN_API_VERSION = '2';
public const string BASE_URL = '[URL_WITH_CREDENTIALS] T
* @param callable(): T $apiCall
* @return T
*
* @throws RateLimitException
*/
private function executeRequest(callable $apiCall)
{
if (! $this->rateLimiter->canMakeRequest($this->config)) {
$retryAfter = $this->rateLimiter->requestAvailableIn($this->config);
$this->log->warning('[Hubspot] Rate limit exceeded, deferring request', [
'team_id' => $this->config->team_id,
'config_id' => $this->config->getId(),
'retry_after' => $retryAfter,
]);
throw new RateLimitException(
'Hubspot rate limit reached for configuration ' . $this->config->getId(),
$retryAfter,
);
}
$this->rateLimiter->incrementRequestCount($this->config);
try {
return $apiCall();
} catch (Throwable $e) {
if ($this->isHubspotRateLimit($e)) {
$retryAfter = $this->parseRetryAfter($e);
$this->log->warning('[Hubspot] Received 429 from API', [
'team_id' => $this->config->team_id,
'config_id' => $this->config->getId(),
'retry_after' => $retryAfter,
'reason' => $e->getMessage(),
]);
throw new RateLimitException('Hubspot returned 429', $retryAfter, $e);
}
throw $e;
}
}
private function isHubspotRateLimit(Throwable $e): bool
{
return method_exists($e, 'getCode') && (int) $e->getCode() === 429;
}
private function parseRetryAfter(Throwable $e): int
{
if (method_exists($e, 'getResponseHeaders')) {
$headers = $e->getResponseHeaders() ?: [];
$value = $headers['Retry-After'] ?? $headers['retry-after'] ?? null;
if (is_array($value)) {
$value = $value[0] ?? null;
}
if (is_numeric($value)) {
return (int) $value;
}
}
return 10;
}
public function getMinimumApiVersion(): string
{
return self::MIN_API_VERSION;
}
public function getInstance(): Factory
{
return new Factory([
'key' => $this->accessToken,
'oauth2' => true,
'base_url' => $this->baseUrl,
]);
}
public function getNewInstance(): Discovery
{
return \HubSpot\Factory::createWithAccessToken($this->accessToken);
}
/**
* Secondly and daily limits for Hubspot API
*
* Product Tier: Free & Starter | Professional & Enterprise | API add-on (any tier)
* Burst: 100/10 seconds | 150/10 seconds | 200/10 seconds
* Daily: 250,000 | 500,000 | 1,000,000
*
* Official documentation states: The search endpoints are rate limited to five requests per second.
* Since with 5 RPS were still hitting secondly rate limits we lowered it to 4
*/
public function getPaginatedData(array $payload, string $type, int $offset = 0): array
{
$total = 0;
$lastId = null;
$rows = [];
foreach ($this->getPaginatedDataGenerator($payload, $type, $offset, $total, $lastId) as $row) {
$rows[] = $row;
}
return ['results' => $rows, 'total' => $total, 'last_record' => $lastId];
}
/**
* @throws HubspotException
* @throws SocialAccountTokenInvalidException
* @throws BadRequest
*/
public function getPaginatedDataGenerator(
array $payload,
string $type,
int $offset = 0,
int &$total = 0,
?string &$lastRecordId = null
): \Generator {
return $this->paginationService->getPaginatedDataGenerator(
$this,
$payload,
$type,
$offset,
$total,
$lastRecordId
);
}
/**
* @throws DealApiException
* @throws CrmException
*/
public function getOpportunityById(string $crmId, array $fields): array
{
try {
$deal = $this->executeRequest(fn () => $this->getNewInstance()->crm()->deals()->basicApi()->getById(
$crmId,
implode(',', $fields),
'companies,contacts'
));
} catch (DealApiException $e) {
$this->log->info('[Hubspot] Failed to fetch opportunity', [
'crm_id' => $crmId,
'reason' => $e->getMessage(),
]);
throw $e;
}
if (! $deal instanceof DealWithAssociations) {
throw new CrmException('Deal not found');
}
return [
'id' => $deal->getId(),
'properties' => $deal->getProperties(),
'associations' => $deal->getAssociations(),
];
}
/**
* Generic batch read method for HubSpot objects
*
* @param string $objectType The object type ('deals', 'companies', 'contacts')
* @param array<string> $crmIds Array of HubSpot object IDs (max 100)
* @param array<string> $fields Array of property names to fetch
*
* @return array<string, array> Array keyed by CRM ID with object data
*/
private function batchReadObjects(string $objectType, array $crmIds, array $fields): array
{
if (empty($crmIds)) {
return [];
}
$this->validateBatchSize($objectType, $crmIds);
$this->ensureValidToken();
try {
$batchConfig = $this->createBatchConfiguration($objectType);
$batchReadRequest = $this->prepareBatchRequest($batchConfig, $crmIds, $fields);
$response = $batchConfig['api']->read($batchReadRequest);
$this->validateApiResponse($response, $objectType);
$results = $this->processApiResults($response);
$this->logBatchResults($objectType, $crmIds, $results);
return $results;
} catch (\Throwable $e) {
$this->handleBatchError($e, $objectType, $crmIds);
}
}
private function validateBatchSize(string $objectType, array $crmIds): void
{
if (count($crmIds) > 100) {
throw new \InvalidArgumentException("Batch size cannot exceed 100 {$objectType}");
}
}
private function createBatchConfiguration(string $objectType): array
{
$configurations = [
'deals' => [
'batchReadRequest' => new \HubSpot\Client\Crm\Deals\Model\BatchReadInputSimplePublicObjectId(),
'inputClass' => \HubSpot\Client\Crm\Deals\Model\SimplePublicObjectId::class,
'api' => $this->getNewInstance()->crm()->deals()->batchApi(),
],
'companies' => [
'batchReadRequest' => new \HubSpot\Client\Crm\Companies\Model\BatchReadInputSimplePublicObjectId(),
'inputClass' => \HubSpot\Client\Crm\Companies\Model\SimplePublicObjectId::class,
'api' => $this->getNewInstance()->crm()->companies()->batchApi(),
],
'contacts' => [
'batchReadRequest' => new \HubSpot\Client\Crm\Contacts\Model\BatchReadInputSimplePublicObjectId(),
'inputClass' => \HubSpot\Client\Crm\Contacts\Model\SimplePublicObjectId::class,
'api' => $this->getNewInstance()->crm()->contacts()->batchApi(),
],
];
if (! isset($configurations[$objectType])) {
throw new \InvalidArgumentException("Unsupported object type: {$objectType}");
}
return $configurations[$objectType];
}
private function prepareBatchRequest(array $batchConfig, array $crmIds, array $fields): object
{
$batchReadRequest = $batchConfig['batchReadRequest'];
$inputClass = $batchConfig['inputClass'];
$inputs = array_map(function ($crmId) use ($inputClass) {
$input = new $inputClass();
$input->setId($crmId);
return $input;
}, $crmIds);
$batchReadRequest->setInputs($inputs);
$batchReadRequest->setProperties($fields);
return $batchReadRequest;
}
private function validateApiResponse($response, string $objectType): void
{
if (! $response) {
throw new CrmException("HubSpot API returned null response for {$objectType} batch read");
}
}
private function processApiResults($response): array
{
$results = [];
$responseResults = $response->getResults();
if ($responseResults) {
foreach ($responseResults as $object) {
if ($object && $object->getId()) {
$results[$object->getId()] = [
'id' => $object->getId(),
'properties' => $object->getProperties() ?: [],
];
}
}
}
return $results;
}
private function logBatchResults(string $objectType, array $crmIds, array $results): void
{
$this->log->info("[HubSpot] Batch fetched {$objectType}", [
'requested_count' => count($crmIds),
'returned_count' => count($results),
'crm_ids' => $crmIds,
]);
}
private function handleBatchError(\Throwable $e, string $objectType, array $crmIds): void
{
$errorMessage = $e->getMessage() ?: 'Unknown error';
$errorTrace = $e->getTraceAsString() ?: 'No trace available';
$this->log->error("[HubSpot] Failed to batch fetch {$objectType}", [
'crm_ids' => $crmIds,
'error' => $errorMessage,
'trace' => $errorTrace,
]);
throw new CrmException("Failed to batch fetch {$objectType}: " . $errorMessage);
}
/**
* Batch read multiple opportunities by their CRM IDs
*
* @param array<string> $crmIds Array of HubSpot deal IDs (max 100)
* @param array<string> $fields Array of property names to fetch
*
* @return array<string, array> Array keyed by CRM ID with opportunity data
*/
public function getOpportunitiesByIds(array $crmIds, array $fields): array
{
return $this->batchReadObjects('deals', $crmIds, $fields);
}
/**
* Batch read multiple companies by their CRM IDs
*
* @param array<string> $crmIds Array of HubSpot company IDs (max 100)
* @param array<string> $fields Array of property names to fetch
*
* @return array<string, array> Array keyed by CRM ID with company data
*/
public function getCompaniesByIds(array $crmIds, array $fields): array
{
return $this->batchReadObjects('companies', $crmIds, $fields);
}
/**
* Batch read multiple contacts by their CRM IDs
*
* @param array<string> $crmIds Array of HubSpot contact IDs (max 100)
* @param array<string> $fields Array of property names to fetch
*
* @return array<string, array> Array keyed by CRM ID with contact data
*/
public function getContactsByIds(array $crmIds, array $fields): array
{
return $this->batchReadObjects('contacts', $crmIds, $fields);
}
/**
* @throws CompanyApiException
* @throws CrmException
*/
public function getAccountById(string $crmId, array $fields): array
{
try {
$company = $this->getNewInstance()->crm()->companies()->basicApi()->getById(
$crmId,
implode(',', $fields),
);
} catch (CompanyApiException $e) {
$this->log->info('[Hubspot] Failed to fetch account', [
'crm_id' => $crmId,
'reason' => $e->getMessage(),
]);
throw $e;
}
if (! $company instanceof CompaniesWithAssociations) {
throw new CrmException('Account not found');
}
return [
'id' => $company->getId(),
'properties' => $company->getProperties(),
];
}
/**
* @throws ContactApiException
* @throws CrmException
*/
public function getContactById(string $crmId, array $fields): array
{
try {
$contact = $this->getNewInstance()->crm()->contacts()->basicApi()->getById(
$crmId,
implode(',', $fields)
);
} catch (ContactApiException $e) {
$this->log->info('[Hubspot] Failed to fetch contact', [
'crm_id' => $crmId,
'reason' => $e->getMessage(),
]);
throw $e;
}
if (! $contact instanceof ContactsWithAssociations) {
throw new CrmException('Contact not found');
}
return [
'id' => $contact->getId(),
'properties' => $contact->getProperties(),
];
}
/**
* This is email search request that Hubspot offers as GET (more generous quota)
*/
public function getContactByEmail(string $email, array $fields = []): array
{
try {
$contact = $this->getNewInstance()->crm()->contacts()->basicApi()->getById(
$email,
implode(',', $fields),
null,
false,
'email'
);
return [
'id' => $contact->getId(),
'properties' => $contact->getProperties(),
];
} catch (ContactApiException $e) {
$this->log->info('[Hubspot] Failed to fetch contact', [
'email' => $email,
'reason' => $e->getMessage(),
]);
return [];
}
}
/**
* @throws CrmException
*/
public function fetchProperty(string $objectType, string $propertyId): Property
{
$result = $this->getNewInstance()->crm()->properties()->coreApi()->getByName($objectType, $propertyId);
if (! $result instanceof Property) {
$this->log->error('[Hubspot] Failed to fetch property', [
'object_type' => $objectType,
'property_id' => $propertyId,
'reason' => $result->getMessage(),
]);
throw new CrmException('Failed to fetch property');
}
return $result;
}
/**
* @return array<CrmFieldOption>
*/
public function fetchPropertyOptions(string $objectType, string $propertyId): array
{
/** @var array<CrmFieldOption> */
return $this->fetchProperty($objectType, $propertyId)->getOptions();
}
/**
* @return array<array{id:string, label:string, deleted:bool}>
*/
public function fetchCallDispositions(): array
{
/** @var Response $response */
$response = $this->getInstance()->engagements()->getCallDispositions();
/**
* @var array<array{
* id:string,
* label:string,
* deleted: bool
* }>
*/
return $response->toArray();
}
/**
* @return array<CrmFieldOption>
*/
public function fetchOpportunityPipelineStages(): array
{
$stages = [];
$apiResponse = $this->getNewInstance()->crm()->pipelines()->pipelinesApi()->getAll('deals');
if ($apiResponse instanceof Error) {
$this->log->error('[Hubspot] Failed to fetch opportunity pipelines', [
'reason' => $apiResponse->getMessage(),
]);
return [];
}
foreach ($apiResponse->getResults() as $pipeline) {
$pipelineStages = array_map(
static function (PipelineStage $stage) {
return [
'id' => $stage->getId(),
'label' => $stage->getLabel(),
];
},
$pipeline->getStages()
);
$stages = array_merge($stages, $pipelineStages);
}
return $stages;
}
public function fetchOpportunityPipelines(): array
{
$pipelines = [];
try {
$apiResponse = $this->makeRequest('/crm/v3/pipelines/deals');
} catch (\Exception $e) {
$this->log->info('[Hubspot] Failed to fetch opportunity pipelines', [
'reason' => $e->getMessage(),
]);
return [];
}
$response = $apiResponse->toArray();
foreach ($response['results'] as $pipeline) {
$pipelines[] = [
'id' => $pipeline['id'],
'label' => $pipeline['label'],
];
}
return $pipelines;
}
/**
* @return array<CrmFieldOption>
*/
public function fetchMeetingOutcomeFieldOptions(Field $field): array
{
return $field->getCrmProviderId() === 'meetingOutcome'
? $this->fetchMeetingOutcomeTypes()
: $this->fetchCallActivityTypes();
}
public function fetchMeetingOutcomeTypes(): array
{
return $this->extractMeetingTypeOptions(
'[URL_WITH_CREDENTIALS] Response $response */
$response = $this->getInstance()
->getClient()
->request('GET', $endpoint);
/**
* @var array<array{
* value: string,
* label: string,
* displayOrder: int
* }> $optionData
*/
$optionData = $response->toArray()['options'] ?? [];
$options = [];
foreach ($optionData as $item) {
$options[] = [
'id' => $item['value'],
'value' => $item['value'],
'label' => $item['label'],
'display_order' => $item['displayOrder'],
];
}
return $options;
}
/**
* @return array<CrmFieldOption>
*/
public function fetchDispositionFieldOptions(): array
{
$options = [];
$dispositions = $this->fetchCallDispositions();
foreach ($dispositions as $disposition) {
if ($disposition['deleted'] !== false) {
continue;
}
$option['value'] = $disposition['id'];
$option['id'] = $disposition['id'];
$option['label'] = $disposition['label'];
$options[] = $option;
}
return $options;
}
/**
* @return array<CrmFieldOption>
*/
public function fetchOpportunityFieldOptions(Field $field): array
{
if ($field->isStageField()) {
return $this->fetchOpportunityPipelineStages();
}
if ($field->isPipelineField()) {
return $this->fetchOpportunityPipelines();
}
return $this->fetchPropertyOptions('deals', $field->getCrmProviderId());
}
/**
* @throws BadRequest
* @throws HubspotException
*/
public function makeRequest(string $endpoint, $method = 'GET', $payload = [], ?string $queryString = null)
{
$endpoint = self::BASE_URL . $endpoint;
if ($method === 'GET') {
$response = $this->getInstance()->getClient()?->request(
method: $method,
endpoint: $endpoint,
query_string: $queryString
);
} else {
$response = $this->getInstance()->getClient()->request($method, $endpoint, [
'json' => ($payload),
]);
}
$max = $response->getHeaderLine('X-HubSpot-RateLimit-Max'); // "110"
$remaining = $response->getHeaderLine('X-HubSpot-RateLimit-Remaining'); // "109"
$interval = $response->getHeaderLine('X-HubSpot-RateLimit-Interval-Milliseconds'); // "10000"
$body = json_decode((string) $response->getBody(), true);
\Illuminate\Support\Facades\Log::channel('custom_channel')->info('$max ' . PHP_EOL . print_r($max, true));
\Illuminate\Support\Facades\Log::channel('custom_channel')->info('$remaining ' . PHP_EOL . print_r($remaining, true));
\Illuminate\Support\Facades\Log::channel('custom_channel')->info('$interval ' . PHP_EOL . print_r($interval, true));
\Illuminate\Support\Facades\Log::channel('custom_channel')->info('$body ' . PHP_EOL . print_r($body, true));
return $response;
}
/**
* @throws BadRequest
* @throws HubspotException
*/
public function createMeeting(array $payload): Response
{
$endpoint = '/crm/v3/objects/meetings';
return $this->makeRequest($endpoint, 'POST', $payload);
}
/**
* @throws BadRequest
* @throws HubspotException
*/
public function updateMeeting(string $meetingId, array $payload): Response
{
$endpoint = '/crm/v3/objects/meetings/' . $meetingId;
return $this->makeRequest($endpoint, 'PATCH', $payload);
}
/**
* @throws \Exception
*/
public function createNote(
string $body,
string $ownerId,
int $timestamp,
string $objectId,
NoteObject $noteObject
): ?string {
try {
$noteInput = new SimplePublicObjectInput([
'properties' => [
'hs_note_body' => $body,
'hubspot_owner_id' => $ownerId,
'hs_timestamp' => $timestamp,
],
]);
// Create note
$note = $this->getNewInstance()->crm()->objects()->basicApi()->create('note', $noteInput);
$this->getNewInstance()->crm()->objects()->associationsApi()->create(
'note',
$note->getId(),
$this->getNoteObject($noteObject),
$objectId,
$this->getNoteAssociationType($noteObject),
);
return $note->getId();
} catch (\Exception $e) {
$this->log->error('[Hubspot] Failed to create note', [
'objectId' => $objectId,
'noteObject' => $noteObject->getObjectType(),
'reason' => $e->getMessage(),
]);
\Sentry::captureException($e);
}
return null;
}
public function updateEngagement(string $objectId, array $engagement, array $metadata): void
{
$this->getInstance()->engagements()->update($objectId, $engagement, $metadata);
}
public function getEngagementData(string $engagementId): array
{
$engagement = $this->getInstance()->engagements()->get($engagementId);
return $engagement->toArray();
}
public function createEngagement(array $engagement, array $associations, array $metadata): Response
{
return $this->getInstance()
->engagements()
->create($engagement, $associations, $metadata);
}
public function isUnauthorizedException(\Exception $e): bool
{
// Check for specific HubSpot API exception types first
if ($e instanceof BadRequest) {
// BadRequest can contain 401 status codes
return $e->getCode() === 401;
}
// Check for HTTP client exceptions with status codes
if ($e instanceof \GuzzleHttp\Exception\RequestException && $e->hasResponse()) {
$response = $e->getResponse();
if ($response !== null) {
return $response->getStatusCode() === 401;
}
}
// Check for Guzzle HTTP exceptions
if ($e instanceof \GuzzleHttp\Exception\ClientException) {
return $e->getCode() === 401;
}
// Fallback to string matching as last resort, but be more specific
$message = strtolower($e->getMessage());
return str_contains($message, '401 unauthorized') ||
str_contains($message, 'http 401') ||
str_contains($message, 'status code 401') ||
(preg_match('/\b401\b/', $message) && str_contains($message, 'unauthorized'));
}
/**
* Validates and refreshes the access token if needed before API requests.
* This ensures long-running processes don't fail due to token expiration.
*
* @throws SocialAccountTokenInvalidException
*/
public function ensureValidToken(): void
{
if ($this->oauthAccount === null) {
return;
}
$newToken = $this->tokenManager->ensureValidToken($this->oauthAccount);
if ($newToken !== null) {
$this->accessToken = $newToken;
}
}
public function getConfig()
{
return $this->config;
}
// returns only active (archived=false)
public function getOwners(): array
{
return $this->getNewInstance()->crm()->owners()->getAll();
}
/**
* @param bool $archived
*
* @return array<Owner>|[]
*/
public function getOwnersArchived(bool $archived = true): array
{
$endpoint = '/crm/v3/owners';
$queryParams = [
'archived' => $archived ? 'true' : 'false',
];
$queryString = http_build_query($queryParams);
$owners = [];
try {
$response = $this->makeRequest(endpoint: $endpoint, queryString: $queryString);
$responseData = $response?->toArray();
foreach ($responseData['results'] as $result) {
try {
$owners[] = Owner::create($result);
} catch (Throwable $e) {
$this->log->error('[HubSpot] Failed to process owner data', [
'result' => $result,
'error' => $e->getMessage(),
]);
continue;
}
}
} catch (Throwable $e) {
$this->log->error('HubSpot] Failed to fetch owners', [
'archived' => $archived,
'error' => $e->getMessage(),
]);
return [];
}
return $owners;
}
public function getMeeting(string $engagementId): ObjectWithAssociations
{
return $this->getNewInstance()->crm()->objects()->basicApi()
->getById('meeting', $engagementId, null, 'contact,company,deal');
}
public function deleteEngagement(string $engagementId): void
{
$this->getInstance()->engagements()->delete((int) $engagementId);
}
public function getAssociationsData(array $ids, string $fromObject, string $toObject): array
{
$associationData = [];
$idChunks = array_chunk($ids, self::ASSOCIATIONS_BATCH_SIZE_LIMIT);
foreach ($idChunks as $idChunk) {
try {
$batchInput = new \HubSpot\Client\Crm\Associations\Model\BatchInputPublicObjectId();
$batchInput->setInputs(array_map(function ($id) {
$publicObjectId = new \HubSpot\Client\Crm\Associations\Model\PublicObjectId();
$publicObjectId->setId($id);
return $publicObjectId;
}, $idChunk));
$associatedObjectsData = $this
->getNewInstance()
->crm()
->associations()
->batchApi()
->read($fromObject, $toObject, $batchInput);
if ($associatedObjectsData instanceof \HubSpot\Client\Crm\Associations\Model\BatchResponsePublicAssociationMulti) {
foreach ($associatedObjectsData->getResults() as $association) {
$from = $association->getFrom()->getId();
$toAssociations = $association->getTo();
if (! empty($toAssociations)) {
$associationData[$from] = array_map(function ($item) {
return $item->getId();
}, $toAssociations);
}
}
}
} catch (\Exception $e) {
$this->log->error('[Hubspot] Failed to fetch associations', [
'from_object' => $fromObject,
'to_object' => $toObject,
'reason' => $e->getMessage(),
]);
}
}
return $associationData;
}
/**
* @throws \Exception
*/
private function getNoteAssociationType(NoteObject $noteObject): string
{
return match($noteObject) {
NoteObject::Opportunity => 'note_to_deal',
NoteObject::Lead, NoteObject::Contact => 'note_to_contact', // or 'note_to_lead' if your portal supports it
NoteObject::Account => 'note_to_company',
NoteObject::Call, NoteObject::Event => throw new \Exception('Not supported'),
};
}
/**
* @throws \Exception
*/
private function getNoteObject(NoteObject $noteObject): string
{
return match($noteObject) {
NoteObject::Opportunity => 'deal',
NoteObject::Lead, NoteObject::Contact => 'contact',
NoteObject::Account => 'company',
NoteObject::Call, NoteObject::Event => throw new \Exception('Not supported'),
};
}
public function addAssociations(string $objectType, string $associationType, array $payload): Response
{
$endpoint = "/crm/v4/associations/$objectType/$associationType/batch/create";
return $this->makeRequest($endpoint, 'POST', $payload);
}
public function removeAssociations(string $objectType, string $associationType, array $payload): Response
{
$endpoint = "/crm/v4/associations/$objectType/$associationType/batch/archive";
return $this->makeRequest($endpoint, 'POST', $payload);
}
}
Project
Project
New File or Directory…
Expand Selected
Collapse All
Options
Hide...
|
[{"role":"AXButton","text" [{"role":"AXButton","text":"Project: faVsco.js, menu","depth":5,"on_screen":true,"help_text":"~/jiminny/app","role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"master, menu","depth":5,"on_screen":true,"help_text":"Git Branch: master","role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Start Listening for PHP Debug Connections","depth":5,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"AskJiminnyReportActivityServiceTest","depth":6,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Run 'AskJiminnyReportActivityServiceTest'","depth":6,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Debug 'AskJiminnyReportActivityServiceTest'","depth":6,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"More Actions","depth":6,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"JetBrains AI","depth":5,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Search Everywhere","depth":5,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"IDE and Project Settings","depth":5,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXTextArea","text":"[2026-05-07 11:36:44] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage before starting command {\"command\":\"crm:sync-opportunity\",\"memoryBeforeCommandInMb\":60.0,\"memoryPeakBeforeCommandInMb\":99.727} {\"correlation_id\":\"462baf35-d312-4641-9896-e8b15e8fdb82\",\"trace_id\":\"aeb070fe-166a-48bf-99c0-acfcbfb40c1b\"}\n[2026-05-07 11:36:44] local.INFO: [SocialAccountService] Fetching token {\"socialAccountId\":1499,\"provider\":\"hubspot\"} {\"correlation_id\":\"462baf35-d312-4641-9896-e8b15e8fdb82\",\"trace_id\":\"aeb070fe-166a-48bf-99c0-acfcbfb40c1b\"}\n[2026-05-07 11:36:44] local.INFO: [SocialAccountService] Token retrieved {\"socialAccountId\":1499,\"provider\":\"hubspot\"} {\"correlation_id\":\"462baf35-d312-4641-9896-e8b15e8fdb82\",\"trace_id\":\"aeb070fe-166a-48bf-99c0-acfcbfb40c1b\"}\n[2026-05-07 11:36:44] local.INFO: [EncryptedTokenManager] Generating access token. {\"mode\":\"legacy\"} {\"correlation_id\":\"462baf35-d312-4641-9896-e8b15e8fdb82\",\"trace_id\":\"aeb070fe-166a-48bf-99c0-acfcbfb40c1b\"}\n[2026-05-07 11:36:44] local.INFO: [HubSpot] Syncing opportunities using strategy: lastModified {\"team\":2} {\"correlation_id\":\"462baf35-d312-4641-9896-e8b15e8fdb82\",\"trace_id\":\"aeb070fe-166a-48bf-99c0-acfcbfb40c1b\"}\n[2026-05-07 11:36:45] local.INFO: [Hubspot] Pagination completed {\"team_id\":2,\"endpoint\":\"https://api.hubapi.com/crm/v3/objects/deals/search\",\"total_requests\":1,\"total_records_fetched\":6,\"total_elapsed_seconds\":0.48,\"average_seconds_per_request\":0.48} {\"correlation_id\":\"462baf35-d312-4641-9896-e8b15e8fdb82\",\"trace_id\":\"aeb070fe-166a-48bf-99c0-acfcbfb40c1b\"}\n[2026-05-07 11:36:46] local.INFO: [HubSpot] importOpportunityBatch timing {\"teamId\":2,\"deal_count\":6,\"total_ms\":659,\"company_assoc_api_ms\":276,\"contact_assoc_api_ms\":203,\"prepare_entities_ms\":19,\"prepare_accounts_ms\":3,\"prepare_contacts_ms\":17,\"total_companies\":5,\"missing_companies\":0,\"total_contacts\":23,\"missing_contacts\":0,\"deals_loop_ms\":152,\"avg_deal_ms\":25,\"slow_deals_count\":0,\"slow_deals\":[]} {\"correlation_id\":\"462baf35-d312-4641-9896-e8b15e8fdb82\",\"trace_id\":\"aeb070fe-166a-48bf-99c0-acfcbfb40c1b\"}\n[2026-05-07 11:36:46] local.INFO: [HubSpot] Synced opportunities {\"team\":2,\"strategies\":\"lastModified\",\"sync_count\":5,\"total\":6,\"last_synced_id\":\"297846423\",\"duration_ms\":1161.39} {\"correlation_id\":\"462baf35-d312-4641-9896-e8b15e8fdb82\",\"trace_id\":\"aeb070fe-166a-48bf-99c0-acfcbfb40c1b\"}\n[2026-05-07 11:36:46] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage for command {\"command\":\"crm:sync-opportunity\",\"memoryBeforeCommandInMb\":60.0,\"memoryAfterCommandInMB\":62.0,\"memoryPeakBeforeCommandInMb\":99.727,\"memoryPeakAfterCommandInMB\":99.727} {\"correlation_id\":\"462baf35-d312-4641-9896-e8b15e8fdb82\",\"trace_id\":\"aeb070fe-166a-48bf-99c0-acfcbfb40c1b\"}\n[2026-05-07 11:36:52] local.INFO: [HubSpot Journal Polling] Getting offset from database {\"offset\":\"\",\"jiminny_team_id\":1} {\"correlation_id\":\"8568abe7-be33-417c-aa2f-b1bd53fb4adc\",\"trace_id\":\"582bff07-5ea4-4469-8ca8-efa41b04d46d\"}\n[2026-05-07 11:36:52] local.INFO: [HubSpot Journal API] Fetching latest journal entry {\"url\":\"https://api.hubapi.com/webhooks/v4/journal/latest\"} {\"correlation_id\":\"8568abe7-be33-417c-aa2f-b1bd53fb4adc\",\"trace_id\":\"582bff07-5ea4-4469-8ca8-efa41b04d46d\"}\n[2026-05-07 11:36:52] local.INFO: [HubSpot Journal Polling] No data {\"correlation_id\":\"8568abe7-be33-417c-aa2f-b1bd53fb4adc\",\"trace_id\":\"582bff07-5ea4-4469-8ca8-efa41b04d46d\"}\n[2026-05-07 11:36:52] local.WARNING: [HubSpot Journal Polling] Maximum empty results reached, stopping {\"empty_results\":5,\"max_empty_results\":5} {\"correlation_id\":\"8568abe7-be33-417c-aa2f-b1bd53fb4adc\",\"trace_id\":\"582bff07-5ea4-4469-8ca8-efa41b04d46d\"}\n[2026-05-07 11:36:52] local.WARNING: [HubSpot Journal Polling] Maximum empty results reached, stopping {\"empty_results\":5,\"max_empty_results\":5} {\"correlation_id\":\"8568abe7-be33-417c-aa2f-b1bd53fb4adc\",\"trace_id\":\"582bff07-5ea4-4469-8ca8-efa41b04d46d\"}\n[2026-05-07 11:36:52] local.INFO: [HubSpot Journal Polling] Service ending {\"runtime_seconds\":56,\"total_cycles\":5,\"files_downloaded\":0,\"empty_files\":0,\"other_portal_skipped\":0,\"total_events\":0,\"events_per_file\":0,\"avg_api_ms\":176.5,\"avg_download_ms\":0.0,\"avg_transform_ms\":0.0,\"avg_process_ms\":0.0,\"peak_memory_mb\":99.73} {\"correlation_id\":\"8568abe7-be33-417c-aa2f-b1bd53fb4adc\",\"trace_id\":\"582bff07-5ea4-4469-8ca8-efa41b04d46d\"}\n[2026-05-07 11:36:52] local.INFO: [HubSpot Journal Polling] Released polling lock {\"correlation_id\":\"8568abe7-be33-417c-aa2f-b1bd53fb4adc\",\"trace_id\":\"582bff07-5ea4-4469-8ca8-efa41b04d46d\"}\n[2026-05-07 11:37:05] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage before starting command {\"command\":\"meeting-bot:schedule-bot\",\"memoryBeforeCommandInMb\":60.0,\"memoryPeakBeforeCommandInMb\":99.727} {\"correlation_id\":\"34ae70b9-ee92-4e0c-bb97-3ecc204278be\",\"trace_id\":\"0d02e69d-5e33-4059-938c-d41350a39b72\"}\n[2026-05-07 11:37:05] local.INFO: [ScheduleBotCommand] Number of activities to be captured: 0 {\"correlation_id\":\"34ae70b9-ee92-4e0c-bb97-3ecc204278be\",\"trace_id\":\"0d02e69d-5e33-4059-938c-d41350a39b72\"}\n[2026-05-07 11:37:05] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage for command {\"command\":\"meeting-bot:schedule-bot\",\"memoryBeforeCommandInMb\":60.0,\"memoryAfterCommandInMB\":62.0,\"memoryPeakBeforeCommandInMb\":99.727,\"memoryPeakAfterCommandInMB\":99.727} {\"correlation_id\":\"34ae70b9-ee92-4e0c-bb97-3ecc204278be\",\"trace_id\":\"0d02e69d-5e33-4059-938c-d41350a39b72\"}\n[2026-05-07 11:37:06] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage before starting command {\"command\":\"dialers:monitor-activities\",\"memoryBeforeCommandInMb\":62.0,\"memoryPeakBeforeCommandInMb\":99.727} {\"correlation_id\":\"6052f7ad-dde1-41de-8051-5ba8d54babdd\",\"trace_id\":\"2e216848-ddf9-4f8c-8808-93a09cef8f5f\"}\n[2026-05-07 11:37:06] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage for command {\"command\":\"dialers:monitor-activities\",\"memoryBeforeCommandInMb\":62.0,\"memoryAfterCommandInMB\":62.0,\"memoryPeakBeforeCommandInMb\":99.727,\"memoryPeakAfterCommandInMB\":99.727} {\"correlation_id\":\"6052f7ad-dde1-41de-8051-5ba8d54babdd\",\"trace_id\":\"2e216848-ddf9-4f8c-8808-93a09cef8f5f\"}\n[2026-05-07 11:37:07] local.NOTICE: Monitoring start {\"correlation_id\":\"64decce4-a339-4c1b-8a73-4f612ef2eea2\",\"trace_id\":\"07e59e6c-d681-41f9-ba04-5cab00e9a754\"}\n[2026-05-07 11:37:07] local.NOTICE: Monitoring end {\"correlation_id\":\"64decce4-a339-4c1b-8a73-4f612ef2eea2\",\"trace_id\":\"07e59e6c-d681-41f9-ba04-5cab00e9a754\"}\n[2026-05-07 11:37:09] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage before starting command {\"command\":\"mailbox:skip-lists:refresh\",\"memoryBeforeCommandInMb\":62.0,\"memoryPeakBeforeCommandInMb\":99.727} {\"correlation_id\":\"0326b1c8-69ac-4d09-aae5-e808abc71f46\",\"trace_id\":\"2d16668b-26b0-44cf-a533-493500255667\"}\n[2026-05-07 11:37:09] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage for command {\"command\":\"mailbox:skip-lists:refresh\",\"memoryBeforeCommandInMb\":62.0,\"memoryAfterCommandInMB\":62.0,\"memoryPeakBeforeCommandInMb\":99.727,\"memoryPeakAfterCommandInMB\":99.727} {\"correlation_id\":\"0326b1c8-69ac-4d09-aae5-e808abc71f46\",\"trace_id\":\"2d16668b-26b0-44cf-a533-493500255667\"}\n[2026-05-07 11:37:10] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage before starting command {\"command\":\"mailbox:batch:process\",\"memoryBeforeCommandInMb\":62.0,\"memoryPeakBeforeCommandInMb\":99.727} {\"correlation_id\":\"aa512302-070c-4f99-b3ef-1a43c71b6297\",\"trace_id\":\"9e5c4036-6a42-4c64-a9b3-f11143254293\"}\n[2026-05-07 11:37:10] local.INFO: [EmailSchedule] STARTING batch process {\"host\":\"docker_lamp_1\"} {\"correlation_id\":\"aa512302-070c-4f99-b3ef-1a43c71b6297\",\"trace_id\":\"9e5c4036-6a42-4c64-a9b3-f11143254293\"}\n[2026-05-07 11:37:10] local.INFO: [EmailSchedule] FINISHED batch process {\"host\":\"docker_lamp_1\",\"processed\":0} {\"correlation_id\":\"aa512302-070c-4f99-b3ef-1a43c71b6297\",\"trace_id\":\"9e5c4036-6a42-4c64-a9b3-f11143254293\"}\n[2026-05-07 11:37:10] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage for command {\"command\":\"mailbox:batch:process\",\"memoryBeforeCommandInMb\":62.0,\"memoryAfterCommandInMB\":62.0,\"memoryPeakBeforeCommandInMb\":99.727,\"memoryPeakAfterCommandInMB\":99.727} {\"correlation_id\":\"aa512302-070c-4f99-b3ef-1a43c71b6297\",\"trace_id\":\"9e5c4036-6a42-4c64-a9b3-f11143254293\"}\n[2026-05-07 11:37:13] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage before starting command {\"command\":\"mailbox:batch:create\",\"memoryBeforeCommandInMb\":62.0,\"memoryPeakBeforeCommandInMb\":99.727} {\"correlation_id\":\"b3d4318e-3db2-413c-8fe1-8827ec834020\",\"trace_id\":\"870e454c-3a47-419e-88f1-2321d38b8937\"}\n[2026-05-07 11:37:13] local.INFO: [EmailSchedule] STARTING batch create {\"host\":\"docker_lamp_1\"} {\"correlation_id\":\"b3d4318e-3db2-413c-8fe1-8827ec834020\",\"trace_id\":\"870e454c-3a47-419e-88f1-2321d38b8937\"}\n[2026-05-07 11:37:13] local.INFO: [EmailSchedule] FINISHED batch create {\"host\":\"docker_lamp_1\"} {\"correlation_id\":\"b3d4318e-3db2-413c-8fe1-8827ec834020\",\"trace_id\":\"870e454c-3a47-419e-88f1-2321d38b8937\"}\n[2026-05-07 11:37:13] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage for command {\"command\":\"mailbox:batch:create\",\"memoryBeforeCommandInMb\":62.0,\"memoryAfterCommandInMB\":62.0,\"memoryPeakBeforeCommandInMb\":99.727,\"memoryPeakAfterCommandInMB\":99.727} {\"correlation_id\":\"b3d4318e-3db2-413c-8fe1-8827ec834020\",\"trace_id\":\"870e454c-3a47-419e-88f1-2321d38b8937\"}\n[2026-05-07 11:37:14] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage before starting command {\"command\":\"activity:sync\",\"memoryBeforeCommandInMb\":62.0,\"memoryPeakBeforeCommandInMb\":99.727} {\"correlation_id\":\"3e5f2a44-295d-4c28-b5e9-1a649c1f49ae\",\"trace_id\":\"f4d85ec2-b1d2-4418-bb65-814c209e628f\"}\n[2026-05-07 11:37:15] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage for command {\"command\":\"activity:sync\",\"memoryBeforeCommandInMb\":62.0,\"memoryAfterCommandInMB\":62.0,\"memoryPeakBeforeCommandInMb\":99.727,\"memoryPeakAfterCommandInMB\":99.727} {\"correlation_id\":\"3e5f2a44-295d-4c28-b5e9-1a649c1f49ae\",\"trace_id\":\"f4d85ec2-b1d2-4418-bb65-814c209e628f\"}\n[2026-05-07 11:37:21] local.INFO: [Commands/AsyncUpdateEsEntities] Starting ES update worker {\"pid\":20519,\"workerId\":\"\",\"target\":\"activities\"} {\"correlation_id\":\"1a81e470-75ac-4728-9d45-9f879fc96c5b\",\"trace_id\":\"7184ea88-9f01-42bc-9508-8e4ad0c1204b\"}\n[2026-05-07 11:37:22] local.INFO: [Jiminny\\Jobs\\Mailbox\\CreateBatches] processed 2 inboxes and created 0 batches {\"userId\":null,\"batchSize\":30,\"maxBatches\":1000} {\"correlation_id\":\"798a3e91-233c-4b61-aa8e-80a7b84ac45f\",\"trace_id\":\"870e454c-3a47-419e-88f1-2321d38b8937\"}\n[2026-05-07 11:37:53] local.INFO: [Commands/AsyncUpdateEsEntities] Starting ES update worker {\"pid\":20661,\"workerId\":\"\",\"target\":\"activities\"} {\"correlation_id\":\"293fa4a8-dc12-452e-88a3-f7408f8ed676\",\"trace_id\":\"e3d77ea8-2ff3-4b4e-a388-8f21192e51f3\"}","depth":4,"on_screen":true,"value":"[2026-05-07 11:36:44] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage before starting command {\"command\":\"crm:sync-opportunity\",\"memoryBeforeCommandInMb\":60.0,\"memoryPeakBeforeCommandInMb\":99.727} {\"correlation_id\":\"462baf35-d312-4641-9896-e8b15e8fdb82\",\"trace_id\":\"aeb070fe-166a-48bf-99c0-acfcbfb40c1b\"}\n[2026-05-07 11:36:44] local.INFO: [SocialAccountService] Fetching token {\"socialAccountId\":1499,\"provider\":\"hubspot\"} {\"correlation_id\":\"462baf35-d312-4641-9896-e8b15e8fdb82\",\"trace_id\":\"aeb070fe-166a-48bf-99c0-acfcbfb40c1b\"}\n[2026-05-07 11:36:44] local.INFO: [SocialAccountService] Token retrieved {\"socialAccountId\":1499,\"provider\":\"hubspot\"} {\"correlation_id\":\"462baf35-d312-4641-9896-e8b15e8fdb82\",\"trace_id\":\"aeb070fe-166a-48bf-99c0-acfcbfb40c1b\"}\n[2026-05-07 11:36:44] local.INFO: [EncryptedTokenManager] Generating access token. {\"mode\":\"legacy\"} {\"correlation_id\":\"462baf35-d312-4641-9896-e8b15e8fdb82\",\"trace_id\":\"aeb070fe-166a-48bf-99c0-acfcbfb40c1b\"}\n[2026-05-07 11:36:44] local.INFO: [HubSpot] Syncing opportunities using strategy: lastModified {\"team\":2} {\"correlation_id\":\"462baf35-d312-4641-9896-e8b15e8fdb82\",\"trace_id\":\"aeb070fe-166a-48bf-99c0-acfcbfb40c1b\"}\n[2026-05-07 11:36:45] local.INFO: [Hubspot] Pagination completed {\"team_id\":2,\"endpoint\":\"https://api.hubapi.com/crm/v3/objects/deals/search\",\"total_requests\":1,\"total_records_fetched\":6,\"total_elapsed_seconds\":0.48,\"average_seconds_per_request\":0.48} {\"correlation_id\":\"462baf35-d312-4641-9896-e8b15e8fdb82\",\"trace_id\":\"aeb070fe-166a-48bf-99c0-acfcbfb40c1b\"}\n[2026-05-07 11:36:46] local.INFO: [HubSpot] importOpportunityBatch timing {\"teamId\":2,\"deal_count\":6,\"total_ms\":659,\"company_assoc_api_ms\":276,\"contact_assoc_api_ms\":203,\"prepare_entities_ms\":19,\"prepare_accounts_ms\":3,\"prepare_contacts_ms\":17,\"total_companies\":5,\"missing_companies\":0,\"total_contacts\":23,\"missing_contacts\":0,\"deals_loop_ms\":152,\"avg_deal_ms\":25,\"slow_deals_count\":0,\"slow_deals\":[]} {\"correlation_id\":\"462baf35-d312-4641-9896-e8b15e8fdb82\",\"trace_id\":\"aeb070fe-166a-48bf-99c0-acfcbfb40c1b\"}\n[2026-05-07 11:36:46] local.INFO: [HubSpot] Synced opportunities {\"team\":2,\"strategies\":\"lastModified\",\"sync_count\":5,\"total\":6,\"last_synced_id\":\"297846423\",\"duration_ms\":1161.39} {\"correlation_id\":\"462baf35-d312-4641-9896-e8b15e8fdb82\",\"trace_id\":\"aeb070fe-166a-48bf-99c0-acfcbfb40c1b\"}\n[2026-05-07 11:36:46] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage for command {\"command\":\"crm:sync-opportunity\",\"memoryBeforeCommandInMb\":60.0,\"memoryAfterCommandInMB\":62.0,\"memoryPeakBeforeCommandInMb\":99.727,\"memoryPeakAfterCommandInMB\":99.727} {\"correlation_id\":\"462baf35-d312-4641-9896-e8b15e8fdb82\",\"trace_id\":\"aeb070fe-166a-48bf-99c0-acfcbfb40c1b\"}\n[2026-05-07 11:36:52] local.INFO: [HubSpot Journal Polling] Getting offset from database {\"offset\":\"\",\"jiminny_team_id\":1} {\"correlation_id\":\"8568abe7-be33-417c-aa2f-b1bd53fb4adc\",\"trace_id\":\"582bff07-5ea4-4469-8ca8-efa41b04d46d\"}\n[2026-05-07 11:36:52] local.INFO: [HubSpot Journal API] Fetching latest journal entry {\"url\":\"https://api.hubapi.com/webhooks/v4/journal/latest\"} {\"correlation_id\":\"8568abe7-be33-417c-aa2f-b1bd53fb4adc\",\"trace_id\":\"582bff07-5ea4-4469-8ca8-efa41b04d46d\"}\n[2026-05-07 11:36:52] local.INFO: [HubSpot Journal Polling] No data {\"correlation_id\":\"8568abe7-be33-417c-aa2f-b1bd53fb4adc\",\"trace_id\":\"582bff07-5ea4-4469-8ca8-efa41b04d46d\"}\n[2026-05-07 11:36:52] local.WARNING: [HubSpot Journal Polling] Maximum empty results reached, stopping {\"empty_results\":5,\"max_empty_results\":5} {\"correlation_id\":\"8568abe7-be33-417c-aa2f-b1bd53fb4adc\",\"trace_id\":\"582bff07-5ea4-4469-8ca8-efa41b04d46d\"}\n[2026-05-07 11:36:52] local.WARNING: [HubSpot Journal Polling] Maximum empty results reached, stopping {\"empty_results\":5,\"max_empty_results\":5} {\"correlation_id\":\"8568abe7-be33-417c-aa2f-b1bd53fb4adc\",\"trace_id\":\"582bff07-5ea4-4469-8ca8-efa41b04d46d\"}\n[2026-05-07 11:36:52] local.INFO: [HubSpot Journal Polling] Service ending {\"runtime_seconds\":56,\"total_cycles\":5,\"files_downloaded\":0,\"empty_files\":0,\"other_portal_skipped\":0,\"total_events\":0,\"events_per_file\":0,\"avg_api_ms\":176.5,\"avg_download_ms\":0.0,\"avg_transform_ms\":0.0,\"avg_process_ms\":0.0,\"peak_memory_mb\":99.73} {\"correlation_id\":\"8568abe7-be33-417c-aa2f-b1bd53fb4adc\",\"trace_id\":\"582bff07-5ea4-4469-8ca8-efa41b04d46d\"}\n[2026-05-07 11:36:52] local.INFO: [HubSpot Journal Polling] Released polling lock {\"correlation_id\":\"8568abe7-be33-417c-aa2f-b1bd53fb4adc\",\"trace_id\":\"582bff07-5ea4-4469-8ca8-efa41b04d46d\"}\n[2026-05-07 11:37:05] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage before starting command {\"command\":\"meeting-bot:schedule-bot\",\"memoryBeforeCommandInMb\":60.0,\"memoryPeakBeforeCommandInMb\":99.727} {\"correlation_id\":\"34ae70b9-ee92-4e0c-bb97-3ecc204278be\",\"trace_id\":\"0d02e69d-5e33-4059-938c-d41350a39b72\"}\n[2026-05-07 11:37:05] local.INFO: [ScheduleBotCommand] Number of activities to be captured: 0 {\"correlation_id\":\"34ae70b9-ee92-4e0c-bb97-3ecc204278be\",\"trace_id\":\"0d02e69d-5e33-4059-938c-d41350a39b72\"}\n[2026-05-07 11:37:05] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage for command {\"command\":\"meeting-bot:schedule-bot\",\"memoryBeforeCommandInMb\":60.0,\"memoryAfterCommandInMB\":62.0,\"memoryPeakBeforeCommandInMb\":99.727,\"memoryPeakAfterCommandInMB\":99.727} {\"correlation_id\":\"34ae70b9-ee92-4e0c-bb97-3ecc204278be\",\"trace_id\":\"0d02e69d-5e33-4059-938c-d41350a39b72\"}\n[2026-05-07 11:37:06] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage before starting command {\"command\":\"dialers:monitor-activities\",\"memoryBeforeCommandInMb\":62.0,\"memoryPeakBeforeCommandInMb\":99.727} {\"correlation_id\":\"6052f7ad-dde1-41de-8051-5ba8d54babdd\",\"trace_id\":\"2e216848-ddf9-4f8c-8808-93a09cef8f5f\"}\n[2026-05-07 11:37:06] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage for command {\"command\":\"dialers:monitor-activities\",\"memoryBeforeCommandInMb\":62.0,\"memoryAfterCommandInMB\":62.0,\"memoryPeakBeforeCommandInMb\":99.727,\"memoryPeakAfterCommandInMB\":99.727} {\"correlation_id\":\"6052f7ad-dde1-41de-8051-5ba8d54babdd\",\"trace_id\":\"2e216848-ddf9-4f8c-8808-93a09cef8f5f\"}\n[2026-05-07 11:37:07] local.NOTICE: Monitoring start {\"correlation_id\":\"64decce4-a339-4c1b-8a73-4f612ef2eea2\",\"trace_id\":\"07e59e6c-d681-41f9-ba04-5cab00e9a754\"}\n[2026-05-07 11:37:07] local.NOTICE: Monitoring end {\"correlation_id\":\"64decce4-a339-4c1b-8a73-4f612ef2eea2\",\"trace_id\":\"07e59e6c-d681-41f9-ba04-5cab00e9a754\"}\n[2026-05-07 11:37:09] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage before starting command {\"command\":\"mailbox:skip-lists:refresh\",\"memoryBeforeCommandInMb\":62.0,\"memoryPeakBeforeCommandInMb\":99.727} {\"correlation_id\":\"0326b1c8-69ac-4d09-aae5-e808abc71f46\",\"trace_id\":\"2d16668b-26b0-44cf-a533-493500255667\"}\n[2026-05-07 11:37:09] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage for command {\"command\":\"mailbox:skip-lists:refresh\",\"memoryBeforeCommandInMb\":62.0,\"memoryAfterCommandInMB\":62.0,\"memoryPeakBeforeCommandInMb\":99.727,\"memoryPeakAfterCommandInMB\":99.727} {\"correlation_id\":\"0326b1c8-69ac-4d09-aae5-e808abc71f46\",\"trace_id\":\"2d16668b-26b0-44cf-a533-493500255667\"}\n[2026-05-07 11:37:10] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage before starting command {\"command\":\"mailbox:batch:process\",\"memoryBeforeCommandInMb\":62.0,\"memoryPeakBeforeCommandInMb\":99.727} {\"correlation_id\":\"aa512302-070c-4f99-b3ef-1a43c71b6297\",\"trace_id\":\"9e5c4036-6a42-4c64-a9b3-f11143254293\"}\n[2026-05-07 11:37:10] local.INFO: [EmailSchedule] STARTING batch process {\"host\":\"docker_lamp_1\"} {\"correlation_id\":\"aa512302-070c-4f99-b3ef-1a43c71b6297\",\"trace_id\":\"9e5c4036-6a42-4c64-a9b3-f11143254293\"}\n[2026-05-07 11:37:10] local.INFO: [EmailSchedule] FINISHED batch process {\"host\":\"docker_lamp_1\",\"processed\":0} {\"correlation_id\":\"aa512302-070c-4f99-b3ef-1a43c71b6297\",\"trace_id\":\"9e5c4036-6a42-4c64-a9b3-f11143254293\"}\n[2026-05-07 11:37:10] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage for command {\"command\":\"mailbox:batch:process\",\"memoryBeforeCommandInMb\":62.0,\"memoryAfterCommandInMB\":62.0,\"memoryPeakBeforeCommandInMb\":99.727,\"memoryPeakAfterCommandInMB\":99.727} {\"correlation_id\":\"aa512302-070c-4f99-b3ef-1a43c71b6297\",\"trace_id\":\"9e5c4036-6a42-4c64-a9b3-f11143254293\"}\n[2026-05-07 11:37:13] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage before starting command {\"command\":\"mailbox:batch:create\",\"memoryBeforeCommandInMb\":62.0,\"memoryPeakBeforeCommandInMb\":99.727} {\"correlation_id\":\"b3d4318e-3db2-413c-8fe1-8827ec834020\",\"trace_id\":\"870e454c-3a47-419e-88f1-2321d38b8937\"}\n[2026-05-07 11:37:13] local.INFO: [EmailSchedule] STARTING batch create {\"host\":\"docker_lamp_1\"} {\"correlation_id\":\"b3d4318e-3db2-413c-8fe1-8827ec834020\",\"trace_id\":\"870e454c-3a47-419e-88f1-2321d38b8937\"}\n[2026-05-07 11:37:13] local.INFO: [EmailSchedule] FINISHED batch create {\"host\":\"docker_lamp_1\"} {\"correlation_id\":\"b3d4318e-3db2-413c-8fe1-8827ec834020\",\"trace_id\":\"870e454c-3a47-419e-88f1-2321d38b8937\"}\n[2026-05-07 11:37:13] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage for command {\"command\":\"mailbox:batch:create\",\"memoryBeforeCommandInMb\":62.0,\"memoryAfterCommandInMB\":62.0,\"memoryPeakBeforeCommandInMb\":99.727,\"memoryPeakAfterCommandInMB\":99.727} {\"correlation_id\":\"b3d4318e-3db2-413c-8fe1-8827ec834020\",\"trace_id\":\"870e454c-3a47-419e-88f1-2321d38b8937\"}\n[2026-05-07 11:37:14] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage before starting command {\"command\":\"activity:sync\",\"memoryBeforeCommandInMb\":62.0,\"memoryPeakBeforeCommandInMb\":99.727} {\"correlation_id\":\"3e5f2a44-295d-4c28-b5e9-1a649c1f49ae\",\"trace_id\":\"f4d85ec2-b1d2-4418-bb65-814c209e628f\"}\n[2026-05-07 11:37:15] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage for command {\"command\":\"activity:sync\",\"memoryBeforeCommandInMb\":62.0,\"memoryAfterCommandInMB\":62.0,\"memoryPeakBeforeCommandInMb\":99.727,\"memoryPeakAfterCommandInMB\":99.727} {\"correlation_id\":\"3e5f2a44-295d-4c28-b5e9-1a649c1f49ae\",\"trace_id\":\"f4d85ec2-b1d2-4418-bb65-814c209e628f\"}\n[2026-05-07 11:37:21] local.INFO: [Commands/AsyncUpdateEsEntities] Starting ES update worker {\"pid\":20519,\"workerId\":\"\",\"target\":\"activities\"} {\"correlation_id\":\"1a81e470-75ac-4728-9d45-9f879fc96c5b\",\"trace_id\":\"7184ea88-9f01-42bc-9508-8e4ad0c1204b\"}\n[2026-05-07 11:37:22] local.INFO: [Jiminny\\Jobs\\Mailbox\\CreateBatches] processed 2 inboxes and created 0 batches {\"userId\":null,\"batchSize\":30,\"maxBatches\":1000} {\"correlation_id\":\"798a3e91-233c-4b61-aa8e-80a7b84ac45f\",\"trace_id\":\"870e454c-3a47-419e-88f1-2321d38b8937\"}\n[2026-05-07 11:37:53] local.INFO: [Commands/AsyncUpdateEsEntities] Starting ES update worker {\"pid\":20661,\"workerId\":\"\",\"target\":\"activities\"} {\"correlation_id\":\"293fa4a8-dc12-452e-88a3-f7408f8ed676\",\"trace_id\":\"e3d77ea8-2ff3-4b4e-a388-8f21192e51f3\"}","role_description":"text entry area","is_enabled":true,"is_focused":true,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Sync Changes","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.018055556,"height":0.026666667},"on_screen":false,"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Hide This Notification","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.018055556,"height":0.026666667},"on_screen":false,"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Code changed:","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.088194445,"height":0.027777778},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Hide","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.018055556,"height":0.026666667},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"2","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.016666668,"height":0.02111111},"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"60","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.021527778,"height":0.02111111},"on_screen":false,"role_description":"text"},{"role":"AXButton","text":"Previous Highlighted Error","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.015277778,"height":0.025555555},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Next Highlighted Error","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.014583333,"height":0.025555555},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXTextArea","text":"<?php\n\ndeclare(strict_types=1);\n\nnamespace Jiminny\\Services\\Crm\\Hubspot;\n\nuse HubSpot\\Client\\Crm\\Deals\\ApiException as DealApiException;\nuse HubSpot\\Client\\Crm\\Contacts\\ApiException as ContactApiException;\nuse HubSpot\\Client\\Crm\\Companies\\ApiException as CompanyApiException;\nuse HubSpot\\Client\\Crm\\Contacts\\Model\\SimplePublicObjectWithAssociations as ContactsWithAssociations;\nuse HubSpot\\Client\\Crm\\Companies\\Model\\SimplePublicObjectWithAssociations as CompaniesWithAssociations;\nuse HubSpot\\Client\\Crm\\Deals\\Model\\SimplePublicObjectWithAssociations as DealWithAssociations;\nuse HubSpot\\Client\\Crm\\Objects\\Model\\SimplePublicObjectInput;\nuse HubSpot\\Client\\Crm\\Objects\\Model\\SimplePublicObjectWithAssociations as ObjectWithAssociations;\nuse HubSpot\\Client\\Crm\\Pipelines\\Model\\Error;\nuse HubSpot\\Client\\Crm\\Pipelines\\Model\\PipelineStage;\nuse HubSpot\\Client\\Crm\\Properties\\Model\\Property;\nuse HubSpot\\Discovery\\Discovery;\nuse Jiminny\\Component\\Utility\\Service\\ProviderRateLimiter;\nuse Jiminny\\Exceptions\\CrmException;\nuse Jiminny\\Exceptions\\RateLimitException;\nuse Jiminny\\Exceptions\\SocialAccountTokenInvalidException;\nuse Jiminny\\Jobs\\Crm\\NoteObject;\nuse Jiminny\\Models\\Crm\\Field;\nuse Jiminny\\Services\\Crm\\BaseClient;\nuse Jiminny\\Services\\Crm\\Hubspot\\DTO\\Response\\Owner;\nuse Jiminny\\Services\\SocialAccountService;\nuse SevenShores\\Hubspot\\Exceptions\\BadRequest;\nuse SevenShores\\Hubspot\\Exceptions\\HubspotException;\nuse SevenShores\\Hubspot\\Factory;\nuse SevenShores\\Hubspot\\Http\\Response;\nuse Jiminny\\Services\\Crm\\Hubspot\\Pagination\\HubspotPaginationService;\nuse Throwable;\n\n/**\n * @phpstan-type CrmFieldOption array{id:string, label:string, value?:string}\n */\nclass Client extends BaseClient implements HubspotClientInterface\n{\n public const string MIN_API_VERSION = '2';\n\n public const string BASE_URL = 'https://api.hubapi.com';\n\n public const int ASSOCIATIONS_BATCH_SIZE_LIMIT = 1000;\n\n private HubspotPaginationService $paginationService;\n private HubspotTokenManager $tokenManager;\n private ProviderRateLimiter $rateLimiter;\n\n public function __construct(\n SocialAccountService $socialAccountService,\n HubspotPaginationService $paginationService,\n HubspotTokenManager $tokenManager,\n ProviderRateLimiter $rateLimiter,\n ) {\n parent::__construct($socialAccountService);\n $this->paginationService = $paginationService;\n $this->tokenManager = $tokenManager;\n $this->rateLimiter = $rateLimiter;\n\n $this->setBaseUrl(self::BASE_URL);\n $this->setVersion(self::MIN_API_VERSION);\n }\n\n /**\n * Single entry point for every HubSpot API call. Enforces the per-portal\n * rate limit configured in the rate_limits table (morphed to the current\n * Configuration) and reacts to a real 429 from HubSpot by translating it\n * into a RateLimitException carrying Retry-After.\n *\n * Wrap any outbound HubSpot call (SDK or raw HTTP) like:\n *\n * $this->executeRequest(fn () => $this->getNewInstance()->crm()->...);\n *\n * @template T\n * @param callable(): T $apiCall\n * @return T\n *\n * @throws RateLimitException\n */\n private function executeRequest(callable $apiCall)\n {\n if (! $this->rateLimiter->canMakeRequest($this->config)) {\n $retryAfter = $this->rateLimiter->requestAvailableIn($this->config);\n\n $this->log->warning('[Hubspot] Rate limit exceeded, deferring request', [\n 'team_id' => $this->config->team_id,\n 'config_id' => $this->config->getId(),\n 'retry_after' => $retryAfter,\n ]);\n\n throw new RateLimitException(\n 'Hubspot rate limit reached for configuration ' . $this->config->getId(),\n $retryAfter,\n );\n }\n\n $this->rateLimiter->incrementRequestCount($this->config);\n\n try {\n return $apiCall();\n } catch (Throwable $e) {\n if ($this->isHubspotRateLimit($e)) {\n $retryAfter = $this->parseRetryAfter($e);\n\n $this->log->warning('[Hubspot] Received 429 from API', [\n 'team_id' => $this->config->team_id,\n 'config_id' => $this->config->getId(),\n 'retry_after' => $retryAfter,\n 'reason' => $e->getMessage(),\n ]);\n\n throw new RateLimitException('Hubspot returned 429', $retryAfter, $e);\n }\n\n throw $e;\n }\n }\n\n private function isHubspotRateLimit(Throwable $e): bool\n {\n return method_exists($e, 'getCode') && (int) $e->getCode() === 429;\n }\n\n private function parseRetryAfter(Throwable $e): int\n {\n if (method_exists($e, 'getResponseHeaders')) {\n $headers = $e->getResponseHeaders() ?: [];\n $value = $headers['Retry-After'] ?? $headers['retry-after'] ?? null;\n if (is_array($value)) {\n $value = $value[0] ?? null;\n }\n if (is_numeric($value)) {\n return (int) $value;\n }\n }\n\n return 10;\n }\n\n public function getMinimumApiVersion(): string\n {\n return self::MIN_API_VERSION;\n }\n\n public function getInstance(): Factory\n {\n return new Factory([\n 'key' => $this->accessToken,\n 'oauth2' => true,\n 'base_url' => $this->baseUrl,\n ]);\n }\n\n public function getNewInstance(): Discovery\n {\n return \\HubSpot\\Factory::createWithAccessToken($this->accessToken);\n }\n\n /**\n * Secondly and daily limits for Hubspot API\n *\n * Product Tier: Free & Starter | Professional & Enterprise | API add-on (any tier)\n * Burst: 100/10 seconds | 150/10 seconds | 200/10 seconds\n * Daily: 250,000 | 500,000 | 1,000,000\n *\n * Official documentation states: The search endpoints are rate limited to five requests per second.\n * Since with 5 RPS were still hitting secondly rate limits we lowered it to 4\n */\n public function getPaginatedData(array $payload, string $type, int $offset = 0): array\n {\n $total = 0;\n $lastId = null;\n $rows = [];\n foreach ($this->getPaginatedDataGenerator($payload, $type, $offset, $total, $lastId) as $row) {\n $rows[] = $row;\n }\n\n return ['results' => $rows, 'total' => $total, 'last_record' => $lastId];\n }\n\n /**\n * @throws HubspotException\n * @throws SocialAccountTokenInvalidException\n * @throws BadRequest\n */\n public function getPaginatedDataGenerator(\n array $payload,\n string $type,\n int $offset = 0,\n int &$total = 0,\n ?string &$lastRecordId = null\n ): \\Generator {\n return $this->paginationService->getPaginatedDataGenerator(\n $this,\n $payload,\n $type,\n $offset,\n $total,\n $lastRecordId\n );\n }\n\n /**\n * @throws DealApiException\n * @throws CrmException\n */\n public function getOpportunityById(string $crmId, array $fields): array\n {\n try {\n $deal = $this->executeRequest(fn () => $this->getNewInstance()->crm()->deals()->basicApi()->getById(\n $crmId,\n implode(',', $fields),\n 'companies,contacts'\n ));\n } catch (DealApiException $e) {\n $this->log->info('[Hubspot] Failed to fetch opportunity', [\n 'crm_id' => $crmId,\n 'reason' => $e->getMessage(),\n ]);\n\n throw $e;\n }\n\n if (! $deal instanceof DealWithAssociations) {\n throw new CrmException('Deal not found');\n }\n\n return [\n 'id' => $deal->getId(),\n 'properties' => $deal->getProperties(),\n 'associations' => $deal->getAssociations(),\n ];\n }\n\n /**\n * Generic batch read method for HubSpot objects\n *\n * @param string $objectType The object type ('deals', 'companies', 'contacts')\n * @param array<string> $crmIds Array of HubSpot object IDs (max 100)\n * @param array<string> $fields Array of property names to fetch\n *\n * @return array<string, array> Array keyed by CRM ID with object data\n */\n private function batchReadObjects(string $objectType, array $crmIds, array $fields): array\n {\n if (empty($crmIds)) {\n return [];\n }\n\n $this->validateBatchSize($objectType, $crmIds);\n $this->ensureValidToken();\n\n try {\n $batchConfig = $this->createBatchConfiguration($objectType);\n $batchReadRequest = $this->prepareBatchRequest($batchConfig, $crmIds, $fields);\n $response = $batchConfig['api']->read($batchReadRequest);\n\n $this->validateApiResponse($response, $objectType);\n\n $results = $this->processApiResults($response);\n $this->logBatchResults($objectType, $crmIds, $results);\n\n return $results;\n } catch (\\Throwable $e) {\n $this->handleBatchError($e, $objectType, $crmIds);\n }\n }\n\n private function validateBatchSize(string $objectType, array $crmIds): void\n {\n if (count($crmIds) > 100) {\n throw new \\InvalidArgumentException(\"Batch size cannot exceed 100 {$objectType}\");\n }\n }\n\n private function createBatchConfiguration(string $objectType): array\n {\n $configurations = [\n 'deals' => [\n 'batchReadRequest' => new \\HubSpot\\Client\\Crm\\Deals\\Model\\BatchReadInputSimplePublicObjectId(),\n 'inputClass' => \\HubSpot\\Client\\Crm\\Deals\\Model\\SimplePublicObjectId::class,\n 'api' => $this->getNewInstance()->crm()->deals()->batchApi(),\n ],\n 'companies' => [\n 'batchReadRequest' => new \\HubSpot\\Client\\Crm\\Companies\\Model\\BatchReadInputSimplePublicObjectId(),\n 'inputClass' => \\HubSpot\\Client\\Crm\\Companies\\Model\\SimplePublicObjectId::class,\n 'api' => $this->getNewInstance()->crm()->companies()->batchApi(),\n ],\n 'contacts' => [\n 'batchReadRequest' => new \\HubSpot\\Client\\Crm\\Contacts\\Model\\BatchReadInputSimplePublicObjectId(),\n 'inputClass' => \\HubSpot\\Client\\Crm\\Contacts\\Model\\SimplePublicObjectId::class,\n 'api' => $this->getNewInstance()->crm()->contacts()->batchApi(),\n ],\n ];\n\n if (! isset($configurations[$objectType])) {\n throw new \\InvalidArgumentException(\"Unsupported object type: {$objectType}\");\n }\n\n return $configurations[$objectType];\n }\n\n private function prepareBatchRequest(array $batchConfig, array $crmIds, array $fields): object\n {\n $batchReadRequest = $batchConfig['batchReadRequest'];\n $inputClass = $batchConfig['inputClass'];\n\n $inputs = array_map(function ($crmId) use ($inputClass) {\n $input = new $inputClass();\n $input->setId($crmId);\n\n return $input;\n }, $crmIds);\n\n $batchReadRequest->setInputs($inputs);\n $batchReadRequest->setProperties($fields);\n\n return $batchReadRequest;\n }\n\n private function validateApiResponse($response, string $objectType): void\n {\n if (! $response) {\n throw new CrmException(\"HubSpot API returned null response for {$objectType} batch read\");\n }\n }\n\n private function processApiResults($response): array\n {\n $results = [];\n $responseResults = $response->getResults();\n\n if ($responseResults) {\n foreach ($responseResults as $object) {\n if ($object && $object->getId()) {\n $results[$object->getId()] = [\n 'id' => $object->getId(),\n 'properties' => $object->getProperties() ?: [],\n ];\n }\n }\n }\n\n return $results;\n }\n\n private function logBatchResults(string $objectType, array $crmIds, array $results): void\n {\n $this->log->info(\"[HubSpot] Batch fetched {$objectType}\", [\n 'requested_count' => count($crmIds),\n 'returned_count' => count($results),\n 'crm_ids' => $crmIds,\n ]);\n }\n\n private function handleBatchError(\\Throwable $e, string $objectType, array $crmIds): void\n {\n $errorMessage = $e->getMessage() ?: 'Unknown error';\n $errorTrace = $e->getTraceAsString() ?: 'No trace available';\n\n $this->log->error(\"[HubSpot] Failed to batch fetch {$objectType}\", [\n 'crm_ids' => $crmIds,\n 'error' => $errorMessage,\n 'trace' => $errorTrace,\n ]);\n\n throw new CrmException(\"Failed to batch fetch {$objectType}: \" . $errorMessage);\n }\n\n /**\n * Batch read multiple opportunities by their CRM IDs\n *\n * @param array<string> $crmIds Array of HubSpot deal IDs (max 100)\n * @param array<string> $fields Array of property names to fetch\n *\n * @return array<string, array> Array keyed by CRM ID with opportunity data\n */\n public function getOpportunitiesByIds(array $crmIds, array $fields): array\n {\n return $this->batchReadObjects('deals', $crmIds, $fields);\n }\n\n /**\n * Batch read multiple companies by their CRM IDs\n *\n * @param array<string> $crmIds Array of HubSpot company IDs (max 100)\n * @param array<string> $fields Array of property names to fetch\n *\n * @return array<string, array> Array keyed by CRM ID with company data\n */\n public function getCompaniesByIds(array $crmIds, array $fields): array\n {\n return $this->batchReadObjects('companies', $crmIds, $fields);\n }\n\n /**\n * Batch read multiple contacts by their CRM IDs\n *\n * @param array<string> $crmIds Array of HubSpot contact IDs (max 100)\n * @param array<string> $fields Array of property names to fetch\n *\n * @return array<string, array> Array keyed by CRM ID with contact data\n */\n public function getContactsByIds(array $crmIds, array $fields): array\n {\n return $this->batchReadObjects('contacts', $crmIds, $fields);\n }\n\n /**\n * @throws CompanyApiException\n * @throws CrmException\n */\n public function getAccountById(string $crmId, array $fields): array\n {\n try {\n $company = $this->getNewInstance()->crm()->companies()->basicApi()->getById(\n $crmId,\n implode(',', $fields),\n );\n } catch (CompanyApiException $e) {\n $this->log->info('[Hubspot] Failed to fetch account', [\n 'crm_id' => $crmId,\n 'reason' => $e->getMessage(),\n ]);\n\n throw $e;\n }\n\n if (! $company instanceof CompaniesWithAssociations) {\n throw new CrmException('Account not found');\n }\n\n return [\n 'id' => $company->getId(),\n 'properties' => $company->getProperties(),\n ];\n }\n\n /**\n * @throws ContactApiException\n * @throws CrmException\n */\n public function getContactById(string $crmId, array $fields): array\n {\n try {\n $contact = $this->getNewInstance()->crm()->contacts()->basicApi()->getById(\n $crmId,\n implode(',', $fields)\n );\n } catch (ContactApiException $e) {\n $this->log->info('[Hubspot] Failed to fetch contact', [\n 'crm_id' => $crmId,\n 'reason' => $e->getMessage(),\n ]);\n\n throw $e;\n }\n\n if (! $contact instanceof ContactsWithAssociations) {\n throw new CrmException('Contact not found');\n }\n\n return [\n 'id' => $contact->getId(),\n 'properties' => $contact->getProperties(),\n ];\n }\n\n /**\n * This is email search request that Hubspot offers as GET (more generous quota)\n */\n public function getContactByEmail(string $email, array $fields = []): array\n {\n try {\n $contact = $this->getNewInstance()->crm()->contacts()->basicApi()->getById(\n $email,\n implode(',', $fields),\n null,\n false,\n 'email'\n );\n\n return [\n 'id' => $contact->getId(),\n 'properties' => $contact->getProperties(),\n ];\n } catch (ContactApiException $e) {\n $this->log->info('[Hubspot] Failed to fetch contact', [\n 'email' => $email,\n 'reason' => $e->getMessage(),\n ]);\n\n return [];\n }\n }\n\n /**\n * @throws CrmException\n */\n public function fetchProperty(string $objectType, string $propertyId): Property\n {\n $result = $this->getNewInstance()->crm()->properties()->coreApi()->getByName($objectType, $propertyId);\n\n if (! $result instanceof Property) {\n $this->log->error('[Hubspot] Failed to fetch property', [\n 'object_type' => $objectType,\n 'property_id' => $propertyId,\n 'reason' => $result->getMessage(),\n ]);\n\n throw new CrmException('Failed to fetch property');\n }\n\n return $result;\n }\n\n /**\n * @return array<CrmFieldOption>\n */\n public function fetchPropertyOptions(string $objectType, string $propertyId): array\n {\n /** @var array<CrmFieldOption> */\n return $this->fetchProperty($objectType, $propertyId)->getOptions();\n }\n\n /**\n * @return array<array{id:string, label:string, deleted:bool}>\n */\n public function fetchCallDispositions(): array\n {\n /** @var Response $response */\n $response = $this->getInstance()->engagements()->getCallDispositions();\n\n /**\n * @var array<array{\n * id:string,\n * label:string,\n * deleted: bool\n * }>\n */\n return $response->toArray();\n }\n\n /**\n * @return array<CrmFieldOption>\n */\n public function fetchOpportunityPipelineStages(): array\n {\n $stages = [];\n $apiResponse = $this->getNewInstance()->crm()->pipelines()->pipelinesApi()->getAll('deals');\n\n if ($apiResponse instanceof Error) {\n $this->log->error('[Hubspot] Failed to fetch opportunity pipelines', [\n 'reason' => $apiResponse->getMessage(),\n ]);\n\n return [];\n }\n\n foreach ($apiResponse->getResults() as $pipeline) {\n $pipelineStages = array_map(\n static function (PipelineStage $stage) {\n return [\n 'id' => $stage->getId(),\n 'label' => $stage->getLabel(),\n ];\n },\n $pipeline->getStages()\n );\n\n $stages = array_merge($stages, $pipelineStages);\n }\n\n return $stages;\n }\n\n public function fetchOpportunityPipelines(): array\n {\n $pipelines = [];\n\n try {\n $apiResponse = $this->makeRequest('/crm/v3/pipelines/deals');\n } catch (\\Exception $e) {\n $this->log->info('[Hubspot] Failed to fetch opportunity pipelines', [\n 'reason' => $e->getMessage(),\n ]);\n\n return [];\n }\n\n $response = $apiResponse->toArray();\n\n foreach ($response['results'] as $pipeline) {\n $pipelines[] = [\n 'id' => $pipeline['id'],\n 'label' => $pipeline['label'],\n ];\n }\n\n return $pipelines;\n }\n\n /**\n * @return array<CrmFieldOption>\n */\n public function fetchMeetingOutcomeFieldOptions(Field $field): array\n {\n return $field->getCrmProviderId() === 'meetingOutcome'\n ? $this->fetchMeetingOutcomeTypes()\n : $this->fetchCallActivityTypes();\n }\n\n public function fetchMeetingOutcomeTypes(): array\n {\n return $this->extractMeetingTypeOptions(\n 'https://api.hubapi.com/crm/v3/properties/meeting/hs_meeting_outcome'\n );\n }\n\n public function fetchCallActivityTypes(): array\n {\n return $this->extractMeetingTypeOptions(\n 'https://api.hubapi.com/crm/v3/properties/call/hs_activity_type'\n );\n }\n\n private function extractMeetingTypeOptions(string $endpoint): array\n {\n /** @var Response $response */\n $response = $this->getInstance()\n ->getClient()\n ->request('GET', $endpoint);\n\n /**\n * @var array<array{\n * value: string,\n * label: string,\n * displayOrder: int\n * }> $optionData\n */\n $optionData = $response->toArray()['options'] ?? [];\n\n $options = [];\n foreach ($optionData as $item) {\n $options[] = [\n 'id' => $item['value'],\n 'value' => $item['value'],\n 'label' => $item['label'],\n 'display_order' => $item['displayOrder'],\n ];\n }\n\n return $options;\n }\n\n /**\n * @return array<CrmFieldOption>\n */\n public function fetchDispositionFieldOptions(): array\n {\n $options = [];\n\n $dispositions = $this->fetchCallDispositions();\n\n foreach ($dispositions as $disposition) {\n if ($disposition['deleted'] !== false) {\n continue;\n }\n\n $option['value'] = $disposition['id'];\n $option['id'] = $disposition['id'];\n $option['label'] = $disposition['label'];\n\n $options[] = $option;\n }\n\n return $options;\n }\n\n /**\n * @return array<CrmFieldOption>\n */\n public function fetchOpportunityFieldOptions(Field $field): array\n {\n if ($field->isStageField()) {\n return $this->fetchOpportunityPipelineStages();\n }\n\n if ($field->isPipelineField()) {\n return $this->fetchOpportunityPipelines();\n }\n\n return $this->fetchPropertyOptions('deals', $field->getCrmProviderId());\n }\n\n /**\n * @throws BadRequest\n * @throws HubspotException\n */\n public function makeRequest(string $endpoint, $method = 'GET', $payload = [], ?string $queryString = null)\n {\n $endpoint = self::BASE_URL . $endpoint;\n\n if ($method === 'GET') {\n $response = $this->getInstance()->getClient()?->request(\n method: $method,\n endpoint: $endpoint,\n query_string: $queryString\n );\n } else {\n $response = $this->getInstance()->getClient()->request($method, $endpoint, [\n 'json' => ($payload),\n ]);\n }\n\n $max = $response->getHeaderLine('X-HubSpot-RateLimit-Max'); // \"110\"\n $remaining = $response->getHeaderLine('X-HubSpot-RateLimit-Remaining'); // \"109\"\n $interval = $response->getHeaderLine('X-HubSpot-RateLimit-Interval-Milliseconds'); // \"10000\"\n $body = json_decode((string) $response->getBody(), true);\n\n \\Illuminate\\Support\\Facades\\Log::channel('custom_channel')->info('$max ' . PHP_EOL . print_r($max, true));\n \\Illuminate\\Support\\Facades\\Log::channel('custom_channel')->info('$remaining ' . PHP_EOL . print_r($remaining, true));\n \\Illuminate\\Support\\Facades\\Log::channel('custom_channel')->info('$interval ' . PHP_EOL . print_r($interval, true));\n \\Illuminate\\Support\\Facades\\Log::channel('custom_channel')->info('$body ' . PHP_EOL . print_r($body, true));\n\n return $response;\n }\n\n /**\n * @throws BadRequest\n * @throws HubspotException\n */\n public function createMeeting(array $payload): Response\n {\n $endpoint = '/crm/v3/objects/meetings';\n\n return $this->makeRequest($endpoint, 'POST', $payload);\n }\n\n /**\n * @throws BadRequest\n * @throws HubspotException\n */\n public function updateMeeting(string $meetingId, array $payload): Response\n {\n $endpoint = '/crm/v3/objects/meetings/' . $meetingId;\n\n return $this->makeRequest($endpoint, 'PATCH', $payload);\n }\n\n /**\n * @throws \\Exception\n */\n public function createNote(\n string $body,\n string $ownerId,\n int $timestamp,\n string $objectId,\n NoteObject $noteObject\n ): ?string {\n try {\n $noteInput = new SimplePublicObjectInput([\n 'properties' => [\n 'hs_note_body' => $body,\n 'hubspot_owner_id' => $ownerId,\n 'hs_timestamp' => $timestamp,\n ],\n ]);\n\n // Create note\n $note = $this->getNewInstance()->crm()->objects()->basicApi()->create('note', $noteInput);\n\n $this->getNewInstance()->crm()->objects()->associationsApi()->create(\n 'note',\n $note->getId(),\n $this->getNoteObject($noteObject),\n $objectId,\n $this->getNoteAssociationType($noteObject),\n );\n\n return $note->getId();\n } catch (\\Exception $e) {\n $this->log->error('[Hubspot] Failed to create note', [\n 'objectId' => $objectId,\n 'noteObject' => $noteObject->getObjectType(),\n 'reason' => $e->getMessage(),\n ]);\n\n \\Sentry::captureException($e);\n }\n\n return null;\n }\n\n public function updateEngagement(string $objectId, array $engagement, array $metadata): void\n {\n $this->getInstance()->engagements()->update($objectId, $engagement, $metadata);\n }\n\n public function getEngagementData(string $engagementId): array\n {\n $engagement = $this->getInstance()->engagements()->get($engagementId);\n\n return $engagement->toArray();\n }\n\n public function createEngagement(array $engagement, array $associations, array $metadata): Response\n {\n return $this->getInstance()\n ->engagements()\n ->create($engagement, $associations, $metadata);\n }\n\n public function isUnauthorizedException(\\Exception $e): bool\n {\n // Check for specific HubSpot API exception types first\n if ($e instanceof BadRequest) {\n // BadRequest can contain 401 status codes\n return $e->getCode() === 401;\n }\n\n // Check for HTTP client exceptions with status codes\n if ($e instanceof \\GuzzleHttp\\Exception\\RequestException && $e->hasResponse()) {\n $response = $e->getResponse();\n if ($response !== null) {\n return $response->getStatusCode() === 401;\n }\n }\n\n // Check for Guzzle HTTP exceptions\n if ($e instanceof \\GuzzleHttp\\Exception\\ClientException) {\n return $e->getCode() === 401;\n }\n\n // Fallback to string matching as last resort, but be more specific\n $message = strtolower($e->getMessage());\n\n return str_contains($message, '401 unauthorized') ||\n str_contains($message, 'http 401') ||\n str_contains($message, 'status code 401') ||\n (preg_match('/\\b401\\b/', $message) && str_contains($message, 'unauthorized'));\n }\n\n /**\n * Validates and refreshes the access token if needed before API requests.\n * This ensures long-running processes don't fail due to token expiration.\n *\n * @throws SocialAccountTokenInvalidException\n */\n public function ensureValidToken(): void\n {\n if ($this->oauthAccount === null) {\n return;\n }\n\n $newToken = $this->tokenManager->ensureValidToken($this->oauthAccount);\n if ($newToken !== null) {\n $this->accessToken = $newToken;\n }\n }\n\n public function getConfig()\n {\n return $this->config;\n }\n\n // returns only active (archived=false)\n public function getOwners(): array\n {\n return $this->getNewInstance()->crm()->owners()->getAll();\n }\n\n /**\n * @param bool $archived\n *\n * @return array<Owner>|[]\n */\n public function getOwnersArchived(bool $archived = true): array\n {\n $endpoint = '/crm/v3/owners';\n $queryParams = [\n 'archived' => $archived ? 'true' : 'false',\n ];\n $queryString = http_build_query($queryParams);\n\n $owners = [];\n\n try {\n $response = $this->makeRequest(endpoint: $endpoint, queryString: $queryString);\n $responseData = $response?->toArray();\n\n foreach ($responseData['results'] as $result) {\n try {\n $owners[] = Owner::create($result);\n } catch (Throwable $e) {\n $this->log->error('[HubSpot] Failed to process owner data', [\n 'result' => $result,\n 'error' => $e->getMessage(),\n ]);\n\n continue;\n }\n }\n } catch (Throwable $e) {\n $this->log->error('HubSpot] Failed to fetch owners', [\n 'archived' => $archived,\n 'error' => $e->getMessage(),\n ]);\n\n return [];\n }\n\n return $owners;\n }\n\n public function getMeeting(string $engagementId): ObjectWithAssociations\n {\n return $this->getNewInstance()->crm()->objects()->basicApi()\n ->getById('meeting', $engagementId, null, 'contact,company,deal');\n }\n\n public function deleteEngagement(string $engagementId): void\n {\n $this->getInstance()->engagements()->delete((int) $engagementId);\n }\n\n public function getAssociationsData(array $ids, string $fromObject, string $toObject): array\n {\n $associationData = [];\n $idChunks = array_chunk($ids, self::ASSOCIATIONS_BATCH_SIZE_LIMIT);\n\n foreach ($idChunks as $idChunk) {\n try {\n $batchInput = new \\HubSpot\\Client\\Crm\\Associations\\Model\\BatchInputPublicObjectId();\n $batchInput->setInputs(array_map(function ($id) {\n $publicObjectId = new \\HubSpot\\Client\\Crm\\Associations\\Model\\PublicObjectId();\n $publicObjectId->setId($id);\n\n return $publicObjectId;\n }, $idChunk));\n\n $associatedObjectsData = $this\n ->getNewInstance()\n ->crm()\n ->associations()\n ->batchApi()\n ->read($fromObject, $toObject, $batchInput);\n\n if ($associatedObjectsData instanceof \\HubSpot\\Client\\Crm\\Associations\\Model\\BatchResponsePublicAssociationMulti) {\n foreach ($associatedObjectsData->getResults() as $association) {\n $from = $association->getFrom()->getId();\n $toAssociations = $association->getTo();\n\n if (! empty($toAssociations)) {\n $associationData[$from] = array_map(function ($item) {\n return $item->getId();\n }, $toAssociations);\n }\n }\n }\n } catch (\\Exception $e) {\n $this->log->error('[Hubspot] Failed to fetch associations', [\n 'from_object' => $fromObject,\n 'to_object' => $toObject,\n 'reason' => $e->getMessage(),\n ]);\n }\n }\n\n return $associationData;\n }\n\n /**\n * @throws \\Exception\n */\n private function getNoteAssociationType(NoteObject $noteObject): string\n {\n return match($noteObject) {\n NoteObject::Opportunity => 'note_to_deal',\n NoteObject::Lead, NoteObject::Contact => 'note_to_contact', // or 'note_to_lead' if your portal supports it\n NoteObject::Account => 'note_to_company',\n NoteObject::Call, NoteObject::Event => throw new \\Exception('Not supported'),\n };\n }\n\n /**\n * @throws \\Exception\n */\n private function getNoteObject(NoteObject $noteObject): string\n {\n return match($noteObject) {\n NoteObject::Opportunity => 'deal',\n NoteObject::Lead, NoteObject::Contact => 'contact',\n NoteObject::Account => 'company',\n NoteObject::Call, NoteObject::Event => throw new \\Exception('Not supported'),\n };\n }\n\n public function addAssociations(string $objectType, string $associationType, array $payload): Response\n {\n $endpoint = \"/crm/v4/associations/$objectType/$associationType/batch/create\";\n\n return $this->makeRequest($endpoint, 'POST', $payload);\n }\n\n public function removeAssociations(string $objectType, string $associationType, array $payload): Response\n {\n $endpoint = \"/crm/v4/associations/$objectType/$associationType/batch/archive\";\n\n return $this->makeRequest($endpoint, 'POST', $payload);\n }\n}","depth":4,"on_screen":true,"value":"<?php\n\ndeclare(strict_types=1);\n\nnamespace Jiminny\\Services\\Crm\\Hubspot;\n\nuse HubSpot\\Client\\Crm\\Deals\\ApiException as DealApiException;\nuse HubSpot\\Client\\Crm\\Contacts\\ApiException as ContactApiException;\nuse HubSpot\\Client\\Crm\\Companies\\ApiException as CompanyApiException;\nuse HubSpot\\Client\\Crm\\Contacts\\Model\\SimplePublicObjectWithAssociations as ContactsWithAssociations;\nuse HubSpot\\Client\\Crm\\Companies\\Model\\SimplePublicObjectWithAssociations as CompaniesWithAssociations;\nuse HubSpot\\Client\\Crm\\Deals\\Model\\SimplePublicObjectWithAssociations as DealWithAssociations;\nuse HubSpot\\Client\\Crm\\Objects\\Model\\SimplePublicObjectInput;\nuse HubSpot\\Client\\Crm\\Objects\\Model\\SimplePublicObjectWithAssociations as ObjectWithAssociations;\nuse HubSpot\\Client\\Crm\\Pipelines\\Model\\Error;\nuse HubSpot\\Client\\Crm\\Pipelines\\Model\\PipelineStage;\nuse HubSpot\\Client\\Crm\\Properties\\Model\\Property;\nuse HubSpot\\Discovery\\Discovery;\nuse Jiminny\\Component\\Utility\\Service\\ProviderRateLimiter;\nuse Jiminny\\Exceptions\\CrmException;\nuse Jiminny\\Exceptions\\RateLimitException;\nuse Jiminny\\Exceptions\\SocialAccountTokenInvalidException;\nuse Jiminny\\Jobs\\Crm\\NoteObject;\nuse Jiminny\\Models\\Crm\\Field;\nuse Jiminny\\Services\\Crm\\BaseClient;\nuse Jiminny\\Services\\Crm\\Hubspot\\DTO\\Response\\Owner;\nuse Jiminny\\Services\\SocialAccountService;\nuse SevenShores\\Hubspot\\Exceptions\\BadRequest;\nuse SevenShores\\Hubspot\\Exceptions\\HubspotException;\nuse SevenShores\\Hubspot\\Factory;\nuse SevenShores\\Hubspot\\Http\\Response;\nuse Jiminny\\Services\\Crm\\Hubspot\\Pagination\\HubspotPaginationService;\nuse Throwable;\n\n/**\n * @phpstan-type CrmFieldOption array{id:string, label:string, value?:string}\n */\nclass Client extends BaseClient implements HubspotClientInterface\n{\n public const string MIN_API_VERSION = '2';\n\n public const string BASE_URL = 'https://api.hubapi.com';\n\n public const int ASSOCIATIONS_BATCH_SIZE_LIMIT = 1000;\n\n private HubspotPaginationService $paginationService;\n private HubspotTokenManager $tokenManager;\n private ProviderRateLimiter $rateLimiter;\n\n public function __construct(\n SocialAccountService $socialAccountService,\n HubspotPaginationService $paginationService,\n HubspotTokenManager $tokenManager,\n ProviderRateLimiter $rateLimiter,\n ) {\n parent::__construct($socialAccountService);\n $this->paginationService = $paginationService;\n $this->tokenManager = $tokenManager;\n $this->rateLimiter = $rateLimiter;\n\n $this->setBaseUrl(self::BASE_URL);\n $this->setVersion(self::MIN_API_VERSION);\n }\n\n /**\n * Single entry point for every HubSpot API call. Enforces the per-portal\n * rate limit configured in the rate_limits table (morphed to the current\n * Configuration) and reacts to a real 429 from HubSpot by translating it\n * into a RateLimitException carrying Retry-After.\n *\n * Wrap any outbound HubSpot call (SDK or raw HTTP) like:\n *\n * $this->executeRequest(fn () => $this->getNewInstance()->crm()->...);\n *\n * @template T\n * @param callable(): T $apiCall\n * @return T\n *\n * @throws RateLimitException\n */\n private function executeRequest(callable $apiCall)\n {\n if (! $this->rateLimiter->canMakeRequest($this->config)) {\n $retryAfter = $this->rateLimiter->requestAvailableIn($this->config);\n\n $this->log->warning('[Hubspot] Rate limit exceeded, deferring request', [\n 'team_id' => $this->config->team_id,\n 'config_id' => $this->config->getId(),\n 'retry_after' => $retryAfter,\n ]);\n\n throw new RateLimitException(\n 'Hubspot rate limit reached for configuration ' . $this->config->getId(),\n $retryAfter,\n );\n }\n\n $this->rateLimiter->incrementRequestCount($this->config);\n\n try {\n return $apiCall();\n } catch (Throwable $e) {\n if ($this->isHubspotRateLimit($e)) {\n $retryAfter = $this->parseRetryAfter($e);\n\n $this->log->warning('[Hubspot] Received 429 from API', [\n 'team_id' => $this->config->team_id,\n 'config_id' => $this->config->getId(),\n 'retry_after' => $retryAfter,\n 'reason' => $e->getMessage(),\n ]);\n\n throw new RateLimitException('Hubspot returned 429', $retryAfter, $e);\n }\n\n throw $e;\n }\n }\n\n private function isHubspotRateLimit(Throwable $e): bool\n {\n return method_exists($e, 'getCode') && (int) $e->getCode() === 429;\n }\n\n private function parseRetryAfter(Throwable $e): int\n {\n if (method_exists($e, 'getResponseHeaders')) {\n $headers = $e->getResponseHeaders() ?: [];\n $value = $headers['Retry-After'] ?? $headers['retry-after'] ?? null;\n if (is_array($value)) {\n $value = $value[0] ?? null;\n }\n if (is_numeric($value)) {\n return (int) $value;\n }\n }\n\n return 10;\n }\n\n public function getMinimumApiVersion(): string\n {\n return self::MIN_API_VERSION;\n }\n\n public function getInstance(): Factory\n {\n return new Factory([\n 'key' => $this->accessToken,\n 'oauth2' => true,\n 'base_url' => $this->baseUrl,\n ]);\n }\n\n public function getNewInstance(): Discovery\n {\n return \\HubSpot\\Factory::createWithAccessToken($this->accessToken);\n }\n\n /**\n * Secondly and daily limits for Hubspot API\n *\n * Product Tier: Free & Starter | Professional & Enterprise | API add-on (any tier)\n * Burst: 100/10 seconds | 150/10 seconds | 200/10 seconds\n * Daily: 250,000 | 500,000 | 1,000,000\n *\n * Official documentation states: The search endpoints are rate limited to five requests per second.\n * Since with 5 RPS were still hitting secondly rate limits we lowered it to 4\n */\n public function getPaginatedData(array $payload, string $type, int $offset = 0): array\n {\n $total = 0;\n $lastId = null;\n $rows = [];\n foreach ($this->getPaginatedDataGenerator($payload, $type, $offset, $total, $lastId) as $row) {\n $rows[] = $row;\n }\n\n return ['results' => $rows, 'total' => $total, 'last_record' => $lastId];\n }\n\n /**\n * @throws HubspotException\n * @throws SocialAccountTokenInvalidException\n * @throws BadRequest\n */\n public function getPaginatedDataGenerator(\n array $payload,\n string $type,\n int $offset = 0,\n int &$total = 0,\n ?string &$lastRecordId = null\n ): \\Generator {\n return $this->paginationService->getPaginatedDataGenerator(\n $this,\n $payload,\n $type,\n $offset,\n $total,\n $lastRecordId\n );\n }\n\n /**\n * @throws DealApiException\n * @throws CrmException\n */\n public function getOpportunityById(string $crmId, array $fields): array\n {\n try {\n $deal = $this->executeRequest(fn () => $this->getNewInstance()->crm()->deals()->basicApi()->getById(\n $crmId,\n implode(',', $fields),\n 'companies,contacts'\n ));\n } catch (DealApiException $e) {\n $this->log->info('[Hubspot] Failed to fetch opportunity', [\n 'crm_id' => $crmId,\n 'reason' => $e->getMessage(),\n ]);\n\n throw $e;\n }\n\n if (! $deal instanceof DealWithAssociations) {\n throw new CrmException('Deal not found');\n }\n\n return [\n 'id' => $deal->getId(),\n 'properties' => $deal->getProperties(),\n 'associations' => $deal->getAssociations(),\n ];\n }\n\n /**\n * Generic batch read method for HubSpot objects\n *\n * @param string $objectType The object type ('deals', 'companies', 'contacts')\n * @param array<string> $crmIds Array of HubSpot object IDs (max 100)\n * @param array<string> $fields Array of property names to fetch\n *\n * @return array<string, array> Array keyed by CRM ID with object data\n */\n private function batchReadObjects(string $objectType, array $crmIds, array $fields): array\n {\n if (empty($crmIds)) {\n return [];\n }\n\n $this->validateBatchSize($objectType, $crmIds);\n $this->ensureValidToken();\n\n try {\n $batchConfig = $this->createBatchConfiguration($objectType);\n $batchReadRequest = $this->prepareBatchRequest($batchConfig, $crmIds, $fields);\n $response = $batchConfig['api']->read($batchReadRequest);\n\n $this->validateApiResponse($response, $objectType);\n\n $results = $this->processApiResults($response);\n $this->logBatchResults($objectType, $crmIds, $results);\n\n return $results;\n } catch (\\Throwable $e) {\n $this->handleBatchError($e, $objectType, $crmIds);\n }\n }\n\n private function validateBatchSize(string $objectType, array $crmIds): void\n {\n if (count($crmIds) > 100) {\n throw new \\InvalidArgumentException(\"Batch size cannot exceed 100 {$objectType}\");\n }\n }\n\n private function createBatchConfiguration(string $objectType): array\n {\n $configurations = [\n 'deals' => [\n 'batchReadRequest' => new \\HubSpot\\Client\\Crm\\Deals\\Model\\BatchReadInputSimplePublicObjectId(),\n 'inputClass' => \\HubSpot\\Client\\Crm\\Deals\\Model\\SimplePublicObjectId::class,\n 'api' => $this->getNewInstance()->crm()->deals()->batchApi(),\n ],\n 'companies' => [\n 'batchReadRequest' => new \\HubSpot\\Client\\Crm\\Companies\\Model\\BatchReadInputSimplePublicObjectId(),\n 'inputClass' => \\HubSpot\\Client\\Crm\\Companies\\Model\\SimplePublicObjectId::class,\n 'api' => $this->getNewInstance()->crm()->companies()->batchApi(),\n ],\n 'contacts' => [\n 'batchReadRequest' => new \\HubSpot\\Client\\Crm\\Contacts\\Model\\BatchReadInputSimplePublicObjectId(),\n 'inputClass' => \\HubSpot\\Client\\Crm\\Contacts\\Model\\SimplePublicObjectId::class,\n 'api' => $this->getNewInstance()->crm()->contacts()->batchApi(),\n ],\n ];\n\n if (! isset($configurations[$objectType])) {\n throw new \\InvalidArgumentException(\"Unsupported object type: {$objectType}\");\n }\n\n return $configurations[$objectType];\n }\n\n private function prepareBatchRequest(array $batchConfig, array $crmIds, array $fields): object\n {\n $batchReadRequest = $batchConfig['batchReadRequest'];\n $inputClass = $batchConfig['inputClass'];\n\n $inputs = array_map(function ($crmId) use ($inputClass) {\n $input = new $inputClass();\n $input->setId($crmId);\n\n return $input;\n }, $crmIds);\n\n $batchReadRequest->setInputs($inputs);\n $batchReadRequest->setProperties($fields);\n\n return $batchReadRequest;\n }\n\n private function validateApiResponse($response, string $objectType): void\n {\n if (! $response) {\n throw new CrmException(\"HubSpot API returned null response for {$objectType} batch read\");\n }\n }\n\n private function processApiResults($response): array\n {\n $results = [];\n $responseResults = $response->getResults();\n\n if ($responseResults) {\n foreach ($responseResults as $object) {\n if ($object && $object->getId()) {\n $results[$object->getId()] = [\n 'id' => $object->getId(),\n 'properties' => $object->getProperties() ?: [],\n ];\n }\n }\n }\n\n return $results;\n }\n\n private function logBatchResults(string $objectType, array $crmIds, array $results): void\n {\n $this->log->info(\"[HubSpot] Batch fetched {$objectType}\", [\n 'requested_count' => count($crmIds),\n 'returned_count' => count($results),\n 'crm_ids' => $crmIds,\n ]);\n }\n\n private function handleBatchError(\\Throwable $e, string $objectType, array $crmIds): void\n {\n $errorMessage = $e->getMessage() ?: 'Unknown error';\n $errorTrace = $e->getTraceAsString() ?: 'No trace available';\n\n $this->log->error(\"[HubSpot] Failed to batch fetch {$objectType}\", [\n 'crm_ids' => $crmIds,\n 'error' => $errorMessage,\n 'trace' => $errorTrace,\n ]);\n\n throw new CrmException(\"Failed to batch fetch {$objectType}: \" . $errorMessage);\n }\n\n /**\n * Batch read multiple opportunities by their CRM IDs\n *\n * @param array<string> $crmIds Array of HubSpot deal IDs (max 100)\n * @param array<string> $fields Array of property names to fetch\n *\n * @return array<string, array> Array keyed by CRM ID with opportunity data\n */\n public function getOpportunitiesByIds(array $crmIds, array $fields): array\n {\n return $this->batchReadObjects('deals', $crmIds, $fields);\n }\n\n /**\n * Batch read multiple companies by their CRM IDs\n *\n * @param array<string> $crmIds Array of HubSpot company IDs (max 100)\n * @param array<string> $fields Array of property names to fetch\n *\n * @return array<string, array> Array keyed by CRM ID with company data\n */\n public function getCompaniesByIds(array $crmIds, array $fields): array\n {\n return $this->batchReadObjects('companies', $crmIds, $fields);\n }\n\n /**\n * Batch read multiple contacts by their CRM IDs\n *\n * @param array<string> $crmIds Array of HubSpot contact IDs (max 100)\n * @param array<string> $fields Array of property names to fetch\n *\n * @return array<string, array> Array keyed by CRM ID with contact data\n */\n public function getContactsByIds(array $crmIds, array $fields): array\n {\n return $this->batchReadObjects('contacts', $crmIds, $fields);\n }\n\n /**\n * @throws CompanyApiException\n * @throws CrmException\n */\n public function getAccountById(string $crmId, array $fields): array\n {\n try {\n $company = $this->getNewInstance()->crm()->companies()->basicApi()->getById(\n $crmId,\n implode(',', $fields),\n );\n } catch (CompanyApiException $e) {\n $this->log->info('[Hubspot] Failed to fetch account', [\n 'crm_id' => $crmId,\n 'reason' => $e->getMessage(),\n ]);\n\n throw $e;\n }\n\n if (! $company instanceof CompaniesWithAssociations) {\n throw new CrmException('Account not found');\n }\n\n return [\n 'id' => $company->getId(),\n 'properties' => $company->getProperties(),\n ];\n }\n\n /**\n * @throws ContactApiException\n * @throws CrmException\n */\n public function getContactById(string $crmId, array $fields): array\n {\n try {\n $contact = $this->getNewInstance()->crm()->contacts()->basicApi()->getById(\n $crmId,\n implode(',', $fields)\n );\n } catch (ContactApiException $e) {\n $this->log->info('[Hubspot] Failed to fetch contact', [\n 'crm_id' => $crmId,\n 'reason' => $e->getMessage(),\n ]);\n\n throw $e;\n }\n\n if (! $contact instanceof ContactsWithAssociations) {\n throw new CrmException('Contact not found');\n }\n\n return [\n 'id' => $contact->getId(),\n 'properties' => $contact->getProperties(),\n ];\n }\n\n /**\n * This is email search request that Hubspot offers as GET (more generous quota)\n */\n public function getContactByEmail(string $email, array $fields = []): array\n {\n try {\n $contact = $this->getNewInstance()->crm()->contacts()->basicApi()->getById(\n $email,\n implode(',', $fields),\n null,\n false,\n 'email'\n );\n\n return [\n 'id' => $contact->getId(),\n 'properties' => $contact->getProperties(),\n ];\n } catch (ContactApiException $e) {\n $this->log->info('[Hubspot] Failed to fetch contact', [\n 'email' => $email,\n 'reason' => $e->getMessage(),\n ]);\n\n return [];\n }\n }\n\n /**\n * @throws CrmException\n */\n public function fetchProperty(string $objectType, string $propertyId): Property\n {\n $result = $this->getNewInstance()->crm()->properties()->coreApi()->getByName($objectType, $propertyId);\n\n if (! $result instanceof Property) {\n $this->log->error('[Hubspot] Failed to fetch property', [\n 'object_type' => $objectType,\n 'property_id' => $propertyId,\n 'reason' => $result->getMessage(),\n ]);\n\n throw new CrmException('Failed to fetch property');\n }\n\n return $result;\n }\n\n /**\n * @return array<CrmFieldOption>\n */\n public function fetchPropertyOptions(string $objectType, string $propertyId): array\n {\n /** @var array<CrmFieldOption> */\n return $this->fetchProperty($objectType, $propertyId)->getOptions();\n }\n\n /**\n * @return array<array{id:string, label:string, deleted:bool}>\n */\n public function fetchCallDispositions(): array\n {\n /** @var Response $response */\n $response = $this->getInstance()->engagements()->getCallDispositions();\n\n /**\n * @var array<array{\n * id:string,\n * label:string,\n * deleted: bool\n * }>\n */\n return $response->toArray();\n }\n\n /**\n * @return array<CrmFieldOption>\n */\n public function fetchOpportunityPipelineStages(): array\n {\n $stages = [];\n $apiResponse = $this->getNewInstance()->crm()->pipelines()->pipelinesApi()->getAll('deals');\n\n if ($apiResponse instanceof Error) {\n $this->log->error('[Hubspot] Failed to fetch opportunity pipelines', [\n 'reason' => $apiResponse->getMessage(),\n ]);\n\n return [];\n }\n\n foreach ($apiResponse->getResults() as $pipeline) {\n $pipelineStages = array_map(\n static function (PipelineStage $stage) {\n return [\n 'id' => $stage->getId(),\n 'label' => $stage->getLabel(),\n ];\n },\n $pipeline->getStages()\n );\n\n $stages = array_merge($stages, $pipelineStages);\n }\n\n return $stages;\n }\n\n public function fetchOpportunityPipelines(): array\n {\n $pipelines = [];\n\n try {\n $apiResponse = $this->makeRequest('/crm/v3/pipelines/deals');\n } catch (\\Exception $e) {\n $this->log->info('[Hubspot] Failed to fetch opportunity pipelines', [\n 'reason' => $e->getMessage(),\n ]);\n\n return [];\n }\n\n $response = $apiResponse->toArray();\n\n foreach ($response['results'] as $pipeline) {\n $pipelines[] = [\n 'id' => $pipeline['id'],\n 'label' => $pipeline['label'],\n ];\n }\n\n return $pipelines;\n }\n\n /**\n * @return array<CrmFieldOption>\n */\n public function fetchMeetingOutcomeFieldOptions(Field $field): array\n {\n return $field->getCrmProviderId() === 'meetingOutcome'\n ? $this->fetchMeetingOutcomeTypes()\n : $this->fetchCallActivityTypes();\n }\n\n public function fetchMeetingOutcomeTypes(): array\n {\n return $this->extractMeetingTypeOptions(\n 'https://api.hubapi.com/crm/v3/properties/meeting/hs_meeting_outcome'\n );\n }\n\n public function fetchCallActivityTypes(): array\n {\n return $this->extractMeetingTypeOptions(\n 'https://api.hubapi.com/crm/v3/properties/call/hs_activity_type'\n );\n }\n\n private function extractMeetingTypeOptions(string $endpoint): array\n {\n /** @var Response $response */\n $response = $this->getInstance()\n ->getClient()\n ->request('GET', $endpoint);\n\n /**\n * @var array<array{\n * value: string,\n * label: string,\n * displayOrder: int\n * }> $optionData\n */\n $optionData = $response->toArray()['options'] ?? [];\n\n $options = [];\n foreach ($optionData as $item) {\n $options[] = [\n 'id' => $item['value'],\n 'value' => $item['value'],\n 'label' => $item['label'],\n 'display_order' => $item['displayOrder'],\n ];\n }\n\n return $options;\n }\n\n /**\n * @return array<CrmFieldOption>\n */\n public function fetchDispositionFieldOptions(): array\n {\n $options = [];\n\n $dispositions = $this->fetchCallDispositions();\n\n foreach ($dispositions as $disposition) {\n if ($disposition['deleted'] !== false) {\n continue;\n }\n\n $option['value'] = $disposition['id'];\n $option['id'] = $disposition['id'];\n $option['label'] = $disposition['label'];\n\n $options[] = $option;\n }\n\n return $options;\n }\n\n /**\n * @return array<CrmFieldOption>\n */\n public function fetchOpportunityFieldOptions(Field $field): array\n {\n if ($field->isStageField()) {\n return $this->fetchOpportunityPipelineStages();\n }\n\n if ($field->isPipelineField()) {\n return $this->fetchOpportunityPipelines();\n }\n\n return $this->fetchPropertyOptions('deals', $field->getCrmProviderId());\n }\n\n /**\n * @throws BadRequest\n * @throws HubspotException\n */\n public function makeRequest(string $endpoint, $method = 'GET', $payload = [], ?string $queryString = null)\n {\n $endpoint = self::BASE_URL . $endpoint;\n\n if ($method === 'GET') {\n $response = $this->getInstance()->getClient()?->request(\n method: $method,\n endpoint: $endpoint,\n query_string: $queryString\n );\n } else {\n $response = $this->getInstance()->getClient()->request($method, $endpoint, [\n 'json' => ($payload),\n ]);\n }\n\n $max = $response->getHeaderLine('X-HubSpot-RateLimit-Max'); // \"110\"\n $remaining = $response->getHeaderLine('X-HubSpot-RateLimit-Remaining'); // \"109\"\n $interval = $response->getHeaderLine('X-HubSpot-RateLimit-Interval-Milliseconds'); // \"10000\"\n $body = json_decode((string) $response->getBody(), true);\n\n \\Illuminate\\Support\\Facades\\Log::channel('custom_channel')->info('$max ' . PHP_EOL . print_r($max, true));\n \\Illuminate\\Support\\Facades\\Log::channel('custom_channel')->info('$remaining ' . PHP_EOL . print_r($remaining, true));\n \\Illuminate\\Support\\Facades\\Log::channel('custom_channel')->info('$interval ' . PHP_EOL . print_r($interval, true));\n \\Illuminate\\Support\\Facades\\Log::channel('custom_channel')->info('$body ' . PHP_EOL . print_r($body, true));\n\n return $response;\n }\n\n /**\n * @throws BadRequest\n * @throws HubspotException\n */\n public function createMeeting(array $payload): Response\n {\n $endpoint = '/crm/v3/objects/meetings';\n\n return $this->makeRequest($endpoint, 'POST', $payload);\n }\n\n /**\n * @throws BadRequest\n * @throws HubspotException\n */\n public function updateMeeting(string $meetingId, array $payload): Response\n {\n $endpoint = '/crm/v3/objects/meetings/' . $meetingId;\n\n return $this->makeRequest($endpoint, 'PATCH', $payload);\n }\n\n /**\n * @throws \\Exception\n */\n public function createNote(\n string $body,\n string $ownerId,\n int $timestamp,\n string $objectId,\n NoteObject $noteObject\n ): ?string {\n try {\n $noteInput = new SimplePublicObjectInput([\n 'properties' => [\n 'hs_note_body' => $body,\n 'hubspot_owner_id' => $ownerId,\n 'hs_timestamp' => $timestamp,\n ],\n ]);\n\n // Create note\n $note = $this->getNewInstance()->crm()->objects()->basicApi()->create('note', $noteInput);\n\n $this->getNewInstance()->crm()->objects()->associationsApi()->create(\n 'note',\n $note->getId(),\n $this->getNoteObject($noteObject),\n $objectId,\n $this->getNoteAssociationType($noteObject),\n );\n\n return $note->getId();\n } catch (\\Exception $e) {\n $this->log->error('[Hubspot] Failed to create note', [\n 'objectId' => $objectId,\n 'noteObject' => $noteObject->getObjectType(),\n 'reason' => $e->getMessage(),\n ]);\n\n \\Sentry::captureException($e);\n }\n\n return null;\n }\n\n public function updateEngagement(string $objectId, array $engagement, array $metadata): void\n {\n $this->getInstance()->engagements()->update($objectId, $engagement, $metadata);\n }\n\n public function getEngagementData(string $engagementId): array\n {\n $engagement = $this->getInstance()->engagements()->get($engagementId);\n\n return $engagement->toArray();\n }\n\n public function createEngagement(array $engagement, array $associations, array $metadata): Response\n {\n return $this->getInstance()\n ->engagements()\n ->create($engagement, $associations, $metadata);\n }\n\n public function isUnauthorizedException(\\Exception $e): bool\n {\n // Check for specific HubSpot API exception types first\n if ($e instanceof BadRequest) {\n // BadRequest can contain 401 status codes\n return $e->getCode() === 401;\n }\n\n // Check for HTTP client exceptions with status codes\n if ($e instanceof \\GuzzleHttp\\Exception\\RequestException && $e->hasResponse()) {\n $response = $e->getResponse();\n if ($response !== null) {\n return $response->getStatusCode() === 401;\n }\n }\n\n // Check for Guzzle HTTP exceptions\n if ($e instanceof \\GuzzleHttp\\Exception\\ClientException) {\n return $e->getCode() === 401;\n }\n\n // Fallback to string matching as last resort, but be more specific\n $message = strtolower($e->getMessage());\n\n return str_contains($message, '401 unauthorized') ||\n str_contains($message, 'http 401') ||\n str_contains($message, 'status code 401') ||\n (preg_match('/\\b401\\b/', $message) && str_contains($message, 'unauthorized'));\n }\n\n /**\n * Validates and refreshes the access token if needed before API requests.\n * This ensures long-running processes don't fail due to token expiration.\n *\n * @throws SocialAccountTokenInvalidException\n */\n public function ensureValidToken(): void\n {\n if ($this->oauthAccount === null) {\n return;\n }\n\n $newToken = $this->tokenManager->ensureValidToken($this->oauthAccount);\n if ($newToken !== null) {\n $this->accessToken = $newToken;\n }\n }\n\n public function getConfig()\n {\n return $this->config;\n }\n\n // returns only active (archived=false)\n public function getOwners(): array\n {\n return $this->getNewInstance()->crm()->owners()->getAll();\n }\n\n /**\n * @param bool $archived\n *\n * @return array<Owner>|[]\n */\n public function getOwnersArchived(bool $archived = true): array\n {\n $endpoint = '/crm/v3/owners';\n $queryParams = [\n 'archived' => $archived ? 'true' : 'false',\n ];\n $queryString = http_build_query($queryParams);\n\n $owners = [];\n\n try {\n $response = $this->makeRequest(endpoint: $endpoint, queryString: $queryString);\n $responseData = $response?->toArray();\n\n foreach ($responseData['results'] as $result) {\n try {\n $owners[] = Owner::create($result);\n } catch (Throwable $e) {\n $this->log->error('[HubSpot] Failed to process owner data', [\n 'result' => $result,\n 'error' => $e->getMessage(),\n ]);\n\n continue;\n }\n }\n } catch (Throwable $e) {\n $this->log->error('HubSpot] Failed to fetch owners', [\n 'archived' => $archived,\n 'error' => $e->getMessage(),\n ]);\n\n return [];\n }\n\n return $owners;\n }\n\n public function getMeeting(string $engagementId): ObjectWithAssociations\n {\n return $this->getNewInstance()->crm()->objects()->basicApi()\n ->getById('meeting', $engagementId, null, 'contact,company,deal');\n }\n\n public function deleteEngagement(string $engagementId): void\n {\n $this->getInstance()->engagements()->delete((int) $engagementId);\n }\n\n public function getAssociationsData(array $ids, string $fromObject, string $toObject): array\n {\n $associationData = [];\n $idChunks = array_chunk($ids, self::ASSOCIATIONS_BATCH_SIZE_LIMIT);\n\n foreach ($idChunks as $idChunk) {\n try {\n $batchInput = new \\HubSpot\\Client\\Crm\\Associations\\Model\\BatchInputPublicObjectId();\n $batchInput->setInputs(array_map(function ($id) {\n $publicObjectId = new \\HubSpot\\Client\\Crm\\Associations\\Model\\PublicObjectId();\n $publicObjectId->setId($id);\n\n return $publicObjectId;\n }, $idChunk));\n\n $associatedObjectsData = $this\n ->getNewInstance()\n ->crm()\n ->associations()\n ->batchApi()\n ->read($fromObject, $toObject, $batchInput);\n\n if ($associatedObjectsData instanceof \\HubSpot\\Client\\Crm\\Associations\\Model\\BatchResponsePublicAssociationMulti) {\n foreach ($associatedObjectsData->getResults() as $association) {\n $from = $association->getFrom()->getId();\n $toAssociations = $association->getTo();\n\n if (! empty($toAssociations)) {\n $associationData[$from] = array_map(function ($item) {\n return $item->getId();\n }, $toAssociations);\n }\n }\n }\n } catch (\\Exception $e) {\n $this->log->error('[Hubspot] Failed to fetch associations', [\n 'from_object' => $fromObject,\n 'to_object' => $toObject,\n 'reason' => $e->getMessage(),\n ]);\n }\n }\n\n return $associationData;\n }\n\n /**\n * @throws \\Exception\n */\n private function getNoteAssociationType(NoteObject $noteObject): string\n {\n return match($noteObject) {\n NoteObject::Opportunity => 'note_to_deal',\n NoteObject::Lead, NoteObject::Contact => 'note_to_contact', // or 'note_to_lead' if your portal supports it\n NoteObject::Account => 'note_to_company',\n NoteObject::Call, NoteObject::Event => throw new \\Exception('Not supported'),\n };\n }\n\n /**\n * @throws \\Exception\n */\n private function getNoteObject(NoteObject $noteObject): string\n {\n return match($noteObject) {\n NoteObject::Opportunity => 'deal',\n NoteObject::Lead, NoteObject::Contact => 'contact',\n NoteObject::Account => 'company',\n NoteObject::Call, NoteObject::Event => throw new \\Exception('Not supported'),\n };\n }\n\n public function addAssociations(string $objectType, string $associationType, array $payload): Response\n {\n $endpoint = \"/crm/v4/associations/$objectType/$associationType/batch/create\";\n\n return $this->makeRequest($endpoint, 'POST', $payload);\n }\n\n public function removeAssociations(string $objectType, string $associationType, array $payload): Response\n {\n $endpoint = \"/crm/v4/associations/$objectType/$associationType/batch/archive\";\n\n return $this->makeRequest($endpoint, 'POST', $payload);\n }\n}","role_description":"text entry area","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"Project","depth":3,"on_screen":false,"role_description":"text"},{"role":"AXButton","text":"Project","depth":3,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"New File or Directory…","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.018055556,"height":0.026666667},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Expand Selected","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.018055556,"height":0.026666667},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Collapse All","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.018055556,"height":0.026666667},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Options","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.018055556,"height":0.026666667},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Hide","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.018055556,"height":0.026666667},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false}]...
|
9067406159853117642
|
6666989725209466980
|
click
|
accessibility
|
NULL
|
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
[2026-05-07 11:36:44] local.INFO: Jiminny\Console\Commands\Command::run Memory usage before starting command {"command":"crm:sync-opportunity","memoryBeforeCommandInMb":60.0,"memoryPeakBeforeCommandInMb":99.727} {"correlation_id":"462baf35-d312-4641-9896-e8b15e8fdb82","trace_id":"aeb070fe-166a-48bf-99c0-acfcbfb40c1b"}
[2026-05-07 11:36:44] local.INFO: [SocialAccountService] Fetching token {"socialAccountId":1499,"provider":"hubspot"} {"correlation_id":"462baf35-d312-4641-9896-e8b15e8fdb82","trace_id":"aeb070fe-166a-48bf-99c0-acfcbfb40c1b"}
[2026-05-07 11:36:44] local.INFO: [SocialAccountService] Token retrieved {"socialAccountId":1499,"provider":"hubspot"} {"correlation_id":"462baf35-d312-4641-9896-e8b15e8fdb82","trace_id":"aeb070fe-166a-48bf-99c0-acfcbfb40c1b"}
[2026-05-07 11:36:44] local.INFO: [EncryptedTokenManager] Generating access token. {"mode":"legacy"} {"correlation_id":"462baf35-d312-4641-9896-e8b15e8fdb82","trace_id":"aeb070fe-166a-48bf-99c0-acfcbfb40c1b"}
[2026-05-07 11:36:44] local.INFO: [HubSpot] Syncing opportunities using strategy: lastModified {"team":2} {"correlation_id":"462baf35-d312-4641-9896-e8b15e8fdb82","trace_id":"aeb070fe-166a-48bf-99c0-acfcbfb40c1b"}
[2026-05-07 11:36:45] local.INFO: [Hubspot] Pagination completed {"team_id":2,"endpoint":"[URL_WITH_CREDENTIALS] CrmFieldOption array{id:string, label:string, value?:string}
*/
class Client extends BaseClient implements HubspotClientInterface
{
public const string MIN_API_VERSION = '2';
public const string BASE_URL = '[URL_WITH_CREDENTIALS] T
* @param callable(): T $apiCall
* @return T
*
* @throws RateLimitException
*/
private function executeRequest(callable $apiCall)
{
if (! $this->rateLimiter->canMakeRequest($this->config)) {
$retryAfter = $this->rateLimiter->requestAvailableIn($this->config);
$this->log->warning('[Hubspot] Rate limit exceeded, deferring request', [
'team_id' => $this->config->team_id,
'config_id' => $this->config->getId(),
'retry_after' => $retryAfter,
]);
throw new RateLimitException(
'Hubspot rate limit reached for configuration ' . $this->config->getId(),
$retryAfter,
);
}
$this->rateLimiter->incrementRequestCount($this->config);
try {
return $apiCall();
} catch (Throwable $e) {
if ($this->isHubspotRateLimit($e)) {
$retryAfter = $this->parseRetryAfter($e);
$this->log->warning('[Hubspot] Received 429 from API', [
'team_id' => $this->config->team_id,
'config_id' => $this->config->getId(),
'retry_after' => $retryAfter,
'reason' => $e->getMessage(),
]);
throw new RateLimitException('Hubspot returned 429', $retryAfter, $e);
}
throw $e;
}
}
private function isHubspotRateLimit(Throwable $e): bool
{
return method_exists($e, 'getCode') && (int) $e->getCode() === 429;
}
private function parseRetryAfter(Throwable $e): int
{
if (method_exists($e, 'getResponseHeaders')) {
$headers = $e->getResponseHeaders() ?: [];
$value = $headers['Retry-After'] ?? $headers['retry-after'] ?? null;
if (is_array($value)) {
$value = $value[0] ?? null;
}
if (is_numeric($value)) {
return (int) $value;
}
}
return 10;
}
public function getMinimumApiVersion(): string
{
return self::MIN_API_VERSION;
}
public function getInstance(): Factory
{
return new Factory([
'key' => $this->accessToken,
'oauth2' => true,
'base_url' => $this->baseUrl,
]);
}
public function getNewInstance(): Discovery
{
return \HubSpot\Factory::createWithAccessToken($this->accessToken);
}
/**
* Secondly and daily limits for Hubspot API
*
* Product Tier: Free & Starter | Professional & Enterprise | API add-on (any tier)
* Burst: 100/10 seconds | 150/10 seconds | 200/10 seconds
* Daily: 250,000 | 500,000 | 1,000,000
*
* Official documentation states: The search endpoints are rate limited to five requests per second.
* Since with 5 RPS were still hitting secondly rate limits we lowered it to 4
*/
public function getPaginatedData(array $payload, string $type, int $offset = 0): array
{
$total = 0;
$lastId = null;
$rows = [];
foreach ($this->getPaginatedDataGenerator($payload, $type, $offset, $total, $lastId) as $row) {
$rows[] = $row;
}
return ['results' => $rows, 'total' => $total, 'last_record' => $lastId];
}
/**
* @throws HubspotException
* @throws SocialAccountTokenInvalidException
* @throws BadRequest
*/
public function getPaginatedDataGenerator(
array $payload,
string $type,
int $offset = 0,
int &$total = 0,
?string &$lastRecordId = null
): \Generator {
return $this->paginationService->getPaginatedDataGenerator(
$this,
$payload,
$type,
$offset,
$total,
$lastRecordId
);
}
/**
* @throws DealApiException
* @throws CrmException
*/
public function getOpportunityById(string $crmId, array $fields): array
{
try {
$deal = $this->executeRequest(fn () => $this->getNewInstance()->crm()->deals()->basicApi()->getById(
$crmId,
implode(',', $fields),
'companies,contacts'
));
} catch (DealApiException $e) {
$this->log->info('[Hubspot] Failed to fetch opportunity', [
'crm_id' => $crmId,
'reason' => $e->getMessage(),
]);
throw $e;
}
if (! $deal instanceof DealWithAssociations) {
throw new CrmException('Deal not found');
}
return [
'id' => $deal->getId(),
'properties' => $deal->getProperties(),
'associations' => $deal->getAssociations(),
];
}
/**
* Generic batch read method for HubSpot objects
*
* @param string $objectType The object type ('deals', 'companies', 'contacts')
* @param array<string> $crmIds Array of HubSpot object IDs (max 100)
* @param array<string> $fields Array of property names to fetch
*
* @return array<string, array> Array keyed by CRM ID with object data
*/
private function batchReadObjects(string $objectType, array $crmIds, array $fields): array
{
if (empty($crmIds)) {
return [];
}
$this->validateBatchSize($objectType, $crmIds);
$this->ensureValidToken();
try {
$batchConfig = $this->createBatchConfiguration($objectType);
$batchReadRequest = $this->prepareBatchRequest($batchConfig, $crmIds, $fields);
$response = $batchConfig['api']->read($batchReadRequest);
$this->validateApiResponse($response, $objectType);
$results = $this->processApiResults($response);
$this->logBatchResults($objectType, $crmIds, $results);
return $results;
} catch (\Throwable $e) {
$this->handleBatchError($e, $objectType, $crmIds);
}
}
private function validateBatchSize(string $objectType, array $crmIds): void
{
if (count($crmIds) > 100) {
throw new \InvalidArgumentException("Batch size cannot exceed 100 {$objectType}");
}
}
private function createBatchConfiguration(string $objectType): array
{
$configurations = [
'deals' => [
'batchReadRequest' => new \HubSpot\Client\Crm\Deals\Model\BatchReadInputSimplePublicObjectId(),
'inputClass' => \HubSpot\Client\Crm\Deals\Model\SimplePublicObjectId::class,
'api' => $this->getNewInstance()->crm()->deals()->batchApi(),
],
'companies' => [
'batchReadRequest' => new \HubSpot\Client\Crm\Companies\Model\BatchReadInputSimplePublicObjectId(),
'inputClass' => \HubSpot\Client\Crm\Companies\Model\SimplePublicObjectId::class,
'api' => $this->getNewInstance()->crm()->companies()->batchApi(),
],
'contacts' => [
'batchReadRequest' => new \HubSpot\Client\Crm\Contacts\Model\BatchReadInputSimplePublicObjectId(),
'inputClass' => \HubSpot\Client\Crm\Contacts\Model\SimplePublicObjectId::class,
'api' => $this->getNewInstance()->crm()->contacts()->batchApi(),
],
];
if (! isset($configurations[$objectType])) {
throw new \InvalidArgumentException("Unsupported object type: {$objectType}");
}
return $configurations[$objectType];
}
private function prepareBatchRequest(array $batchConfig, array $crmIds, array $fields): object
{
$batchReadRequest = $batchConfig['batchReadRequest'];
$inputClass = $batchConfig['inputClass'];
$inputs = array_map(function ($crmId) use ($inputClass) {
$input = new $inputClass();
$input->setId($crmId);
return $input;
}, $crmIds);
$batchReadRequest->setInputs($inputs);
$batchReadRequest->setProperties($fields);
return $batchReadRequest;
}
private function validateApiResponse($response, string $objectType): void
{
if (! $response) {
throw new CrmException("HubSpot API returned null response for {$objectType} batch read");
}
}
private function processApiResults($response): array
{
$results = [];
$responseResults = $response->getResults();
if ($responseResults) {
foreach ($responseResults as $object) {
if ($object && $object->getId()) {
$results[$object->getId()] = [
'id' => $object->getId(),
'properties' => $object->getProperties() ?: [],
];
}
}
}
return $results;
}
private function logBatchResults(string $objectType, array $crmIds, array $results): void
{
$this->log->info("[HubSpot] Batch fetched {$objectType}", [
'requested_count' => count($crmIds),
'returned_count' => count($results),
'crm_ids' => $crmIds,
]);
}
private function handleBatchError(\Throwable $e, string $objectType, array $crmIds): void
{
$errorMessage = $e->getMessage() ?: 'Unknown error';
$errorTrace = $e->getTraceAsString() ?: 'No trace available';
$this->log->error("[HubSpot] Failed to batch fetch {$objectType}", [
'crm_ids' => $crmIds,
'error' => $errorMessage,
'trace' => $errorTrace,
]);
throw new CrmException("Failed to batch fetch {$objectType}: " . $errorMessage);
}
/**
* Batch read multiple opportunities by their CRM IDs
*
* @param array<string> $crmIds Array of HubSpot deal IDs (max 100)
* @param array<string> $fields Array of property names to fetch
*
* @return array<string, array> Array keyed by CRM ID with opportunity data
*/
public function getOpportunitiesByIds(array $crmIds, array $fields): array
{
return $this->batchReadObjects('deals', $crmIds, $fields);
}
/**
* Batch read multiple companies by their CRM IDs
*
* @param array<string> $crmIds Array of HubSpot company IDs (max 100)
* @param array<string> $fields Array of property names to fetch
*
* @return array<string, array> Array keyed by CRM ID with company data
*/
public function getCompaniesByIds(array $crmIds, array $fields): array
{
return $this->batchReadObjects('companies', $crmIds, $fields);
}
/**
* Batch read multiple contacts by their CRM IDs
*
* @param array<string> $crmIds Array of HubSpot contact IDs (max 100)
* @param array<string> $fields Array of property names to fetch
*
* @return array<string, array> Array keyed by CRM ID with contact data
*/
public function getContactsByIds(array $crmIds, array $fields): array
{
return $this->batchReadObjects('contacts', $crmIds, $fields);
}
/**
* @throws CompanyApiException
* @throws CrmException
*/
public function getAccountById(string $crmId, array $fields): array
{
try {
$company = $this->getNewInstance()->crm()->companies()->basicApi()->getById(
$crmId,
implode(',', $fields),
);
} catch (CompanyApiException $e) {
$this->log->info('[Hubspot] Failed to fetch account', [
'crm_id' => $crmId,
'reason' => $e->getMessage(),
]);
throw $e;
}
if (! $company instanceof CompaniesWithAssociations) {
throw new CrmException('Account not found');
}
return [
'id' => $company->getId(),
'properties' => $company->getProperties(),
];
}
/**
* @throws ContactApiException
* @throws CrmException
*/
public function getContactById(string $crmId, array $fields): array
{
try {
$contact = $this->getNewInstance()->crm()->contacts()->basicApi()->getById(
$crmId,
implode(',', $fields)
);
} catch (ContactApiException $e) {
$this->log->info('[Hubspot] Failed to fetch contact', [
'crm_id' => $crmId,
'reason' => $e->getMessage(),
]);
throw $e;
}
if (! $contact instanceof ContactsWithAssociations) {
throw new CrmException('Contact not found');
}
return [
'id' => $contact->getId(),
'properties' => $contact->getProperties(),
];
}
/**
* This is email search request that Hubspot offers as GET (more generous quota)
*/
public function getContactByEmail(string $email, array $fields = []): array
{
try {
$contact = $this->getNewInstance()->crm()->contacts()->basicApi()->getById(
$email,
implode(',', $fields),
null,
false,
'email'
);
return [
'id' => $contact->getId(),
'properties' => $contact->getProperties(),
];
} catch (ContactApiException $e) {
$this->log->info('[Hubspot] Failed to fetch contact', [
'email' => $email,
'reason' => $e->getMessage(),
]);
return [];
}
}
/**
* @throws CrmException
*/
public function fetchProperty(string $objectType, string $propertyId): Property
{
$result = $this->getNewInstance()->crm()->properties()->coreApi()->getByName($objectType, $propertyId);
if (! $result instanceof Property) {
$this->log->error('[Hubspot] Failed to fetch property', [
'object_type' => $objectType,
'property_id' => $propertyId,
'reason' => $result->getMessage(),
]);
throw new CrmException('Failed to fetch property');
}
return $result;
}
/**
* @return array<CrmFieldOption>
*/
public function fetchPropertyOptions(string $objectType, string $propertyId): array
{
/** @var array<CrmFieldOption> */
return $this->fetchProperty($objectType, $propertyId)->getOptions();
}
/**
* @return array<array{id:string, label:string, deleted:bool}>
*/
public function fetchCallDispositions(): array
{
/** @var Response $response */
$response = $this->getInstance()->engagements()->getCallDispositions();
/**
* @var array<array{
* id:string,
* label:string,
* deleted: bool
* }>
*/
return $response->toArray();
}
/**
* @return array<CrmFieldOption>
*/
public function fetchOpportunityPipelineStages(): array
{
$stages = [];
$apiResponse = $this->getNewInstance()->crm()->pipelines()->pipelinesApi()->getAll('deals');
if ($apiResponse instanceof Error) {
$this->log->error('[Hubspot] Failed to fetch opportunity pipelines', [
'reason' => $apiResponse->getMessage(),
]);
return [];
}
foreach ($apiResponse->getResults() as $pipeline) {
$pipelineStages = array_map(
static function (PipelineStage $stage) {
return [
'id' => $stage->getId(),
'label' => $stage->getLabel(),
];
},
$pipeline->getStages()
);
$stages = array_merge($stages, $pipelineStages);
}
return $stages;
}
public function fetchOpportunityPipelines(): array
{
$pipelines = [];
try {
$apiResponse = $this->makeRequest('/crm/v3/pipelines/deals');
} catch (\Exception $e) {
$this->log->info('[Hubspot] Failed to fetch opportunity pipelines', [
'reason' => $e->getMessage(),
]);
return [];
}
$response = $apiResponse->toArray();
foreach ($response['results'] as $pipeline) {
$pipelines[] = [
'id' => $pipeline['id'],
'label' => $pipeline['label'],
];
}
return $pipelines;
}
/**
* @return array<CrmFieldOption>
*/
public function fetchMeetingOutcomeFieldOptions(Field $field): array
{
return $field->getCrmProviderId() === 'meetingOutcome'
? $this->fetchMeetingOutcomeTypes()
: $this->fetchCallActivityTypes();
}
public function fetchMeetingOutcomeTypes(): array
{
return $this->extractMeetingTypeOptions(
'[URL_WITH_CREDENTIALS] Response $response */
$response = $this->getInstance()
->getClient()
->request('GET', $endpoint);
/**
* @var array<array{
* value: string,
* label: string,
* displayOrder: int
* }> $optionData
*/
$optionData = $response->toArray()['options'] ?? [];
$options = [];
foreach ($optionData as $item) {
$options[] = [
'id' => $item['value'],
'value' => $item['value'],
'label' => $item['label'],
'display_order' => $item['displayOrder'],
];
}
return $options;
}
/**
* @return array<CrmFieldOption>
*/
public function fetchDispositionFieldOptions(): array
{
$options = [];
$dispositions = $this->fetchCallDispositions();
foreach ($dispositions as $disposition) {
if ($disposition['deleted'] !== false) {
continue;
}
$option['value'] = $disposition['id'];
$option['id'] = $disposition['id'];
$option['label'] = $disposition['label'];
$options[] = $option;
}
return $options;
}
/**
* @return array<CrmFieldOption>
*/
public function fetchOpportunityFieldOptions(Field $field): array
{
if ($field->isStageField()) {
return $this->fetchOpportunityPipelineStages();
}
if ($field->isPipelineField()) {
return $this->fetchOpportunityPipelines();
}
return $this->fetchPropertyOptions('deals', $field->getCrmProviderId());
}
/**
* @throws BadRequest
* @throws HubspotException
*/
public function makeRequest(string $endpoint, $method = 'GET', $payload = [], ?string $queryString = null)
{
$endpoint = self::BASE_URL . $endpoint;
if ($method === 'GET') {
$response = $this->getInstance()->getClient()?->request(
method: $method,
endpoint: $endpoint,
query_string: $queryString
);
} else {
$response = $this->getInstance()->getClient()->request($method, $endpoint, [
'json' => ($payload),
]);
}
$max = $response->getHeaderLine('X-HubSpot-RateLimit-Max'); // "110"
$remaining = $response->getHeaderLine('X-HubSpot-RateLimit-Remaining'); // "109"
$interval = $response->getHeaderLine('X-HubSpot-RateLimit-Interval-Milliseconds'); // "10000"
$body = json_decode((string) $response->getBody(), true);
\Illuminate\Support\Facades\Log::channel('custom_channel')->info('$max ' . PHP_EOL . print_r($max, true));
\Illuminate\Support\Facades\Log::channel('custom_channel')->info('$remaining ' . PHP_EOL . print_r($remaining, true));
\Illuminate\Support\Facades\Log::channel('custom_channel')->info('$interval ' . PHP_EOL . print_r($interval, true));
\Illuminate\Support\Facades\Log::channel('custom_channel')->info('$body ' . PHP_EOL . print_r($body, true));
return $response;
}
/**
* @throws BadRequest
* @throws HubspotException
*/
public function createMeeting(array $payload): Response
{
$endpoint = '/crm/v3/objects/meetings';
return $this->makeRequest($endpoint, 'POST', $payload);
}
/**
* @throws BadRequest
* @throws HubspotException
*/
public function updateMeeting(string $meetingId, array $payload): Response
{
$endpoint = '/crm/v3/objects/meetings/' . $meetingId;
return $this->makeRequest($endpoint, 'PATCH', $payload);
}
/**
* @throws \Exception
*/
public function createNote(
string $body,
string $ownerId,
int $timestamp,
string $objectId,
NoteObject $noteObject
): ?string {
try {
$noteInput = new SimplePublicObjectInput([
'properties' => [
'hs_note_body' => $body,
'hubspot_owner_id' => $ownerId,
'hs_timestamp' => $timestamp,
],
]);
// Create note
$note = $this->getNewInstance()->crm()->objects()->basicApi()->create('note', $noteInput);
$this->getNewInstance()->crm()->objects()->associationsApi()->create(
'note',
$note->getId(),
$this->getNoteObject($noteObject),
$objectId,
$this->getNoteAssociationType($noteObject),
);
return $note->getId();
} catch (\Exception $e) {
$this->log->error('[Hubspot] Failed to create note', [
'objectId' => $objectId,
'noteObject' => $noteObject->getObjectType(),
'reason' => $e->getMessage(),
]);
\Sentry::captureException($e);
}
return null;
}
public function updateEngagement(string $objectId, array $engagement, array $metadata): void
{
$this->getInstance()->engagements()->update($objectId, $engagement, $metadata);
}
public function getEngagementData(string $engagementId): array
{
$engagement = $this->getInstance()->engagements()->get($engagementId);
return $engagement->toArray();
}
public function createEngagement(array $engagement, array $associations, array $metadata): Response
{
return $this->getInstance()
->engagements()
->create($engagement, $associations, $metadata);
}
public function isUnauthorizedException(\Exception $e): bool
{
// Check for specific HubSpot API exception types first
if ($e instanceof BadRequest) {
// BadRequest can contain 401 status codes
return $e->getCode() === 401;
}
// Check for HTTP client exceptions with status codes
if ($e instanceof \GuzzleHttp\Exception\RequestException && $e->hasResponse()) {
$response = $e->getResponse();
if ($response !== null) {
return $response->getStatusCode() === 401;
}
}
// Check for Guzzle HTTP exceptions
if ($e instanceof \GuzzleHttp\Exception\ClientException) {
return $e->getCode() === 401;
}
// Fallback to string matching as last resort, but be more specific
$message = strtolower($e->getMessage());
return str_contains($message, '401 unauthorized') ||
str_contains($message, 'http 401') ||
str_contains($message, 'status code 401') ||
(preg_match('/\b401\b/', $message) && str_contains($message, 'unauthorized'));
}
/**
* Validates and refreshes the access token if needed before API requests.
* This ensures long-running processes don't fail due to token expiration.
*
* @throws SocialAccountTokenInvalidException
*/
public function ensureValidToken(): void
{
if ($this->oauthAccount === null) {
return;
}
$newToken = $this->tokenManager->ensureValidToken($this->oauthAccount);
if ($newToken !== null) {
$this->accessToken = $newToken;
}
}
public function getConfig()
{
return $this->config;
}
// returns only active (archived=false)
public function getOwners(): array
{
return $this->getNewInstance()->crm()->owners()->getAll();
}
/**
* @param bool $archived
*
* @return array<Owner>|[]
*/
public function getOwnersArchived(bool $archived = true): array
{
$endpoint = '/crm/v3/owners';
$queryParams = [
'archived' => $archived ? 'true' : 'false',
];
$queryString = http_build_query($queryParams);
$owners = [];
try {
$response = $this->makeRequest(endpoint: $endpoint, queryString: $queryString);
$responseData = $response?->toArray();
foreach ($responseData['results'] as $result) {
try {
$owners[] = Owner::create($result);
} catch (Throwable $e) {
$this->log->error('[HubSpot] Failed to process owner data', [
'result' => $result,
'error' => $e->getMessage(),
]);
continue;
}
}
} catch (Throwable $e) {
$this->log->error('HubSpot] Failed to fetch owners', [
'archived' => $archived,
'error' => $e->getMessage(),
]);
return [];
}
return $owners;
}
public function getMeeting(string $engagementId): ObjectWithAssociations
{
return $this->getNewInstance()->crm()->objects()->basicApi()
->getById('meeting', $engagementId, null, 'contact,company,deal');
}
public function deleteEngagement(string $engagementId): void
{
$this->getInstance()->engagements()->delete((int) $engagementId);
}
public function getAssociationsData(array $ids, string $fromObject, string $toObject): array
{
$associationData = [];
$idChunks = array_chunk($ids, self::ASSOCIATIONS_BATCH_SIZE_LIMIT);
foreach ($idChunks as $idChunk) {
try {
$batchInput = new \HubSpot\Client\Crm\Associations\Model\BatchInputPublicObjectId();
$batchInput->setInputs(array_map(function ($id) {
$publicObjectId = new \HubSpot\Client\Crm\Associations\Model\PublicObjectId();
$publicObjectId->setId($id);
return $publicObjectId;
}, $idChunk));
$associatedObjectsData = $this
->getNewInstance()
->crm()
->associations()
->batchApi()
->read($fromObject, $toObject, $batchInput);
if ($associatedObjectsData instanceof \HubSpot\Client\Crm\Associations\Model\BatchResponsePublicAssociationMulti) {
foreach ($associatedObjectsData->getResults() as $association) {
$from = $association->getFrom()->getId();
$toAssociations = $association->getTo();
if (! empty($toAssociations)) {
$associationData[$from] = array_map(function ($item) {
return $item->getId();
}, $toAssociations);
}
}
}
} catch (\Exception $e) {
$this->log->error('[Hubspot] Failed to fetch associations', [
'from_object' => $fromObject,
'to_object' => $toObject,
'reason' => $e->getMessage(),
]);
}
}
return $associationData;
}
/**
* @throws \Exception
*/
private function getNoteAssociationType(NoteObject $noteObject): string
{
return match($noteObject) {
NoteObject::Opportunity => 'note_to_deal',
NoteObject::Lead, NoteObject::Contact => 'note_to_contact', // or 'note_to_lead' if your portal supports it
NoteObject::Account => 'note_to_company',
NoteObject::Call, NoteObject::Event => throw new \Exception('Not supported'),
};
}
/**
* @throws \Exception
*/
private function getNoteObject(NoteObject $noteObject): string
{
return match($noteObject) {
NoteObject::Opportunity => 'deal',
NoteObject::Lead, NoteObject::Contact => 'contact',
NoteObject::Account => 'company',
NoteObject::Call, NoteObject::Event => throw new \Exception('Not supported'),
};
}
public function addAssociations(string $objectType, string $associationType, array $payload): Response
{
$endpoint = "/crm/v4/associations/$objectType/$associationType/batch/create";
return $this->makeRequest($endpoint, 'POST', $payload);
}
public function removeAssociations(string $objectType, string $associationType, array $payload): Response
{
$endpoint = "/crm/v4/associations/$objectType/$associationType/batch/archive";
return $this->makeRequest($endpoint, 'POST', $payload);
}
}
Project
Project
New File or Directory…
Expand Selected
Collapse All
Options
Hide...
|
2736
|
NULL
|
NULL
|
NULL
|
|
2739
|
112
|
41
|
2026-05-07T11:38:04.426350+00:00
|
/Users/lukas/.screenpipe/data/data/2026-05-07/1778 /Users/lukas/.screenpipe/data/data/2026-05-07/1778153884426_m2.jpg...
|
PhpStorm
|
faVsco.js – laravel.log
|
True
|
NULL
|
monitor_2
|
NULL
|
NULL
|
NULL
|
NULL
|
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
[2026-05-07 11:36:44] local.INFO: Jiminny\Console\Commands\Command::run Memory usage before starting command {"command":"crm:sync-opportunity","memoryBeforeCommandInMb":60.0,"memoryPeakBeforeCommandInMb":99.727} {"correlation_id":"462baf35-d312-4641-9896-e8b15e8fdb82","trace_id":"aeb070fe-166a-48bf-99c0-acfcbfb40c1b"}
[2026-05-07 11:36:44] local.INFO: [SocialAccountService] Fetching token {"socialAccountId":1499,"provider":"hubspot"} {"correlation_id":"462baf35-d312-4641-9896-e8b15e8fdb82","trace_id":"aeb070fe-166a-48bf-99c0-acfcbfb40c1b"}
[2026-05-07 11:36:44] local.INFO: [SocialAccountService] Token retrieved {"socialAccountId":1499,"provider":"hubspot"} {"correlation_id":"462baf35-d312-4641-9896-e8b15e8fdb82","trace_id":"aeb070fe-166a-48bf-99c0-acfcbfb40c1b"}
[2026-05-07 11:36:44] local.INFO: [EncryptedTokenManager] Generating access token. {"mode":"legacy"} {"correlation_id":"462baf35-d312-4641-9896-e8b15e8fdb82","trace_id":"aeb070fe-166a-48bf-99c0-acfcbfb40c1b"}
[2026-05-07 11:36:44] local.INFO: [HubSpot] Syncing opportunities using strategy: lastModified {"team":2} {"correlation_id":"462baf35-d312-4641-9896-e8b15e8fdb82","trace_id":"aeb070fe-166a-48bf-99c0-acfcbfb40c1b"}
[2026-05-07 11:36:45] local.INFO: [Hubspot] Pagination completed {"team_id":2,"endpoint":"[URL_WITH_CREDENTIALS] CrmFieldOption array{id:string, label:string, value?:string}
*/
class Client extends BaseClient implements HubspotClientInterface
{
public const string MIN_API_VERSION = '2';
public const string BASE_URL = '[URL_WITH_CREDENTIALS] T
* @param callable(): T $apiCall
* @return T
*
* @throws RateLimitException
*/
private function executeRequest(callable $apiCall)
{
if (! $this->rateLimiter->canMakeRequest($this->config)) {
$retryAfter = $this->rateLimiter->requestAvailableIn($this->config);
$this->log->warning('[Hubspot] Rate limit exceeded, deferring request', [
'team_id' => $this->config->team_id,
'config_id' => $this->config->getId(),
'retry_after' => $retryAfter,
]);
throw new RateLimitException(
'Hubspot rate limit reached for configuration ' . $this->config->getId(),
$retryAfter,
);
}
$this->rateLimiter->incrementRequestCount($this->config);
try {
return $apiCall();
} catch (Throwable $e) {
if ($this->isHubspotRateLimit($e)) {
$retryAfter = $this->parseRetryAfter($e);
$this->log->warning('[Hubspot] Received 429 from API', [
'team_id' => $this->config->team_id,
'config_id' => $this->config->getId(),
'retry_after' => $retryAfter,
'reason' => $e->getMessage(),
]);
throw new RateLimitException('Hubspot returned 429', $retryAfter, $e);
}
throw $e;
}
}
private function isHubspotRateLimit(Throwable $e): bool
{
return method_exists($e, 'getCode') && (int) $e->getCode() === 429;
}
private function parseRetryAfter(Throwable $e): int
{
if (method_exists($e, 'getResponseHeaders')) {
$headers = $e->getResponseHeaders() ?: [];
$value = $headers['Retry-After'] ?? $headers['retry-after'] ?? null;
if (is_array($value)) {
$value = $value[0] ?? null;
}
if (is_numeric($value)) {
return (int) $value;
}
}
return 10;
}
public function getMinimumApiVersion(): string
{
return self::MIN_API_VERSION;
}
public function getInstance(): Factory
{
return new Factory([
'key' => $this->accessToken,
'oauth2' => true,
'base_url' => $this->baseUrl,
]);
}
public function getNewInstance(): Discovery
{
return \HubSpot\Factory::createWithAccessToken($this->accessToken);
}
/**
* Secondly and daily limits for Hubspot API
*
* Product Tier: Free & Starter | Professional & Enterprise | API add-on (any tier)
* Burst: 100/10 seconds | 150/10 seconds | 200/10 seconds
* Daily: 250,000 | 500,000 | 1,000,000
*
* Official documentation states: The search endpoints are rate limited to five requests per second.
* Since with 5 RPS were still hitting secondly rate limits we lowered it to 4
*/
public function getPaginatedData(array $payload, string $type, int $offset = 0): array
{
$total = 0;
$lastId = null;
$rows = [];
foreach ($this->getPaginatedDataGenerator($payload, $type, $offset, $total, $lastId) as $row) {
$rows[] = $row;
}
return ['results' => $rows, 'total' => $total, 'last_record' => $lastId];
}
/**
* @throws HubspotException
* @throws SocialAccountTokenInvalidException
* @throws BadRequest
*/
public function getPaginatedDataGenerator(
array $payload,
string $type,
int $offset = 0,
int &$total = 0,
?string &$lastRecordId = null
): \Generator {
return $this->paginationService->getPaginatedDataGenerator(
$this,
$payload,
$type,
$offset,
$total,
$lastRecordId
);
}
/**
* @throws DealApiException
* @throws CrmException
*/
public function getOpportunityById(string $crmId, array $fields): array
{
try {
$deal = $this->executeRequest(fn () => $this->getNewInstance()->crm()->deals()->basicApi()->getById(
$crmId,
implode(',', $fields),
'companies,contacts'
));
} catch (DealApiException $e) {
$this->log->info('[Hubspot] Failed to fetch opportunity', [
'crm_id' => $crmId,
'reason' => $e->getMessage(),
]);
throw $e;
}
if (! $deal instanceof DealWithAssociations) {
throw new CrmException('Deal not found');
}
return [
'id' => $deal->getId(),
'properties' => $deal->getProperties(),
'associations' => $deal->getAssociations(),
];
}
/**
* Generic batch read method for HubSpot objects
*
* @param string $objectType The object type ('deals', 'companies', 'contacts')
* @param array<string> $crmIds Array of HubSpot object IDs (max 100)
* @param array<string> $fields Array of property names to fetch
*
* @return array<string, array> Array keyed by CRM ID with object data
*/
private function batchReadObjects(string $objectType, array $crmIds, array $fields): array
{
if (empty($crmIds)) {
return [];
}
$this->validateBatchSize($objectType, $crmIds);
$this->ensureValidToken();
try {
$batchConfig = $this->createBatchConfiguration($objectType);
$batchReadRequest = $this->prepareBatchRequest($batchConfig, $crmIds, $fields);
$response = $batchConfig['api']->read($batchReadRequest);
$this->validateApiResponse($response, $objectType);
$results = $this->processApiResults($response);
$this->logBatchResults($objectType, $crmIds, $results);
return $results;
} catch (\Throwable $e) {
$this->handleBatchError($e, $objectType, $crmIds);
}
}
private function validateBatchSize(string $objectType, array $crmIds): void
{
if (count($crmIds) > 100) {
throw new \InvalidArgumentException("Batch size cannot exceed 100 {$objectType}");
}
}
private function createBatchConfiguration(string $objectType): array
{
$configurations = [
'deals' => [
'batchReadRequest' => new \HubSpot\Client\Crm\Deals\Model\BatchReadInputSimplePublicObjectId(),
'inputClass' => \HubSpot\Client\Crm\Deals\Model\SimplePublicObjectId::class,
'api' => $this->getNewInstance()->crm()->deals()->batchApi(),
],
'companies' => [
'batchReadRequest' => new \HubSpot\Client\Crm\Companies\Model\BatchReadInputSimplePublicObjectId(),
'inputClass' => \HubSpot\Client\Crm\Companies\Model\SimplePublicObjectId::class,
'api' => $this->getNewInstance()->crm()->companies()->batchApi(),
],
'contacts' => [
'batchReadRequest' => new \HubSpot\Client\Crm\Contacts\Model\BatchReadInputSimplePublicObjectId(),
'inputClass' => \HubSpot\Client\Crm\Contacts\Model\SimplePublicObjectId::class,
'api' => $this->getNewInstance()->crm()->contacts()->batchApi(),
],
];
if (! isset($configurations[$objectType])) {
throw new \InvalidArgumentException("Unsupported object type: {$objectType}");
}
return $configurations[$objectType];
}
private function prepareBatchRequest(array $batchConfig, array $crmIds, array $fields): object
{
$batchReadRequest = $batchConfig['batchReadRequest'];
$inputClass = $batchConfig['inputClass'];
$inputs = array_map(function ($crmId) use ($inputClass) {
$input = new $inputClass();
$input->setId($crmId);
return $input;
}, $crmIds);
$batchReadRequest->setInputs($inputs);
$batchReadRequest->setProperties($fields);
return $batchReadRequest;
}
private function validateApiResponse($response, string $objectType): void
{
if (! $response) {
throw new CrmException("HubSpot API returned null response for {$objectType} batch read");
}
}
private function processApiResults($response): array
{
$results = [];
$responseResults = $response->getResults();
if ($responseResults) {
foreach ($responseResults as $object) {
if ($object && $object->getId()) {
$results[$object->getId()] = [
'id' => $object->getId(),
'properties' => $object->getProperties() ?: [],
];
}
}
}
return $results;
}
private function logBatchResults(string $objectType, array $crmIds, array $results): void
{
$this->log->info("[HubSpot] Batch fetched {$objectType}", [
'requested_count' => count($crmIds),
'returned_count' => count($results),
'crm_ids' => $crmIds,
]);
}
private function handleBatchError(\Throwable $e, string $objectType, array $crmIds): void
{
$errorMessage = $e->getMessage() ?: 'Unknown error';
$errorTrace = $e->getTraceAsString() ?: 'No trace available';
$this->log->error("[HubSpot] Failed to batch fetch {$objectType}", [
'crm_ids' => $crmIds,
'error' => $errorMessage,
'trace' => $errorTrace,
]);
throw new CrmException("Failed to batch fetch {$objectType}: " . $errorMessage);
}
/**
* Batch read multiple opportunities by their CRM IDs
*
* @param array<string> $crmIds Array of HubSpot deal IDs (max 100)
* @param array<string> $fields Array of property names to fetch
*
* @return array<string, array> Array keyed by CRM ID with opportunity data
*/
public function getOpportunitiesByIds(array $crmIds, array $fields): array
{
return $this->batchReadObjects('deals', $crmIds, $fields);
}
/**
* Batch read multiple companies by their CRM IDs
*
* @param array<string> $crmIds Array of HubSpot company IDs (max 100)
* @param array<string> $fields Array of property names to fetch
*
* @return array<string, array> Array keyed by CRM ID with company data
*/
public function getCompaniesByIds(array $crmIds, array $fields): array
{
return $this->batchReadObjects('companies', $crmIds, $fields);
}
/**
* Batch read multiple contacts by their CRM IDs
*
* @param array<string> $crmIds Array of HubSpot contact IDs (max 100)
* @param array<string> $fields Array of property names to fetch
*
* @return array<string, array> Array keyed by CRM ID with contact data
*/
public function getContactsByIds(array $crmIds, array $fields): array
{
return $this->batchReadObjects('contacts', $crmIds, $fields);
}
/**
* @throws CompanyApiException
* @throws CrmException
*/
public function getAccountById(string $crmId, array $fields): array
{
try {
$company = $this->getNewInstance()->crm()->companies()->basicApi()->getById(
$crmId,
implode(',', $fields),
);
} catch (CompanyApiException $e) {
$this->log->info('[Hubspot] Failed to fetch account', [
'crm_id' => $crmId,
'reason' => $e->getMessage(),
]);
throw $e;
}
if (! $company instanceof CompaniesWithAssociations) {
throw new CrmException('Account not found');
}
return [
'id' => $company->getId(),
'properties' => $company->getProperties(),
];
}
/**
* @throws ContactApiException
* @throws CrmException
*/
public function getContactById(string $crmId, array $fields): array
{
try {
$contact = $this->getNewInstance()->crm()->contacts()->basicApi()->getById(
$crmId,
implode(',', $fields)
);
} catch (ContactApiException $e) {
$this->log->info('[Hubspot] Failed to fetch contact', [
'crm_id' => $crmId,
'reason' => $e->getMessage(),
]);
throw $e;
}
if (! $contact instanceof ContactsWithAssociations) {
throw new CrmException('Contact not found');
}
return [
'id' => $contact->getId(),
'properties' => $contact->getProperties(),
];
}
/**
* This is email search request that Hubspot offers as GET (more generous quota)
*/
public function getContactByEmail(string $email, array $fields = []): array
{
try {
$contact = $this->getNewInstance()->crm()->contacts()->basicApi()->getById(
$email,
implode(',', $fields),
null,
false,
'email'
);
return [
'id' => $contact->getId(),
'properties' => $contact->getProperties(),
];
} catch (ContactApiException $e) {
$this->log->info('[Hubspot] Failed to fetch contact', [
'email' => $email,
'reason' => $e->getMessage(),
]);
return [];
}
}
/**
* @throws CrmException
*/
public function fetchProperty(string $objectType, string $propertyId): Property
{
$result = $this->getNewInstance()->crm()->properties()->coreApi()->getByName($objectType, $propertyId);
if (! $result instanceof Property) {
$this->log->error('[Hubspot] Failed to fetch property', [
'object_type' => $objectType,
'property_id' => $propertyId,
'reason' => $result->getMessage(),
]);
throw new CrmException('Failed to fetch property');
}
return $result;
}
/**
* @return array<CrmFieldOption>
*/
public function fetchPropertyOptions(string $objectType, string $propertyId): array
{
/** @var array<CrmFieldOption> */
return $this->fetchProperty($objectType, $propertyId)->getOptions();
}
/**
* @return array<array{id:string, label:string, deleted:bool}>
*/
public function fetchCallDispositions(): array
{
/** @var Response $response */
$response = $this->getInstance()->engagements()->getCallDispositions();
/**
* @var array<array{
* id:string,
* label:string,
* deleted: bool
* }>
*/
return $response->toArray();
}
/**
* @return array<CrmFieldOption>
*/
public function fetchOpportunityPipelineStages(): array
{
$stages = [];
$apiResponse = $this->getNewInstance()->crm()->pipelines()->pipelinesApi()->getAll('deals');
if ($apiResponse instanceof Error) {
$this->log->error('[Hubspot] Failed to fetch opportunity pipelines', [
'reason' => $apiResponse->getMessage(),
]);
return [];
}
foreach ($apiResponse->getResults() as $pipeline) {
$pipelineStages = array_map(
static function (PipelineStage $stage) {
return [
'id' => $stage->getId(),
'label' => $stage->getLabel(),
];
},
$pipeline->getStages()
);
$stages = array_merge($stages, $pipelineStages);
}
return $stages;
}
public function fetchOpportunityPipelines(): array
{
$pipelines = [];
try {
$apiResponse = $this->makeRequest('/crm/v3/pipelines/deals');
} catch (\Exception $e) {
$this->log->info('[Hubspot] Failed to fetch opportunity pipelines', [
'reason' => $e->getMessage(),
]);
return [];
}
$response = $apiResponse->toArray();
foreach ($response['results'] as $pipeline) {
$pipelines[] = [
'id' => $pipeline['id'],
'label' => $pipeline['label'],
];
}
return $pipelines;
}
/**
* @return array<CrmFieldOption>
*/
public function fetchMeetingOutcomeFieldOptions(Field $field): array
{
return $field->getCrmProviderId() === 'meetingOutcome'
? $this->fetchMeetingOutcomeTypes()
: $this->fetchCallActivityTypes();
}
public function fetchMeetingOutcomeTypes(): array
{
return $this->extractMeetingTypeOptions(
'[URL_WITH_CREDENTIALS] Response $response */
$response = $this->getInstance()
->getClient()
->request('GET', $endpoint);
/**
* @var array<array{
* value: string,
* label: string,
* displayOrder: int
* }> $optionData
*/
$optionData = $response->toArray()['options'] ?? [];
$options = [];
foreach ($optionData as $item) {
$options[] = [
'id' => $item['value'],
'value' => $item['value'],
'label' => $item['label'],
'display_order' => $item['displayOrder'],
];
}
return $options;
}
/**
* @return array<CrmFieldOption>
*/
public function fetchDispositionFieldOptions(): array
{
$options = [];
$dispositions = $this->fetchCallDispositions();
foreach ($dispositions as $disposition) {
if ($disposition['deleted'] !== false) {
continue;
}
$option['value'] = $disposition['id'];
$option['id'] = $disposition['id'];
$option['label'] = $disposition['label'];
$options[] = $option;
}
return $options;
}
/**
* @return array<CrmFieldOption>
*/
public function fetchOpportunityFieldOptions(Field $field): array
{
if ($field->isStageField()) {
return $this->fetchOpportunityPipelineStages();
}
if ($field->isPipelineField()) {
return $this->fetchOpportunityPipelines();
}
return $this->fetchPropertyOptions('deals', $field->getCrmProviderId());
}
/**
* @throws BadRequest
* @throws HubspotException
*/
public function makeRequest(string $endpoint, $method = 'GET', $payload = [], ?string $queryString = null)
{
$endpoint = self::BASE_URL . $endpoint;
if ($method === 'GET') {
$response = $this->getInstance()->getClient()?->request(
method: $method,
endpoint: $endpoint,
query_string: $queryString
);
} else {
$response = $this->getInstance()->getClient()->request($method, $endpoint, [
'json' => ($payload),
]);
}
$max = $response->getHeaderLine('X-HubSpot-RateLimit-Max'); // "110"
$remaining = $response->getHeaderLine('X-HubSpot-RateLimit-Remaining'); // "109"
$interval = $response->getHeaderLine('X-HubSpot-RateLimit-Interval-Milliseconds'); // "10000"
$body = json_decode((string) $response->getBody(), true);
\Illuminate\Support\Facades\Log::channel('custom_channel')->info('$max ' . PHP_EOL . print_r($max, true));
\Illuminate\Support\Facades\Log::channel('custom_channel')->info('$remaining ' . PHP_EOL . print_r($remaining, true));
\Illuminate\Support\Facades\Log::channel('custom_channel')->info('$interval ' . PHP_EOL . print_r($interval, true));
\Illuminate\Support\Facades\Log::channel('custom_channel')->info('$body ' . PHP_EOL . print_r($body, true));
return $response;
}
/**
* @throws BadRequest
* @throws HubspotException
*/
public function createMeeting(array $payload): Response
{
$endpoint = '/crm/v3/objects/meetings';
return $this->makeRequest($endpoint, 'POST', $payload);
}
/**
* @throws BadRequest
* @throws HubspotException
*/
public function updateMeeting(string $meetingId, array $payload): Response
{
$endpoint = '/crm/v3/objects/meetings/' . $meetingId;
return $this->makeRequest($endpoint, 'PATCH', $payload);
}
/**
* @throws \Exception
*/
public function createNote(
string $body,
string $ownerId,
int $timestamp,
string $objectId,
NoteObject $noteObject
): ?string {
try {
$noteInput = new SimplePublicObjectInput([
'properties' => [
'hs_note_body' => $body,
'hubspot_owner_id' => $ownerId,
'hs_timestamp' => $timestamp,
],
]);
// Create note
$note = $this->getNewInstance()->crm()->objects()->basicApi()->create('note', $noteInput);
$this->getNewInstance()->crm()->objects()->associationsApi()->create(
'note',
$note->getId(),
$this->getNoteObject($noteObject),
$objectId,
$this->getNoteAssociationType($noteObject),
);
return $note->getId();
} catch (\Exception $e) {
$this->log->error('[Hubspot] Failed to create note', [
'objectId' => $objectId,
'noteObject' => $noteObject->getObjectType(),
'reason' => $e->getMessage(),
]);
\Sentry::captureException($e);
}
return null;
}
public function updateEngagement(string $objectId, array $engagement, array $metadata): void
{
$this->getInstance()->engagements()->update($objectId, $engagement, $metadata);
}
public function getEngagementData(string $engagementId): array
{
$engagement = $this->getInstance()->engagements()->get($engagementId);
return $engagement->toArray();
}
public function createEngagement(array $engagement, array $associations, array $metadata): Response
{
return $this->getInstance()
->engagements()
->create($engagement, $associations, $metadata);
}
public function isUnauthorizedException(\Exception $e): bool
{
// Check for specific HubSpot API exception types first
if ($e instanceof BadRequest) {
// BadRequest can contain 401 status codes
return $e->getCode() === 401;
}
// Check for HTTP client exceptions with status codes
if ($e instanceof \GuzzleHttp\Exception\RequestException && $e->hasResponse()) {
$response = $e->getResponse();
if ($response !== null) {
return $response->getStatusCode() === 401;
}
}
// Check for Guzzle HTTP exceptions
if ($e instanceof \GuzzleHttp\Exception\ClientException) {
return $e->getCode() === 401;
}
// Fallback to string matching as last resort, but be more specific
$message = strtolower($e->getMessage());
return str_contains($message, '401 unauthorized') ||
str_contains($message, 'http 401') ||
str_contains($message, 'status code 401') ||
(preg_match('/\b401\b/', $message) && str_contains($message, 'unauthorized'));
}
/**
* Validates and refreshes the access token if needed before API requests.
* This ensures long-running processes don't fail due to token expiration.
*
* @throws SocialAccountTokenInvalidException
*/
public function ensureValidToken(): void
{
if ($this->oauthAccount === null) {
return;
}
$newToken = $this->tokenManager->ensureValidToken($this->oauthAccount);
if ($newToken !== null) {
$this->accessToken = $newToken;
}
}
public function getConfig()
{
return $this->config;
}
// returns only active (archived=false)
public function getOwners(): array
{
return $this->getNewInstance()->crm()->owners()->getAll();
}
/**
* @param bool $archived
*
* @return array<Owner>|[]
*/
public function getOwnersArchived(bool $archived = true): array
{
$endpoint = '/crm/v3/owners';
$queryParams = [
'archived' => $archived ? 'true' : 'false',
];
$queryString = http_build_query($queryParams);
$owners = [];
try {
$response = $this->makeRequest(endpoint: $endpoint, queryString: $queryString);
$responseData = $response?->toArray();
foreach ($responseData['results'] as $result) {
try {
$owners[] = Owner::create($result);
} catch (Throwable $e) {
$this->log->error('[HubSpot] Failed to process owner data', [
'result' => $result,
'error' => $e->getMessage(),
]);
continue;
}
}
} catch (Throwable $e) {
$this->log->error('HubSpot] Failed to fetch owners', [
'archived' => $archived,
'error' => $e->getMessage(),
]);
return [];
}
return $owners;
}
public function getMeeting(string $engagementId): ObjectWithAssociations
{
return $this->getNewInstance()->crm()->objects()->basicApi()
->getById('meeting', $engagementId, null, 'contact,company,deal');
}
public function deleteEngagement(string $engagementId): void
{
$this->getInstance()->engagements()->delete((int) $engagementId);
}
public function getAssociationsData(array $ids, string $fromObject, string $toObject): array
{
$associationData = [];
$idChunks = array_chunk($ids, self::ASSOCIATIONS_BATCH_SIZE_LIMIT);
foreach ($idChunks as $idChunk) {
try {
$batchInput = new \HubSpot\Client\Crm\Associations\Model\BatchInputPublicObjectId();
$batchInput->setInputs(array_map(function ($id) {
$publicObjectId = new \HubSpot\Client\Crm\Associations\Model\PublicObjectId();
$publicObjectId->setId($id);
return $publicObjectId;
}, $idChunk));
$associatedObjectsData = $this
->getNewInstance()
->crm()
->associations()
->batchApi()
->read($fromObject, $toObject, $batchInput);
if ($associatedObjectsData instanceof \HubSpot\Client\Crm\Associations\Model\BatchResponsePublicAssociationMulti) {
foreach ($associatedObjectsData->getResults() as $association) {
$from = $association->getFrom()->getId();
$toAssociations = $association->getTo();
if (! empty($toAssociations)) {
$associationData[$from] = array_map(function ($item) {
return $item->getId();
}, $toAssociations);
}
}
}
} catch (\Exception $e) {
$this->log->error('[Hubspot] Failed to fetch associations', [
'from_object' => $fromObject,
'to_object' => $toObject,
'reason' => $e->getMessage(),
]);
}
}
return $associationData;
}
/**
* @throws \Exception
*/
private function getNoteAssociationType(NoteObject $noteObject): string
{
return match($noteObject) {
NoteObject::Opportunity => 'note_to_deal',
NoteObject::Lead, NoteObject::Contact => 'note_to_contact', // or 'note_to_lead' if your portal supports it
NoteObject::Account => 'note_to_company',
NoteObject::Call, NoteObject::Event => throw new \Exception('Not supported'),
};
}
/**
* @throws \Exception
*/
private function getNoteObject(NoteObject $noteObject): string
{
return match($noteObject) {
NoteObject::Opportunity => 'deal',
NoteObject::Lead, NoteObject::Contact => 'contact',
NoteObject::Account => 'company',
NoteObject::Call, NoteObject::Event => throw new \Exception('Not supported'),
};
}
public function addAssociations(string $objectType, string $associationType, array $payload): Response
{
$endpoint = "/crm/v4/associations/$objectType/$associationType/batch/create";
return $this->makeRequest($endpoint, 'POST', $payload);
}
public function removeAssociations(string $objectType, string $associationType, array $payload): Response
{
$endpoint = "/crm/v4/associations/$objectType/$associationType/batch/archive";
return $this->makeRequest($endpoint, 'POST', $payload);
}
}
Project
Project
New File or Directory…
Expand Selected
Collapse All
Options
Hide...
|
[{"role":"AXButton","text" [{"role":"AXButton","text":"Project: faVsco.js, menu","depth":5,"bounds":{"left":0.025930852,"top":0.019952115,"width":0.03856383,"height":0.025538707},"on_screen":true,"help_text":"~/jiminny/app","role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"master, menu","depth":5,"bounds":{"left":0.064494684,"top":0.019952115,"width":0.034242023,"height":0.025538707},"on_screen":true,"help_text":"Git Branch: master","role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Start Listening for PHP Debug Connections","depth":5,"bounds":{"left":0.8081782,"top":0.019952115,"width":0.011303191,"height":0.025538707},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"AskJiminnyReportActivityServiceTest","depth":6,"bounds":{"left":0.8234708,"top":0.019952115,"width":0.09208777,"height":0.025538707},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Run 'AskJiminnyReportActivityServiceTest'","depth":6,"bounds":{"left":0.9155585,"top":0.019952115,"width":0.011303191,"height":0.025538707},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Debug 'AskJiminnyReportActivityServiceTest'","depth":6,"bounds":{"left":0.9268617,"top":0.019952115,"width":0.011303191,"height":0.025538707},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"More Actions","depth":6,"bounds":{"left":0.9381649,"top":0.019952115,"width":0.011303191,"height":0.025538707},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"JetBrains AI","depth":5,"bounds":{"left":0.96609044,"top":0.019952115,"width":0.011303191,"height":0.025538707},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Search Everywhere","depth":5,"bounds":{"left":0.9773936,"top":0.019952115,"width":0.011303191,"height":0.025538707},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"IDE and Project Settings","depth":5,"bounds":{"left":0.9886968,"top":0.019952115,"width":0.011303186,"height":0.025538707},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXTextArea","text":"[2026-05-07 11:36:44] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage before starting command {\"command\":\"crm:sync-opportunity\",\"memoryBeforeCommandInMb\":60.0,\"memoryPeakBeforeCommandInMb\":99.727} {\"correlation_id\":\"462baf35-d312-4641-9896-e8b15e8fdb82\",\"trace_id\":\"aeb070fe-166a-48bf-99c0-acfcbfb40c1b\"}\n[2026-05-07 11:36:44] local.INFO: [SocialAccountService] Fetching token {\"socialAccountId\":1499,\"provider\":\"hubspot\"} {\"correlation_id\":\"462baf35-d312-4641-9896-e8b15e8fdb82\",\"trace_id\":\"aeb070fe-166a-48bf-99c0-acfcbfb40c1b\"}\n[2026-05-07 11:36:44] local.INFO: [SocialAccountService] Token retrieved {\"socialAccountId\":1499,\"provider\":\"hubspot\"} {\"correlation_id\":\"462baf35-d312-4641-9896-e8b15e8fdb82\",\"trace_id\":\"aeb070fe-166a-48bf-99c0-acfcbfb40c1b\"}\n[2026-05-07 11:36:44] local.INFO: [EncryptedTokenManager] Generating access token. {\"mode\":\"legacy\"} {\"correlation_id\":\"462baf35-d312-4641-9896-e8b15e8fdb82\",\"trace_id\":\"aeb070fe-166a-48bf-99c0-acfcbfb40c1b\"}\n[2026-05-07 11:36:44] local.INFO: [HubSpot] Syncing opportunities using strategy: lastModified {\"team\":2} {\"correlation_id\":\"462baf35-d312-4641-9896-e8b15e8fdb82\",\"trace_id\":\"aeb070fe-166a-48bf-99c0-acfcbfb40c1b\"}\n[2026-05-07 11:36:45] local.INFO: [Hubspot] Pagination completed {\"team_id\":2,\"endpoint\":\"https://api.hubapi.com/crm/v3/objects/deals/search\",\"total_requests\":1,\"total_records_fetched\":6,\"total_elapsed_seconds\":0.48,\"average_seconds_per_request\":0.48} {\"correlation_id\":\"462baf35-d312-4641-9896-e8b15e8fdb82\",\"trace_id\":\"aeb070fe-166a-48bf-99c0-acfcbfb40c1b\"}\n[2026-05-07 11:36:46] local.INFO: [HubSpot] importOpportunityBatch timing {\"teamId\":2,\"deal_count\":6,\"total_ms\":659,\"company_assoc_api_ms\":276,\"contact_assoc_api_ms\":203,\"prepare_entities_ms\":19,\"prepare_accounts_ms\":3,\"prepare_contacts_ms\":17,\"total_companies\":5,\"missing_companies\":0,\"total_contacts\":23,\"missing_contacts\":0,\"deals_loop_ms\":152,\"avg_deal_ms\":25,\"slow_deals_count\":0,\"slow_deals\":[]} {\"correlation_id\":\"462baf35-d312-4641-9896-e8b15e8fdb82\",\"trace_id\":\"aeb070fe-166a-48bf-99c0-acfcbfb40c1b\"}\n[2026-05-07 11:36:46] local.INFO: [HubSpot] Synced opportunities {\"team\":2,\"strategies\":\"lastModified\",\"sync_count\":5,\"total\":6,\"last_synced_id\":\"297846423\",\"duration_ms\":1161.39} {\"correlation_id\":\"462baf35-d312-4641-9896-e8b15e8fdb82\",\"trace_id\":\"aeb070fe-166a-48bf-99c0-acfcbfb40c1b\"}\n[2026-05-07 11:36:46] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage for command {\"command\":\"crm:sync-opportunity\",\"memoryBeforeCommandInMb\":60.0,\"memoryAfterCommandInMB\":62.0,\"memoryPeakBeforeCommandInMb\":99.727,\"memoryPeakAfterCommandInMB\":99.727} {\"correlation_id\":\"462baf35-d312-4641-9896-e8b15e8fdb82\",\"trace_id\":\"aeb070fe-166a-48bf-99c0-acfcbfb40c1b\"}\n[2026-05-07 11:36:52] local.INFO: [HubSpot Journal Polling] Getting offset from database {\"offset\":\"\",\"jiminny_team_id\":1} {\"correlation_id\":\"8568abe7-be33-417c-aa2f-b1bd53fb4adc\",\"trace_id\":\"582bff07-5ea4-4469-8ca8-efa41b04d46d\"}\n[2026-05-07 11:36:52] local.INFO: [HubSpot Journal API] Fetching latest journal entry {\"url\":\"https://api.hubapi.com/webhooks/v4/journal/latest\"} {\"correlation_id\":\"8568abe7-be33-417c-aa2f-b1bd53fb4adc\",\"trace_id\":\"582bff07-5ea4-4469-8ca8-efa41b04d46d\"}\n[2026-05-07 11:36:52] local.INFO: [HubSpot Journal Polling] No data {\"correlation_id\":\"8568abe7-be33-417c-aa2f-b1bd53fb4adc\",\"trace_id\":\"582bff07-5ea4-4469-8ca8-efa41b04d46d\"}\n[2026-05-07 11:36:52] local.WARNING: [HubSpot Journal Polling] Maximum empty results reached, stopping {\"empty_results\":5,\"max_empty_results\":5} {\"correlation_id\":\"8568abe7-be33-417c-aa2f-b1bd53fb4adc\",\"trace_id\":\"582bff07-5ea4-4469-8ca8-efa41b04d46d\"}\n[2026-05-07 11:36:52] local.WARNING: [HubSpot Journal Polling] Maximum empty results reached, stopping {\"empty_results\":5,\"max_empty_results\":5} {\"correlation_id\":\"8568abe7-be33-417c-aa2f-b1bd53fb4adc\",\"trace_id\":\"582bff07-5ea4-4469-8ca8-efa41b04d46d\"}\n[2026-05-07 11:36:52] local.INFO: [HubSpot Journal Polling] Service ending {\"runtime_seconds\":56,\"total_cycles\":5,\"files_downloaded\":0,\"empty_files\":0,\"other_portal_skipped\":0,\"total_events\":0,\"events_per_file\":0,\"avg_api_ms\":176.5,\"avg_download_ms\":0.0,\"avg_transform_ms\":0.0,\"avg_process_ms\":0.0,\"peak_memory_mb\":99.73} {\"correlation_id\":\"8568abe7-be33-417c-aa2f-b1bd53fb4adc\",\"trace_id\":\"582bff07-5ea4-4469-8ca8-efa41b04d46d\"}\n[2026-05-07 11:36:52] local.INFO: [HubSpot Journal Polling] Released polling lock {\"correlation_id\":\"8568abe7-be33-417c-aa2f-b1bd53fb4adc\",\"trace_id\":\"582bff07-5ea4-4469-8ca8-efa41b04d46d\"}\n[2026-05-07 11:37:05] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage before starting command {\"command\":\"meeting-bot:schedule-bot\",\"memoryBeforeCommandInMb\":60.0,\"memoryPeakBeforeCommandInMb\":99.727} {\"correlation_id\":\"34ae70b9-ee92-4e0c-bb97-3ecc204278be\",\"trace_id\":\"0d02e69d-5e33-4059-938c-d41350a39b72\"}\n[2026-05-07 11:37:05] local.INFO: [ScheduleBotCommand] Number of activities to be captured: 0 {\"correlation_id\":\"34ae70b9-ee92-4e0c-bb97-3ecc204278be\",\"trace_id\":\"0d02e69d-5e33-4059-938c-d41350a39b72\"}\n[2026-05-07 11:37:05] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage for command {\"command\":\"meeting-bot:schedule-bot\",\"memoryBeforeCommandInMb\":60.0,\"memoryAfterCommandInMB\":62.0,\"memoryPeakBeforeCommandInMb\":99.727,\"memoryPeakAfterCommandInMB\":99.727} {\"correlation_id\":\"34ae70b9-ee92-4e0c-bb97-3ecc204278be\",\"trace_id\":\"0d02e69d-5e33-4059-938c-d41350a39b72\"}\n[2026-05-07 11:37:06] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage before starting command {\"command\":\"dialers:monitor-activities\",\"memoryBeforeCommandInMb\":62.0,\"memoryPeakBeforeCommandInMb\":99.727} {\"correlation_id\":\"6052f7ad-dde1-41de-8051-5ba8d54babdd\",\"trace_id\":\"2e216848-ddf9-4f8c-8808-93a09cef8f5f\"}\n[2026-05-07 11:37:06] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage for command {\"command\":\"dialers:monitor-activities\",\"memoryBeforeCommandInMb\":62.0,\"memoryAfterCommandInMB\":62.0,\"memoryPeakBeforeCommandInMb\":99.727,\"memoryPeakAfterCommandInMB\":99.727} {\"correlation_id\":\"6052f7ad-dde1-41de-8051-5ba8d54babdd\",\"trace_id\":\"2e216848-ddf9-4f8c-8808-93a09cef8f5f\"}\n[2026-05-07 11:37:07] local.NOTICE: Monitoring start {\"correlation_id\":\"64decce4-a339-4c1b-8a73-4f612ef2eea2\",\"trace_id\":\"07e59e6c-d681-41f9-ba04-5cab00e9a754\"}\n[2026-05-07 11:37:07] local.NOTICE: Monitoring end {\"correlation_id\":\"64decce4-a339-4c1b-8a73-4f612ef2eea2\",\"trace_id\":\"07e59e6c-d681-41f9-ba04-5cab00e9a754\"}\n[2026-05-07 11:37:09] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage before starting command {\"command\":\"mailbox:skip-lists:refresh\",\"memoryBeforeCommandInMb\":62.0,\"memoryPeakBeforeCommandInMb\":99.727} {\"correlation_id\":\"0326b1c8-69ac-4d09-aae5-e808abc71f46\",\"trace_id\":\"2d16668b-26b0-44cf-a533-493500255667\"}\n[2026-05-07 11:37:09] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage for command {\"command\":\"mailbox:skip-lists:refresh\",\"memoryBeforeCommandInMb\":62.0,\"memoryAfterCommandInMB\":62.0,\"memoryPeakBeforeCommandInMb\":99.727,\"memoryPeakAfterCommandInMB\":99.727} {\"correlation_id\":\"0326b1c8-69ac-4d09-aae5-e808abc71f46\",\"trace_id\":\"2d16668b-26b0-44cf-a533-493500255667\"}\n[2026-05-07 11:37:10] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage before starting command {\"command\":\"mailbox:batch:process\",\"memoryBeforeCommandInMb\":62.0,\"memoryPeakBeforeCommandInMb\":99.727} {\"correlation_id\":\"aa512302-070c-4f99-b3ef-1a43c71b6297\",\"trace_id\":\"9e5c4036-6a42-4c64-a9b3-f11143254293\"}\n[2026-05-07 11:37:10] local.INFO: [EmailSchedule] STARTING batch process {\"host\":\"docker_lamp_1\"} {\"correlation_id\":\"aa512302-070c-4f99-b3ef-1a43c71b6297\",\"trace_id\":\"9e5c4036-6a42-4c64-a9b3-f11143254293\"}\n[2026-05-07 11:37:10] local.INFO: [EmailSchedule] FINISHED batch process {\"host\":\"docker_lamp_1\",\"processed\":0} {\"correlation_id\":\"aa512302-070c-4f99-b3ef-1a43c71b6297\",\"trace_id\":\"9e5c4036-6a42-4c64-a9b3-f11143254293\"}\n[2026-05-07 11:37:10] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage for command {\"command\":\"mailbox:batch:process\",\"memoryBeforeCommandInMb\":62.0,\"memoryAfterCommandInMB\":62.0,\"memoryPeakBeforeCommandInMb\":99.727,\"memoryPeakAfterCommandInMB\":99.727} {\"correlation_id\":\"aa512302-070c-4f99-b3ef-1a43c71b6297\",\"trace_id\":\"9e5c4036-6a42-4c64-a9b3-f11143254293\"}\n[2026-05-07 11:37:13] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage before starting command {\"command\":\"mailbox:batch:create\",\"memoryBeforeCommandInMb\":62.0,\"memoryPeakBeforeCommandInMb\":99.727} {\"correlation_id\":\"b3d4318e-3db2-413c-8fe1-8827ec834020\",\"trace_id\":\"870e454c-3a47-419e-88f1-2321d38b8937\"}\n[2026-05-07 11:37:13] local.INFO: [EmailSchedule] STARTING batch create {\"host\":\"docker_lamp_1\"} {\"correlation_id\":\"b3d4318e-3db2-413c-8fe1-8827ec834020\",\"trace_id\":\"870e454c-3a47-419e-88f1-2321d38b8937\"}\n[2026-05-07 11:37:13] local.INFO: [EmailSchedule] FINISHED batch create {\"host\":\"docker_lamp_1\"} {\"correlation_id\":\"b3d4318e-3db2-413c-8fe1-8827ec834020\",\"trace_id\":\"870e454c-3a47-419e-88f1-2321d38b8937\"}\n[2026-05-07 11:37:13] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage for command {\"command\":\"mailbox:batch:create\",\"memoryBeforeCommandInMb\":62.0,\"memoryAfterCommandInMB\":62.0,\"memoryPeakBeforeCommandInMb\":99.727,\"memoryPeakAfterCommandInMB\":99.727} {\"correlation_id\":\"b3d4318e-3db2-413c-8fe1-8827ec834020\",\"trace_id\":\"870e454c-3a47-419e-88f1-2321d38b8937\"}\n[2026-05-07 11:37:14] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage before starting command {\"command\":\"activity:sync\",\"memoryBeforeCommandInMb\":62.0,\"memoryPeakBeforeCommandInMb\":99.727} {\"correlation_id\":\"3e5f2a44-295d-4c28-b5e9-1a649c1f49ae\",\"trace_id\":\"f4d85ec2-b1d2-4418-bb65-814c209e628f\"}\n[2026-05-07 11:37:15] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage for command {\"command\":\"activity:sync\",\"memoryBeforeCommandInMb\":62.0,\"memoryAfterCommandInMB\":62.0,\"memoryPeakBeforeCommandInMb\":99.727,\"memoryPeakAfterCommandInMB\":99.727} {\"correlation_id\":\"3e5f2a44-295d-4c28-b5e9-1a649c1f49ae\",\"trace_id\":\"f4d85ec2-b1d2-4418-bb65-814c209e628f\"}\n[2026-05-07 11:37:21] local.INFO: [Commands/AsyncUpdateEsEntities] Starting ES update worker {\"pid\":20519,\"workerId\":\"\",\"target\":\"activities\"} {\"correlation_id\":\"1a81e470-75ac-4728-9d45-9f879fc96c5b\",\"trace_id\":\"7184ea88-9f01-42bc-9508-8e4ad0c1204b\"}\n[2026-05-07 11:37:22] local.INFO: [Jiminny\\Jobs\\Mailbox\\CreateBatches] processed 2 inboxes and created 0 batches {\"userId\":null,\"batchSize\":30,\"maxBatches\":1000} {\"correlation_id\":\"798a3e91-233c-4b61-aa8e-80a7b84ac45f\",\"trace_id\":\"870e454c-3a47-419e-88f1-2321d38b8937\"}\n[2026-05-07 11:37:53] local.INFO: [Commands/AsyncUpdateEsEntities] Starting ES update worker {\"pid\":20661,\"workerId\":\"\",\"target\":\"activities\"} {\"correlation_id\":\"293fa4a8-dc12-452e-88a3-f7408f8ed676\",\"trace_id\":\"e3d77ea8-2ff3-4b4e-a388-8f21192e51f3\"}","depth":4,"bounds":{"left":0.52859044,"top":0.0726257,"width":0.45977393,"height":0.9273743},"on_screen":true,"value":"[2026-05-07 11:36:44] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage before starting command {\"command\":\"crm:sync-opportunity\",\"memoryBeforeCommandInMb\":60.0,\"memoryPeakBeforeCommandInMb\":99.727} {\"correlation_id\":\"462baf35-d312-4641-9896-e8b15e8fdb82\",\"trace_id\":\"aeb070fe-166a-48bf-99c0-acfcbfb40c1b\"}\n[2026-05-07 11:36:44] local.INFO: [SocialAccountService] Fetching token {\"socialAccountId\":1499,\"provider\":\"hubspot\"} {\"correlation_id\":\"462baf35-d312-4641-9896-e8b15e8fdb82\",\"trace_id\":\"aeb070fe-166a-48bf-99c0-acfcbfb40c1b\"}\n[2026-05-07 11:36:44] local.INFO: [SocialAccountService] Token retrieved {\"socialAccountId\":1499,\"provider\":\"hubspot\"} {\"correlation_id\":\"462baf35-d312-4641-9896-e8b15e8fdb82\",\"trace_id\":\"aeb070fe-166a-48bf-99c0-acfcbfb40c1b\"}\n[2026-05-07 11:36:44] local.INFO: [EncryptedTokenManager] Generating access token. {\"mode\":\"legacy\"} {\"correlation_id\":\"462baf35-d312-4641-9896-e8b15e8fdb82\",\"trace_id\":\"aeb070fe-166a-48bf-99c0-acfcbfb40c1b\"}\n[2026-05-07 11:36:44] local.INFO: [HubSpot] Syncing opportunities using strategy: lastModified {\"team\":2} {\"correlation_id\":\"462baf35-d312-4641-9896-e8b15e8fdb82\",\"trace_id\":\"aeb070fe-166a-48bf-99c0-acfcbfb40c1b\"}\n[2026-05-07 11:36:45] local.INFO: [Hubspot] Pagination completed {\"team_id\":2,\"endpoint\":\"https://api.hubapi.com/crm/v3/objects/deals/search\",\"total_requests\":1,\"total_records_fetched\":6,\"total_elapsed_seconds\":0.48,\"average_seconds_per_request\":0.48} {\"correlation_id\":\"462baf35-d312-4641-9896-e8b15e8fdb82\",\"trace_id\":\"aeb070fe-166a-48bf-99c0-acfcbfb40c1b\"}\n[2026-05-07 11:36:46] local.INFO: [HubSpot] importOpportunityBatch timing {\"teamId\":2,\"deal_count\":6,\"total_ms\":659,\"company_assoc_api_ms\":276,\"contact_assoc_api_ms\":203,\"prepare_entities_ms\":19,\"prepare_accounts_ms\":3,\"prepare_contacts_ms\":17,\"total_companies\":5,\"missing_companies\":0,\"total_contacts\":23,\"missing_contacts\":0,\"deals_loop_ms\":152,\"avg_deal_ms\":25,\"slow_deals_count\":0,\"slow_deals\":[]} {\"correlation_id\":\"462baf35-d312-4641-9896-e8b15e8fdb82\",\"trace_id\":\"aeb070fe-166a-48bf-99c0-acfcbfb40c1b\"}\n[2026-05-07 11:36:46] local.INFO: [HubSpot] Synced opportunities {\"team\":2,\"strategies\":\"lastModified\",\"sync_count\":5,\"total\":6,\"last_synced_id\":\"297846423\",\"duration_ms\":1161.39} {\"correlation_id\":\"462baf35-d312-4641-9896-e8b15e8fdb82\",\"trace_id\":\"aeb070fe-166a-48bf-99c0-acfcbfb40c1b\"}\n[2026-05-07 11:36:46] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage for command {\"command\":\"crm:sync-opportunity\",\"memoryBeforeCommandInMb\":60.0,\"memoryAfterCommandInMB\":62.0,\"memoryPeakBeforeCommandInMb\":99.727,\"memoryPeakAfterCommandInMB\":99.727} {\"correlation_id\":\"462baf35-d312-4641-9896-e8b15e8fdb82\",\"trace_id\":\"aeb070fe-166a-48bf-99c0-acfcbfb40c1b\"}\n[2026-05-07 11:36:52] local.INFO: [HubSpot Journal Polling] Getting offset from database {\"offset\":\"\",\"jiminny_team_id\":1} {\"correlation_id\":\"8568abe7-be33-417c-aa2f-b1bd53fb4adc\",\"trace_id\":\"582bff07-5ea4-4469-8ca8-efa41b04d46d\"}\n[2026-05-07 11:36:52] local.INFO: [HubSpot Journal API] Fetching latest journal entry {\"url\":\"https://api.hubapi.com/webhooks/v4/journal/latest\"} {\"correlation_id\":\"8568abe7-be33-417c-aa2f-b1bd53fb4adc\",\"trace_id\":\"582bff07-5ea4-4469-8ca8-efa41b04d46d\"}\n[2026-05-07 11:36:52] local.INFO: [HubSpot Journal Polling] No data {\"correlation_id\":\"8568abe7-be33-417c-aa2f-b1bd53fb4adc\",\"trace_id\":\"582bff07-5ea4-4469-8ca8-efa41b04d46d\"}\n[2026-05-07 11:36:52] local.WARNING: [HubSpot Journal Polling] Maximum empty results reached, stopping {\"empty_results\":5,\"max_empty_results\":5} {\"correlation_id\":\"8568abe7-be33-417c-aa2f-b1bd53fb4adc\",\"trace_id\":\"582bff07-5ea4-4469-8ca8-efa41b04d46d\"}\n[2026-05-07 11:36:52] local.WARNING: [HubSpot Journal Polling] Maximum empty results reached, stopping {\"empty_results\":5,\"max_empty_results\":5} {\"correlation_id\":\"8568abe7-be33-417c-aa2f-b1bd53fb4adc\",\"trace_id\":\"582bff07-5ea4-4469-8ca8-efa41b04d46d\"}\n[2026-05-07 11:36:52] local.INFO: [HubSpot Journal Polling] Service ending {\"runtime_seconds\":56,\"total_cycles\":5,\"files_downloaded\":0,\"empty_files\":0,\"other_portal_skipped\":0,\"total_events\":0,\"events_per_file\":0,\"avg_api_ms\":176.5,\"avg_download_ms\":0.0,\"avg_transform_ms\":0.0,\"avg_process_ms\":0.0,\"peak_memory_mb\":99.73} {\"correlation_id\":\"8568abe7-be33-417c-aa2f-b1bd53fb4adc\",\"trace_id\":\"582bff07-5ea4-4469-8ca8-efa41b04d46d\"}\n[2026-05-07 11:36:52] local.INFO: [HubSpot Journal Polling] Released polling lock {\"correlation_id\":\"8568abe7-be33-417c-aa2f-b1bd53fb4adc\",\"trace_id\":\"582bff07-5ea4-4469-8ca8-efa41b04d46d\"}\n[2026-05-07 11:37:05] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage before starting command {\"command\":\"meeting-bot:schedule-bot\",\"memoryBeforeCommandInMb\":60.0,\"memoryPeakBeforeCommandInMb\":99.727} {\"correlation_id\":\"34ae70b9-ee92-4e0c-bb97-3ecc204278be\",\"trace_id\":\"0d02e69d-5e33-4059-938c-d41350a39b72\"}\n[2026-05-07 11:37:05] local.INFO: [ScheduleBotCommand] Number of activities to be captured: 0 {\"correlation_id\":\"34ae70b9-ee92-4e0c-bb97-3ecc204278be\",\"trace_id\":\"0d02e69d-5e33-4059-938c-d41350a39b72\"}\n[2026-05-07 11:37:05] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage for command {\"command\":\"meeting-bot:schedule-bot\",\"memoryBeforeCommandInMb\":60.0,\"memoryAfterCommandInMB\":62.0,\"memoryPeakBeforeCommandInMb\":99.727,\"memoryPeakAfterCommandInMB\":99.727} {\"correlation_id\":\"34ae70b9-ee92-4e0c-bb97-3ecc204278be\",\"trace_id\":\"0d02e69d-5e33-4059-938c-d41350a39b72\"}\n[2026-05-07 11:37:06] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage before starting command {\"command\":\"dialers:monitor-activities\",\"memoryBeforeCommandInMb\":62.0,\"memoryPeakBeforeCommandInMb\":99.727} {\"correlation_id\":\"6052f7ad-dde1-41de-8051-5ba8d54babdd\",\"trace_id\":\"2e216848-ddf9-4f8c-8808-93a09cef8f5f\"}\n[2026-05-07 11:37:06] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage for command {\"command\":\"dialers:monitor-activities\",\"memoryBeforeCommandInMb\":62.0,\"memoryAfterCommandInMB\":62.0,\"memoryPeakBeforeCommandInMb\":99.727,\"memoryPeakAfterCommandInMB\":99.727} {\"correlation_id\":\"6052f7ad-dde1-41de-8051-5ba8d54babdd\",\"trace_id\":\"2e216848-ddf9-4f8c-8808-93a09cef8f5f\"}\n[2026-05-07 11:37:07] local.NOTICE: Monitoring start {\"correlation_id\":\"64decce4-a339-4c1b-8a73-4f612ef2eea2\",\"trace_id\":\"07e59e6c-d681-41f9-ba04-5cab00e9a754\"}\n[2026-05-07 11:37:07] local.NOTICE: Monitoring end {\"correlation_id\":\"64decce4-a339-4c1b-8a73-4f612ef2eea2\",\"trace_id\":\"07e59e6c-d681-41f9-ba04-5cab00e9a754\"}\n[2026-05-07 11:37:09] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage before starting command {\"command\":\"mailbox:skip-lists:refresh\",\"memoryBeforeCommandInMb\":62.0,\"memoryPeakBeforeCommandInMb\":99.727} {\"correlation_id\":\"0326b1c8-69ac-4d09-aae5-e808abc71f46\",\"trace_id\":\"2d16668b-26b0-44cf-a533-493500255667\"}\n[2026-05-07 11:37:09] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage for command {\"command\":\"mailbox:skip-lists:refresh\",\"memoryBeforeCommandInMb\":62.0,\"memoryAfterCommandInMB\":62.0,\"memoryPeakBeforeCommandInMb\":99.727,\"memoryPeakAfterCommandInMB\":99.727} {\"correlation_id\":\"0326b1c8-69ac-4d09-aae5-e808abc71f46\",\"trace_id\":\"2d16668b-26b0-44cf-a533-493500255667\"}\n[2026-05-07 11:37:10] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage before starting command {\"command\":\"mailbox:batch:process\",\"memoryBeforeCommandInMb\":62.0,\"memoryPeakBeforeCommandInMb\":99.727} {\"correlation_id\":\"aa512302-070c-4f99-b3ef-1a43c71b6297\",\"trace_id\":\"9e5c4036-6a42-4c64-a9b3-f11143254293\"}\n[2026-05-07 11:37:10] local.INFO: [EmailSchedule] STARTING batch process {\"host\":\"docker_lamp_1\"} {\"correlation_id\":\"aa512302-070c-4f99-b3ef-1a43c71b6297\",\"trace_id\":\"9e5c4036-6a42-4c64-a9b3-f11143254293\"}\n[2026-05-07 11:37:10] local.INFO: [EmailSchedule] FINISHED batch process {\"host\":\"docker_lamp_1\",\"processed\":0} {\"correlation_id\":\"aa512302-070c-4f99-b3ef-1a43c71b6297\",\"trace_id\":\"9e5c4036-6a42-4c64-a9b3-f11143254293\"}\n[2026-05-07 11:37:10] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage for command {\"command\":\"mailbox:batch:process\",\"memoryBeforeCommandInMb\":62.0,\"memoryAfterCommandInMB\":62.0,\"memoryPeakBeforeCommandInMb\":99.727,\"memoryPeakAfterCommandInMB\":99.727} {\"correlation_id\":\"aa512302-070c-4f99-b3ef-1a43c71b6297\",\"trace_id\":\"9e5c4036-6a42-4c64-a9b3-f11143254293\"}\n[2026-05-07 11:37:13] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage before starting command {\"command\":\"mailbox:batch:create\",\"memoryBeforeCommandInMb\":62.0,\"memoryPeakBeforeCommandInMb\":99.727} {\"correlation_id\":\"b3d4318e-3db2-413c-8fe1-8827ec834020\",\"trace_id\":\"870e454c-3a47-419e-88f1-2321d38b8937\"}\n[2026-05-07 11:37:13] local.INFO: [EmailSchedule] STARTING batch create {\"host\":\"docker_lamp_1\"} {\"correlation_id\":\"b3d4318e-3db2-413c-8fe1-8827ec834020\",\"trace_id\":\"870e454c-3a47-419e-88f1-2321d38b8937\"}\n[2026-05-07 11:37:13] local.INFO: [EmailSchedule] FINISHED batch create {\"host\":\"docker_lamp_1\"} {\"correlation_id\":\"b3d4318e-3db2-413c-8fe1-8827ec834020\",\"trace_id\":\"870e454c-3a47-419e-88f1-2321d38b8937\"}\n[2026-05-07 11:37:13] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage for command {\"command\":\"mailbox:batch:create\",\"memoryBeforeCommandInMb\":62.0,\"memoryAfterCommandInMB\":62.0,\"memoryPeakBeforeCommandInMb\":99.727,\"memoryPeakAfterCommandInMB\":99.727} {\"correlation_id\":\"b3d4318e-3db2-413c-8fe1-8827ec834020\",\"trace_id\":\"870e454c-3a47-419e-88f1-2321d38b8937\"}\n[2026-05-07 11:37:14] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage before starting command {\"command\":\"activity:sync\",\"memoryBeforeCommandInMb\":62.0,\"memoryPeakBeforeCommandInMb\":99.727} {\"correlation_id\":\"3e5f2a44-295d-4c28-b5e9-1a649c1f49ae\",\"trace_id\":\"f4d85ec2-b1d2-4418-bb65-814c209e628f\"}\n[2026-05-07 11:37:15] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage for command {\"command\":\"activity:sync\",\"memoryBeforeCommandInMb\":62.0,\"memoryAfterCommandInMB\":62.0,\"memoryPeakBeforeCommandInMb\":99.727,\"memoryPeakAfterCommandInMB\":99.727} {\"correlation_id\":\"3e5f2a44-295d-4c28-b5e9-1a649c1f49ae\",\"trace_id\":\"f4d85ec2-b1d2-4418-bb65-814c209e628f\"}\n[2026-05-07 11:37:21] local.INFO: [Commands/AsyncUpdateEsEntities] Starting ES update worker {\"pid\":20519,\"workerId\":\"\",\"target\":\"activities\"} {\"correlation_id\":\"1a81e470-75ac-4728-9d45-9f879fc96c5b\",\"trace_id\":\"7184ea88-9f01-42bc-9508-8e4ad0c1204b\"}\n[2026-05-07 11:37:22] local.INFO: [Jiminny\\Jobs\\Mailbox\\CreateBatches] processed 2 inboxes and created 0 batches {\"userId\":null,\"batchSize\":30,\"maxBatches\":1000} {\"correlation_id\":\"798a3e91-233c-4b61-aa8e-80a7b84ac45f\",\"trace_id\":\"870e454c-3a47-419e-88f1-2321d38b8937\"}\n[2026-05-07 11:37:53] local.INFO: [Commands/AsyncUpdateEsEntities] Starting ES update worker {\"pid\":20661,\"workerId\":\"\",\"target\":\"activities\"} {\"correlation_id\":\"293fa4a8-dc12-452e-88a3-f7408f8ed676\",\"trace_id\":\"e3d77ea8-2ff3-4b4e-a388-8f21192e51f3\"}","role_description":"text entry area","is_enabled":true,"is_focused":true,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Sync Changes","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.008643617,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Hide This Notification","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.008643617,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Code changed:","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.042220745,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Hide","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.008643617,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"2","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.007978723,"height":0.0},"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"60","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.010305851,"height":0.0},"on_screen":false,"role_description":"text"},{"role":"AXButton","text":"Previous Highlighted Error","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.00731383,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Next Highlighted Error","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.006981383,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXTextArea","text":"<?php\n\ndeclare(strict_types=1);\n\nnamespace Jiminny\\Services\\Crm\\Hubspot;\n\nuse HubSpot\\Client\\Crm\\Deals\\ApiException as DealApiException;\nuse HubSpot\\Client\\Crm\\Contacts\\ApiException as ContactApiException;\nuse HubSpot\\Client\\Crm\\Companies\\ApiException as CompanyApiException;\nuse HubSpot\\Client\\Crm\\Contacts\\Model\\SimplePublicObjectWithAssociations as ContactsWithAssociations;\nuse HubSpot\\Client\\Crm\\Companies\\Model\\SimplePublicObjectWithAssociations as CompaniesWithAssociations;\nuse HubSpot\\Client\\Crm\\Deals\\Model\\SimplePublicObjectWithAssociations as DealWithAssociations;\nuse HubSpot\\Client\\Crm\\Objects\\Model\\SimplePublicObjectInput;\nuse HubSpot\\Client\\Crm\\Objects\\Model\\SimplePublicObjectWithAssociations as ObjectWithAssociations;\nuse HubSpot\\Client\\Crm\\Pipelines\\Model\\Error;\nuse HubSpot\\Client\\Crm\\Pipelines\\Model\\PipelineStage;\nuse HubSpot\\Client\\Crm\\Properties\\Model\\Property;\nuse HubSpot\\Discovery\\Discovery;\nuse Jiminny\\Component\\Utility\\Service\\ProviderRateLimiter;\nuse Jiminny\\Exceptions\\CrmException;\nuse Jiminny\\Exceptions\\RateLimitException;\nuse Jiminny\\Exceptions\\SocialAccountTokenInvalidException;\nuse Jiminny\\Jobs\\Crm\\NoteObject;\nuse Jiminny\\Models\\Crm\\Field;\nuse Jiminny\\Services\\Crm\\BaseClient;\nuse Jiminny\\Services\\Crm\\Hubspot\\DTO\\Response\\Owner;\nuse Jiminny\\Services\\SocialAccountService;\nuse SevenShores\\Hubspot\\Exceptions\\BadRequest;\nuse SevenShores\\Hubspot\\Exceptions\\HubspotException;\nuse SevenShores\\Hubspot\\Factory;\nuse SevenShores\\Hubspot\\Http\\Response;\nuse Jiminny\\Services\\Crm\\Hubspot\\Pagination\\HubspotPaginationService;\nuse Throwable;\n\n/**\n * @phpstan-type CrmFieldOption array{id:string, label:string, value?:string}\n */\nclass Client extends BaseClient implements HubspotClientInterface\n{\n public const string MIN_API_VERSION = '2';\n\n public const string BASE_URL = 'https://api.hubapi.com';\n\n public const int ASSOCIATIONS_BATCH_SIZE_LIMIT = 1000;\n\n private HubspotPaginationService $paginationService;\n private HubspotTokenManager $tokenManager;\n private ProviderRateLimiter $rateLimiter;\n\n public function __construct(\n SocialAccountService $socialAccountService,\n HubspotPaginationService $paginationService,\n HubspotTokenManager $tokenManager,\n ProviderRateLimiter $rateLimiter,\n ) {\n parent::__construct($socialAccountService);\n $this->paginationService = $paginationService;\n $this->tokenManager = $tokenManager;\n $this->rateLimiter = $rateLimiter;\n\n $this->setBaseUrl(self::BASE_URL);\n $this->setVersion(self::MIN_API_VERSION);\n }\n\n /**\n * Single entry point for every HubSpot API call. Enforces the per-portal\n * rate limit configured in the rate_limits table (morphed to the current\n * Configuration) and reacts to a real 429 from HubSpot by translating it\n * into a RateLimitException carrying Retry-After.\n *\n * Wrap any outbound HubSpot call (SDK or raw HTTP) like:\n *\n * $this->executeRequest(fn () => $this->getNewInstance()->crm()->...);\n *\n * @template T\n * @param callable(): T $apiCall\n * @return T\n *\n * @throws RateLimitException\n */\n private function executeRequest(callable $apiCall)\n {\n if (! $this->rateLimiter->canMakeRequest($this->config)) {\n $retryAfter = $this->rateLimiter->requestAvailableIn($this->config);\n\n $this->log->warning('[Hubspot] Rate limit exceeded, deferring request', [\n 'team_id' => $this->config->team_id,\n 'config_id' => $this->config->getId(),\n 'retry_after' => $retryAfter,\n ]);\n\n throw new RateLimitException(\n 'Hubspot rate limit reached for configuration ' . $this->config->getId(),\n $retryAfter,\n );\n }\n\n $this->rateLimiter->incrementRequestCount($this->config);\n\n try {\n return $apiCall();\n } catch (Throwable $e) {\n if ($this->isHubspotRateLimit($e)) {\n $retryAfter = $this->parseRetryAfter($e);\n\n $this->log->warning('[Hubspot] Received 429 from API', [\n 'team_id' => $this->config->team_id,\n 'config_id' => $this->config->getId(),\n 'retry_after' => $retryAfter,\n 'reason' => $e->getMessage(),\n ]);\n\n throw new RateLimitException('Hubspot returned 429', $retryAfter, $e);\n }\n\n throw $e;\n }\n }\n\n private function isHubspotRateLimit(Throwable $e): bool\n {\n return method_exists($e, 'getCode') && (int) $e->getCode() === 429;\n }\n\n private function parseRetryAfter(Throwable $e): int\n {\n if (method_exists($e, 'getResponseHeaders')) {\n $headers = $e->getResponseHeaders() ?: [];\n $value = $headers['Retry-After'] ?? $headers['retry-after'] ?? null;\n if (is_array($value)) {\n $value = $value[0] ?? null;\n }\n if (is_numeric($value)) {\n return (int) $value;\n }\n }\n\n return 10;\n }\n\n public function getMinimumApiVersion(): string\n {\n return self::MIN_API_VERSION;\n }\n\n public function getInstance(): Factory\n {\n return new Factory([\n 'key' => $this->accessToken,\n 'oauth2' => true,\n 'base_url' => $this->baseUrl,\n ]);\n }\n\n public function getNewInstance(): Discovery\n {\n return \\HubSpot\\Factory::createWithAccessToken($this->accessToken);\n }\n\n /**\n * Secondly and daily limits for Hubspot API\n *\n * Product Tier: Free & Starter | Professional & Enterprise | API add-on (any tier)\n * Burst: 100/10 seconds | 150/10 seconds | 200/10 seconds\n * Daily: 250,000 | 500,000 | 1,000,000\n *\n * Official documentation states: The search endpoints are rate limited to five requests per second.\n * Since with 5 RPS were still hitting secondly rate limits we lowered it to 4\n */\n public function getPaginatedData(array $payload, string $type, int $offset = 0): array\n {\n $total = 0;\n $lastId = null;\n $rows = [];\n foreach ($this->getPaginatedDataGenerator($payload, $type, $offset, $total, $lastId) as $row) {\n $rows[] = $row;\n }\n\n return ['results' => $rows, 'total' => $total, 'last_record' => $lastId];\n }\n\n /**\n * @throws HubspotException\n * @throws SocialAccountTokenInvalidException\n * @throws BadRequest\n */\n public function getPaginatedDataGenerator(\n array $payload,\n string $type,\n int $offset = 0,\n int &$total = 0,\n ?string &$lastRecordId = null\n ): \\Generator {\n return $this->paginationService->getPaginatedDataGenerator(\n $this,\n $payload,\n $type,\n $offset,\n $total,\n $lastRecordId\n );\n }\n\n /**\n * @throws DealApiException\n * @throws CrmException\n */\n public function getOpportunityById(string $crmId, array $fields): array\n {\n try {\n $deal = $this->executeRequest(fn () => $this->getNewInstance()->crm()->deals()->basicApi()->getById(\n $crmId,\n implode(',', $fields),\n 'companies,contacts'\n ));\n } catch (DealApiException $e) {\n $this->log->info('[Hubspot] Failed to fetch opportunity', [\n 'crm_id' => $crmId,\n 'reason' => $e->getMessage(),\n ]);\n\n throw $e;\n }\n\n if (! $deal instanceof DealWithAssociations) {\n throw new CrmException('Deal not found');\n }\n\n return [\n 'id' => $deal->getId(),\n 'properties' => $deal->getProperties(),\n 'associations' => $deal->getAssociations(),\n ];\n }\n\n /**\n * Generic batch read method for HubSpot objects\n *\n * @param string $objectType The object type ('deals', 'companies', 'contacts')\n * @param array<string> $crmIds Array of HubSpot object IDs (max 100)\n * @param array<string> $fields Array of property names to fetch\n *\n * @return array<string, array> Array keyed by CRM ID with object data\n */\n private function batchReadObjects(string $objectType, array $crmIds, array $fields): array\n {\n if (empty($crmIds)) {\n return [];\n }\n\n $this->validateBatchSize($objectType, $crmIds);\n $this->ensureValidToken();\n\n try {\n $batchConfig = $this->createBatchConfiguration($objectType);\n $batchReadRequest = $this->prepareBatchRequest($batchConfig, $crmIds, $fields);\n $response = $batchConfig['api']->read($batchReadRequest);\n\n $this->validateApiResponse($response, $objectType);\n\n $results = $this->processApiResults($response);\n $this->logBatchResults($objectType, $crmIds, $results);\n\n return $results;\n } catch (\\Throwable $e) {\n $this->handleBatchError($e, $objectType, $crmIds);\n }\n }\n\n private function validateBatchSize(string $objectType, array $crmIds): void\n {\n if (count($crmIds) > 100) {\n throw new \\InvalidArgumentException(\"Batch size cannot exceed 100 {$objectType}\");\n }\n }\n\n private function createBatchConfiguration(string $objectType): array\n {\n $configurations = [\n 'deals' => [\n 'batchReadRequest' => new \\HubSpot\\Client\\Crm\\Deals\\Model\\BatchReadInputSimplePublicObjectId(),\n 'inputClass' => \\HubSpot\\Client\\Crm\\Deals\\Model\\SimplePublicObjectId::class,\n 'api' => $this->getNewInstance()->crm()->deals()->batchApi(),\n ],\n 'companies' => [\n 'batchReadRequest' => new \\HubSpot\\Client\\Crm\\Companies\\Model\\BatchReadInputSimplePublicObjectId(),\n 'inputClass' => \\HubSpot\\Client\\Crm\\Companies\\Model\\SimplePublicObjectId::class,\n 'api' => $this->getNewInstance()->crm()->companies()->batchApi(),\n ],\n 'contacts' => [\n 'batchReadRequest' => new \\HubSpot\\Client\\Crm\\Contacts\\Model\\BatchReadInputSimplePublicObjectId(),\n 'inputClass' => \\HubSpot\\Client\\Crm\\Contacts\\Model\\SimplePublicObjectId::class,\n 'api' => $this->getNewInstance()->crm()->contacts()->batchApi(),\n ],\n ];\n\n if (! isset($configurations[$objectType])) {\n throw new \\InvalidArgumentException(\"Unsupported object type: {$objectType}\");\n }\n\n return $configurations[$objectType];\n }\n\n private function prepareBatchRequest(array $batchConfig, array $crmIds, array $fields): object\n {\n $batchReadRequest = $batchConfig['batchReadRequest'];\n $inputClass = $batchConfig['inputClass'];\n\n $inputs = array_map(function ($crmId) use ($inputClass) {\n $input = new $inputClass();\n $input->setId($crmId);\n\n return $input;\n }, $crmIds);\n\n $batchReadRequest->setInputs($inputs);\n $batchReadRequest->setProperties($fields);\n\n return $batchReadRequest;\n }\n\n private function validateApiResponse($response, string $objectType): void\n {\n if (! $response) {\n throw new CrmException(\"HubSpot API returned null response for {$objectType} batch read\");\n }\n }\n\n private function processApiResults($response): array\n {\n $results = [];\n $responseResults = $response->getResults();\n\n if ($responseResults) {\n foreach ($responseResults as $object) {\n if ($object && $object->getId()) {\n $results[$object->getId()] = [\n 'id' => $object->getId(),\n 'properties' => $object->getProperties() ?: [],\n ];\n }\n }\n }\n\n return $results;\n }\n\n private function logBatchResults(string $objectType, array $crmIds, array $results): void\n {\n $this->log->info(\"[HubSpot] Batch fetched {$objectType}\", [\n 'requested_count' => count($crmIds),\n 'returned_count' => count($results),\n 'crm_ids' => $crmIds,\n ]);\n }\n\n private function handleBatchError(\\Throwable $e, string $objectType, array $crmIds): void\n {\n $errorMessage = $e->getMessage() ?: 'Unknown error';\n $errorTrace = $e->getTraceAsString() ?: 'No trace available';\n\n $this->log->error(\"[HubSpot] Failed to batch fetch {$objectType}\", [\n 'crm_ids' => $crmIds,\n 'error' => $errorMessage,\n 'trace' => $errorTrace,\n ]);\n\n throw new CrmException(\"Failed to batch fetch {$objectType}: \" . $errorMessage);\n }\n\n /**\n * Batch read multiple opportunities by their CRM IDs\n *\n * @param array<string> $crmIds Array of HubSpot deal IDs (max 100)\n * @param array<string> $fields Array of property names to fetch\n *\n * @return array<string, array> Array keyed by CRM ID with opportunity data\n */\n public function getOpportunitiesByIds(array $crmIds, array $fields): array\n {\n return $this->batchReadObjects('deals', $crmIds, $fields);\n }\n\n /**\n * Batch read multiple companies by their CRM IDs\n *\n * @param array<string> $crmIds Array of HubSpot company IDs (max 100)\n * @param array<string> $fields Array of property names to fetch\n *\n * @return array<string, array> Array keyed by CRM ID with company data\n */\n public function getCompaniesByIds(array $crmIds, array $fields): array\n {\n return $this->batchReadObjects('companies', $crmIds, $fields);\n }\n\n /**\n * Batch read multiple contacts by their CRM IDs\n *\n * @param array<string> $crmIds Array of HubSpot contact IDs (max 100)\n * @param array<string> $fields Array of property names to fetch\n *\n * @return array<string, array> Array keyed by CRM ID with contact data\n */\n public function getContactsByIds(array $crmIds, array $fields): array\n {\n return $this->batchReadObjects('contacts', $crmIds, $fields);\n }\n\n /**\n * @throws CompanyApiException\n * @throws CrmException\n */\n public function getAccountById(string $crmId, array $fields): array\n {\n try {\n $company = $this->getNewInstance()->crm()->companies()->basicApi()->getById(\n $crmId,\n implode(',', $fields),\n );\n } catch (CompanyApiException $e) {\n $this->log->info('[Hubspot] Failed to fetch account', [\n 'crm_id' => $crmId,\n 'reason' => $e->getMessage(),\n ]);\n\n throw $e;\n }\n\n if (! $company instanceof CompaniesWithAssociations) {\n throw new CrmException('Account not found');\n }\n\n return [\n 'id' => $company->getId(),\n 'properties' => $company->getProperties(),\n ];\n }\n\n /**\n * @throws ContactApiException\n * @throws CrmException\n */\n public function getContactById(string $crmId, array $fields): array\n {\n try {\n $contact = $this->getNewInstance()->crm()->contacts()->basicApi()->getById(\n $crmId,\n implode(',', $fields)\n );\n } catch (ContactApiException $e) {\n $this->log->info('[Hubspot] Failed to fetch contact', [\n 'crm_id' => $crmId,\n 'reason' => $e->getMessage(),\n ]);\n\n throw $e;\n }\n\n if (! $contact instanceof ContactsWithAssociations) {\n throw new CrmException('Contact not found');\n }\n\n return [\n 'id' => $contact->getId(),\n 'properties' => $contact->getProperties(),\n ];\n }\n\n /**\n * This is email search request that Hubspot offers as GET (more generous quota)\n */\n public function getContactByEmail(string $email, array $fields = []): array\n {\n try {\n $contact = $this->getNewInstance()->crm()->contacts()->basicApi()->getById(\n $email,\n implode(',', $fields),\n null,\n false,\n 'email'\n );\n\n return [\n 'id' => $contact->getId(),\n 'properties' => $contact->getProperties(),\n ];\n } catch (ContactApiException $e) {\n $this->log->info('[Hubspot] Failed to fetch contact', [\n 'email' => $email,\n 'reason' => $e->getMessage(),\n ]);\n\n return [];\n }\n }\n\n /**\n * @throws CrmException\n */\n public function fetchProperty(string $objectType, string $propertyId): Property\n {\n $result = $this->getNewInstance()->crm()->properties()->coreApi()->getByName($objectType, $propertyId);\n\n if (! $result instanceof Property) {\n $this->log->error('[Hubspot] Failed to fetch property', [\n 'object_type' => $objectType,\n 'property_id' => $propertyId,\n 'reason' => $result->getMessage(),\n ]);\n\n throw new CrmException('Failed to fetch property');\n }\n\n return $result;\n }\n\n /**\n * @return array<CrmFieldOption>\n */\n public function fetchPropertyOptions(string $objectType, string $propertyId): array\n {\n /** @var array<CrmFieldOption> */\n return $this->fetchProperty($objectType, $propertyId)->getOptions();\n }\n\n /**\n * @return array<array{id:string, label:string, deleted:bool}>\n */\n public function fetchCallDispositions(): array\n {\n /** @var Response $response */\n $response = $this->getInstance()->engagements()->getCallDispositions();\n\n /**\n * @var array<array{\n * id:string,\n * label:string,\n * deleted: bool\n * }>\n */\n return $response->toArray();\n }\n\n /**\n * @return array<CrmFieldOption>\n */\n public function fetchOpportunityPipelineStages(): array\n {\n $stages = [];\n $apiResponse = $this->getNewInstance()->crm()->pipelines()->pipelinesApi()->getAll('deals');\n\n if ($apiResponse instanceof Error) {\n $this->log->error('[Hubspot] Failed to fetch opportunity pipelines', [\n 'reason' => $apiResponse->getMessage(),\n ]);\n\n return [];\n }\n\n foreach ($apiResponse->getResults() as $pipeline) {\n $pipelineStages = array_map(\n static function (PipelineStage $stage) {\n return [\n 'id' => $stage->getId(),\n 'label' => $stage->getLabel(),\n ];\n },\n $pipeline->getStages()\n );\n\n $stages = array_merge($stages, $pipelineStages);\n }\n\n return $stages;\n }\n\n public function fetchOpportunityPipelines(): array\n {\n $pipelines = [];\n\n try {\n $apiResponse = $this->makeRequest('/crm/v3/pipelines/deals');\n } catch (\\Exception $e) {\n $this->log->info('[Hubspot] Failed to fetch opportunity pipelines', [\n 'reason' => $e->getMessage(),\n ]);\n\n return [];\n }\n\n $response = $apiResponse->toArray();\n\n foreach ($response['results'] as $pipeline) {\n $pipelines[] = [\n 'id' => $pipeline['id'],\n 'label' => $pipeline['label'],\n ];\n }\n\n return $pipelines;\n }\n\n /**\n * @return array<CrmFieldOption>\n */\n public function fetchMeetingOutcomeFieldOptions(Field $field): array\n {\n return $field->getCrmProviderId() === 'meetingOutcome'\n ? $this->fetchMeetingOutcomeTypes()\n : $this->fetchCallActivityTypes();\n }\n\n public function fetchMeetingOutcomeTypes(): array\n {\n return $this->extractMeetingTypeOptions(\n 'https://api.hubapi.com/crm/v3/properties/meeting/hs_meeting_outcome'\n );\n }\n\n public function fetchCallActivityTypes(): array\n {\n return $this->extractMeetingTypeOptions(\n 'https://api.hubapi.com/crm/v3/properties/call/hs_activity_type'\n );\n }\n\n private function extractMeetingTypeOptions(string $endpoint): array\n {\n /** @var Response $response */\n $response = $this->getInstance()\n ->getClient()\n ->request('GET', $endpoint);\n\n /**\n * @var array<array{\n * value: string,\n * label: string,\n * displayOrder: int\n * }> $optionData\n */\n $optionData = $response->toArray()['options'] ?? [];\n\n $options = [];\n foreach ($optionData as $item) {\n $options[] = [\n 'id' => $item['value'],\n 'value' => $item['value'],\n 'label' => $item['label'],\n 'display_order' => $item['displayOrder'],\n ];\n }\n\n return $options;\n }\n\n /**\n * @return array<CrmFieldOption>\n */\n public function fetchDispositionFieldOptions(): array\n {\n $options = [];\n\n $dispositions = $this->fetchCallDispositions();\n\n foreach ($dispositions as $disposition) {\n if ($disposition['deleted'] !== false) {\n continue;\n }\n\n $option['value'] = $disposition['id'];\n $option['id'] = $disposition['id'];\n $option['label'] = $disposition['label'];\n\n $options[] = $option;\n }\n\n return $options;\n }\n\n /**\n * @return array<CrmFieldOption>\n */\n public function fetchOpportunityFieldOptions(Field $field): array\n {\n if ($field->isStageField()) {\n return $this->fetchOpportunityPipelineStages();\n }\n\n if ($field->isPipelineField()) {\n return $this->fetchOpportunityPipelines();\n }\n\n return $this->fetchPropertyOptions('deals', $field->getCrmProviderId());\n }\n\n /**\n * @throws BadRequest\n * @throws HubspotException\n */\n public function makeRequest(string $endpoint, $method = 'GET', $payload = [], ?string $queryString = null)\n {\n $endpoint = self::BASE_URL . $endpoint;\n\n if ($method === 'GET') {\n $response = $this->getInstance()->getClient()?->request(\n method: $method,\n endpoint: $endpoint,\n query_string: $queryString\n );\n } else {\n $response = $this->getInstance()->getClient()->request($method, $endpoint, [\n 'json' => ($payload),\n ]);\n }\n\n $max = $response->getHeaderLine('X-HubSpot-RateLimit-Max'); // \"110\"\n $remaining = $response->getHeaderLine('X-HubSpot-RateLimit-Remaining'); // \"109\"\n $interval = $response->getHeaderLine('X-HubSpot-RateLimit-Interval-Milliseconds'); // \"10000\"\n $body = json_decode((string) $response->getBody(), true);\n\n \\Illuminate\\Support\\Facades\\Log::channel('custom_channel')->info('$max ' . PHP_EOL . print_r($max, true));\n \\Illuminate\\Support\\Facades\\Log::channel('custom_channel')->info('$remaining ' . PHP_EOL . print_r($remaining, true));\n \\Illuminate\\Support\\Facades\\Log::channel('custom_channel')->info('$interval ' . PHP_EOL . print_r($interval, true));\n \\Illuminate\\Support\\Facades\\Log::channel('custom_channel')->info('$body ' . PHP_EOL . print_r($body, true));\n\n return $response;\n }\n\n /**\n * @throws BadRequest\n * @throws HubspotException\n */\n public function createMeeting(array $payload): Response\n {\n $endpoint = '/crm/v3/objects/meetings';\n\n return $this->makeRequest($endpoint, 'POST', $payload);\n }\n\n /**\n * @throws BadRequest\n * @throws HubspotException\n */\n public function updateMeeting(string $meetingId, array $payload): Response\n {\n $endpoint = '/crm/v3/objects/meetings/' . $meetingId;\n\n return $this->makeRequest($endpoint, 'PATCH', $payload);\n }\n\n /**\n * @throws \\Exception\n */\n public function createNote(\n string $body,\n string $ownerId,\n int $timestamp,\n string $objectId,\n NoteObject $noteObject\n ): ?string {\n try {\n $noteInput = new SimplePublicObjectInput([\n 'properties' => [\n 'hs_note_body' => $body,\n 'hubspot_owner_id' => $ownerId,\n 'hs_timestamp' => $timestamp,\n ],\n ]);\n\n // Create note\n $note = $this->getNewInstance()->crm()->objects()->basicApi()->create('note', $noteInput);\n\n $this->getNewInstance()->crm()->objects()->associationsApi()->create(\n 'note',\n $note->getId(),\n $this->getNoteObject($noteObject),\n $objectId,\n $this->getNoteAssociationType($noteObject),\n );\n\n return $note->getId();\n } catch (\\Exception $e) {\n $this->log->error('[Hubspot] Failed to create note', [\n 'objectId' => $objectId,\n 'noteObject' => $noteObject->getObjectType(),\n 'reason' => $e->getMessage(),\n ]);\n\n \\Sentry::captureException($e);\n }\n\n return null;\n }\n\n public function updateEngagement(string $objectId, array $engagement, array $metadata): void\n {\n $this->getInstance()->engagements()->update($objectId, $engagement, $metadata);\n }\n\n public function getEngagementData(string $engagementId): array\n {\n $engagement = $this->getInstance()->engagements()->get($engagementId);\n\n return $engagement->toArray();\n }\n\n public function createEngagement(array $engagement, array $associations, array $metadata): Response\n {\n return $this->getInstance()\n ->engagements()\n ->create($engagement, $associations, $metadata);\n }\n\n public function isUnauthorizedException(\\Exception $e): bool\n {\n // Check for specific HubSpot API exception types first\n if ($e instanceof BadRequest) {\n // BadRequest can contain 401 status codes\n return $e->getCode() === 401;\n }\n\n // Check for HTTP client exceptions with status codes\n if ($e instanceof \\GuzzleHttp\\Exception\\RequestException && $e->hasResponse()) {\n $response = $e->getResponse();\n if ($response !== null) {\n return $response->getStatusCode() === 401;\n }\n }\n\n // Check for Guzzle HTTP exceptions\n if ($e instanceof \\GuzzleHttp\\Exception\\ClientException) {\n return $e->getCode() === 401;\n }\n\n // Fallback to string matching as last resort, but be more specific\n $message = strtolower($e->getMessage());\n\n return str_contains($message, '401 unauthorized') ||\n str_contains($message, 'http 401') ||\n str_contains($message, 'status code 401') ||\n (preg_match('/\\b401\\b/', $message) && str_contains($message, 'unauthorized'));\n }\n\n /**\n * Validates and refreshes the access token if needed before API requests.\n * This ensures long-running processes don't fail due to token expiration.\n *\n * @throws SocialAccountTokenInvalidException\n */\n public function ensureValidToken(): void\n {\n if ($this->oauthAccount === null) {\n return;\n }\n\n $newToken = $this->tokenManager->ensureValidToken($this->oauthAccount);\n if ($newToken !== null) {\n $this->accessToken = $newToken;\n }\n }\n\n public function getConfig()\n {\n return $this->config;\n }\n\n // returns only active (archived=false)\n public function getOwners(): array\n {\n return $this->getNewInstance()->crm()->owners()->getAll();\n }\n\n /**\n * @param bool $archived\n *\n * @return array<Owner>|[]\n */\n public function getOwnersArchived(bool $archived = true): array\n {\n $endpoint = '/crm/v3/owners';\n $queryParams = [\n 'archived' => $archived ? 'true' : 'false',\n ];\n $queryString = http_build_query($queryParams);\n\n $owners = [];\n\n try {\n $response = $this->makeRequest(endpoint: $endpoint, queryString: $queryString);\n $responseData = $response?->toArray();\n\n foreach ($responseData['results'] as $result) {\n try {\n $owners[] = Owner::create($result);\n } catch (Throwable $e) {\n $this->log->error('[HubSpot] Failed to process owner data', [\n 'result' => $result,\n 'error' => $e->getMessage(),\n ]);\n\n continue;\n }\n }\n } catch (Throwable $e) {\n $this->log->error('HubSpot] Failed to fetch owners', [\n 'archived' => $archived,\n 'error' => $e->getMessage(),\n ]);\n\n return [];\n }\n\n return $owners;\n }\n\n public function getMeeting(string $engagementId): ObjectWithAssociations\n {\n return $this->getNewInstance()->crm()->objects()->basicApi()\n ->getById('meeting', $engagementId, null, 'contact,company,deal');\n }\n\n public function deleteEngagement(string $engagementId): void\n {\n $this->getInstance()->engagements()->delete((int) $engagementId);\n }\n\n public function getAssociationsData(array $ids, string $fromObject, string $toObject): array\n {\n $associationData = [];\n $idChunks = array_chunk($ids, self::ASSOCIATIONS_BATCH_SIZE_LIMIT);\n\n foreach ($idChunks as $idChunk) {\n try {\n $batchInput = new \\HubSpot\\Client\\Crm\\Associations\\Model\\BatchInputPublicObjectId();\n $batchInput->setInputs(array_map(function ($id) {\n $publicObjectId = new \\HubSpot\\Client\\Crm\\Associations\\Model\\PublicObjectId();\n $publicObjectId->setId($id);\n\n return $publicObjectId;\n }, $idChunk));\n\n $associatedObjectsData = $this\n ->getNewInstance()\n ->crm()\n ->associations()\n ->batchApi()\n ->read($fromObject, $toObject, $batchInput);\n\n if ($associatedObjectsData instanceof \\HubSpot\\Client\\Crm\\Associations\\Model\\BatchResponsePublicAssociationMulti) {\n foreach ($associatedObjectsData->getResults() as $association) {\n $from = $association->getFrom()->getId();\n $toAssociations = $association->getTo();\n\n if (! empty($toAssociations)) {\n $associationData[$from] = array_map(function ($item) {\n return $item->getId();\n }, $toAssociations);\n }\n }\n }\n } catch (\\Exception $e) {\n $this->log->error('[Hubspot] Failed to fetch associations', [\n 'from_object' => $fromObject,\n 'to_object' => $toObject,\n 'reason' => $e->getMessage(),\n ]);\n }\n }\n\n return $associationData;\n }\n\n /**\n * @throws \\Exception\n */\n private function getNoteAssociationType(NoteObject $noteObject): string\n {\n return match($noteObject) {\n NoteObject::Opportunity => 'note_to_deal',\n NoteObject::Lead, NoteObject::Contact => 'note_to_contact', // or 'note_to_lead' if your portal supports it\n NoteObject::Account => 'note_to_company',\n NoteObject::Call, NoteObject::Event => throw new \\Exception('Not supported'),\n };\n }\n\n /**\n * @throws \\Exception\n */\n private function getNoteObject(NoteObject $noteObject): string\n {\n return match($noteObject) {\n NoteObject::Opportunity => 'deal',\n NoteObject::Lead, NoteObject::Contact => 'contact',\n NoteObject::Account => 'company',\n NoteObject::Call, NoteObject::Event => throw new \\Exception('Not supported'),\n };\n }\n\n public function addAssociations(string $objectType, string $associationType, array $payload): Response\n {\n $endpoint = \"/crm/v4/associations/$objectType/$associationType/batch/create\";\n\n return $this->makeRequest($endpoint, 'POST', $payload);\n }\n\n public function removeAssociations(string $objectType, string $associationType, array $payload): Response\n {\n $endpoint = \"/crm/v4/associations/$objectType/$associationType/batch/archive\";\n\n return $this->makeRequest($endpoint, 'POST', $payload);\n }\n}","depth":4,"on_screen":true,"value":"<?php\n\ndeclare(strict_types=1);\n\nnamespace Jiminny\\Services\\Crm\\Hubspot;\n\nuse HubSpot\\Client\\Crm\\Deals\\ApiException as DealApiException;\nuse HubSpot\\Client\\Crm\\Contacts\\ApiException as ContactApiException;\nuse HubSpot\\Client\\Crm\\Companies\\ApiException as CompanyApiException;\nuse HubSpot\\Client\\Crm\\Contacts\\Model\\SimplePublicObjectWithAssociations as ContactsWithAssociations;\nuse HubSpot\\Client\\Crm\\Companies\\Model\\SimplePublicObjectWithAssociations as CompaniesWithAssociations;\nuse HubSpot\\Client\\Crm\\Deals\\Model\\SimplePublicObjectWithAssociations as DealWithAssociations;\nuse HubSpot\\Client\\Crm\\Objects\\Model\\SimplePublicObjectInput;\nuse HubSpot\\Client\\Crm\\Objects\\Model\\SimplePublicObjectWithAssociations as ObjectWithAssociations;\nuse HubSpot\\Client\\Crm\\Pipelines\\Model\\Error;\nuse HubSpot\\Client\\Crm\\Pipelines\\Model\\PipelineStage;\nuse HubSpot\\Client\\Crm\\Properties\\Model\\Property;\nuse HubSpot\\Discovery\\Discovery;\nuse Jiminny\\Component\\Utility\\Service\\ProviderRateLimiter;\nuse Jiminny\\Exceptions\\CrmException;\nuse Jiminny\\Exceptions\\RateLimitException;\nuse Jiminny\\Exceptions\\SocialAccountTokenInvalidException;\nuse Jiminny\\Jobs\\Crm\\NoteObject;\nuse Jiminny\\Models\\Crm\\Field;\nuse Jiminny\\Services\\Crm\\BaseClient;\nuse Jiminny\\Services\\Crm\\Hubspot\\DTO\\Response\\Owner;\nuse Jiminny\\Services\\SocialAccountService;\nuse SevenShores\\Hubspot\\Exceptions\\BadRequest;\nuse SevenShores\\Hubspot\\Exceptions\\HubspotException;\nuse SevenShores\\Hubspot\\Factory;\nuse SevenShores\\Hubspot\\Http\\Response;\nuse Jiminny\\Services\\Crm\\Hubspot\\Pagination\\HubspotPaginationService;\nuse Throwable;\n\n/**\n * @phpstan-type CrmFieldOption array{id:string, label:string, value?:string}\n */\nclass Client extends BaseClient implements HubspotClientInterface\n{\n public const string MIN_API_VERSION = '2';\n\n public const string BASE_URL = 'https://api.hubapi.com';\n\n public const int ASSOCIATIONS_BATCH_SIZE_LIMIT = 1000;\n\n private HubspotPaginationService $paginationService;\n private HubspotTokenManager $tokenManager;\n private ProviderRateLimiter $rateLimiter;\n\n public function __construct(\n SocialAccountService $socialAccountService,\n HubspotPaginationService $paginationService,\n HubspotTokenManager $tokenManager,\n ProviderRateLimiter $rateLimiter,\n ) {\n parent::__construct($socialAccountService);\n $this->paginationService = $paginationService;\n $this->tokenManager = $tokenManager;\n $this->rateLimiter = $rateLimiter;\n\n $this->setBaseUrl(self::BASE_URL);\n $this->setVersion(self::MIN_API_VERSION);\n }\n\n /**\n * Single entry point for every HubSpot API call. Enforces the per-portal\n * rate limit configured in the rate_limits table (morphed to the current\n * Configuration) and reacts to a real 429 from HubSpot by translating it\n * into a RateLimitException carrying Retry-After.\n *\n * Wrap any outbound HubSpot call (SDK or raw HTTP) like:\n *\n * $this->executeRequest(fn () => $this->getNewInstance()->crm()->...);\n *\n * @template T\n * @param callable(): T $apiCall\n * @return T\n *\n * @throws RateLimitException\n */\n private function executeRequest(callable $apiCall)\n {\n if (! $this->rateLimiter->canMakeRequest($this->config)) {\n $retryAfter = $this->rateLimiter->requestAvailableIn($this->config);\n\n $this->log->warning('[Hubspot] Rate limit exceeded, deferring request', [\n 'team_id' => $this->config->team_id,\n 'config_id' => $this->config->getId(),\n 'retry_after' => $retryAfter,\n ]);\n\n throw new RateLimitException(\n 'Hubspot rate limit reached for configuration ' . $this->config->getId(),\n $retryAfter,\n );\n }\n\n $this->rateLimiter->incrementRequestCount($this->config);\n\n try {\n return $apiCall();\n } catch (Throwable $e) {\n if ($this->isHubspotRateLimit($e)) {\n $retryAfter = $this->parseRetryAfter($e);\n\n $this->log->warning('[Hubspot] Received 429 from API', [\n 'team_id' => $this->config->team_id,\n 'config_id' => $this->config->getId(),\n 'retry_after' => $retryAfter,\n 'reason' => $e->getMessage(),\n ]);\n\n throw new RateLimitException('Hubspot returned 429', $retryAfter, $e);\n }\n\n throw $e;\n }\n }\n\n private function isHubspotRateLimit(Throwable $e): bool\n {\n return method_exists($e, 'getCode') && (int) $e->getCode() === 429;\n }\n\n private function parseRetryAfter(Throwable $e): int\n {\n if (method_exists($e, 'getResponseHeaders')) {\n $headers = $e->getResponseHeaders() ?: [];\n $value = $headers['Retry-After'] ?? $headers['retry-after'] ?? null;\n if (is_array($value)) {\n $value = $value[0] ?? null;\n }\n if (is_numeric($value)) {\n return (int) $value;\n }\n }\n\n return 10;\n }\n\n public function getMinimumApiVersion(): string\n {\n return self::MIN_API_VERSION;\n }\n\n public function getInstance(): Factory\n {\n return new Factory([\n 'key' => $this->accessToken,\n 'oauth2' => true,\n 'base_url' => $this->baseUrl,\n ]);\n }\n\n public function getNewInstance(): Discovery\n {\n return \\HubSpot\\Factory::createWithAccessToken($this->accessToken);\n }\n\n /**\n * Secondly and daily limits for Hubspot API\n *\n * Product Tier: Free & Starter | Professional & Enterprise | API add-on (any tier)\n * Burst: 100/10 seconds | 150/10 seconds | 200/10 seconds\n * Daily: 250,000 | 500,000 | 1,000,000\n *\n * Official documentation states: The search endpoints are rate limited to five requests per second.\n * Since with 5 RPS were still hitting secondly rate limits we lowered it to 4\n */\n public function getPaginatedData(array $payload, string $type, int $offset = 0): array\n {\n $total = 0;\n $lastId = null;\n $rows = [];\n foreach ($this->getPaginatedDataGenerator($payload, $type, $offset, $total, $lastId) as $row) {\n $rows[] = $row;\n }\n\n return ['results' => $rows, 'total' => $total, 'last_record' => $lastId];\n }\n\n /**\n * @throws HubspotException\n * @throws SocialAccountTokenInvalidException\n * @throws BadRequest\n */\n public function getPaginatedDataGenerator(\n array $payload,\n string $type,\n int $offset = 0,\n int &$total = 0,\n ?string &$lastRecordId = null\n ): \\Generator {\n return $this->paginationService->getPaginatedDataGenerator(\n $this,\n $payload,\n $type,\n $offset,\n $total,\n $lastRecordId\n );\n }\n\n /**\n * @throws DealApiException\n * @throws CrmException\n */\n public function getOpportunityById(string $crmId, array $fields): array\n {\n try {\n $deal = $this->executeRequest(fn () => $this->getNewInstance()->crm()->deals()->basicApi()->getById(\n $crmId,\n implode(',', $fields),\n 'companies,contacts'\n ));\n } catch (DealApiException $e) {\n $this->log->info('[Hubspot] Failed to fetch opportunity', [\n 'crm_id' => $crmId,\n 'reason' => $e->getMessage(),\n ]);\n\n throw $e;\n }\n\n if (! $deal instanceof DealWithAssociations) {\n throw new CrmException('Deal not found');\n }\n\n return [\n 'id' => $deal->getId(),\n 'properties' => $deal->getProperties(),\n 'associations' => $deal->getAssociations(),\n ];\n }\n\n /**\n * Generic batch read method for HubSpot objects\n *\n * @param string $objectType The object type ('deals', 'companies', 'contacts')\n * @param array<string> $crmIds Array of HubSpot object IDs (max 100)\n * @param array<string> $fields Array of property names to fetch\n *\n * @return array<string, array> Array keyed by CRM ID with object data\n */\n private function batchReadObjects(string $objectType, array $crmIds, array $fields): array\n {\n if (empty($crmIds)) {\n return [];\n }\n\n $this->validateBatchSize($objectType, $crmIds);\n $this->ensureValidToken();\n\n try {\n $batchConfig = $this->createBatchConfiguration($objectType);\n $batchReadRequest = $this->prepareBatchRequest($batchConfig, $crmIds, $fields);\n $response = $batchConfig['api']->read($batchReadRequest);\n\n $this->validateApiResponse($response, $objectType);\n\n $results = $this->processApiResults($response);\n $this->logBatchResults($objectType, $crmIds, $results);\n\n return $results;\n } catch (\\Throwable $e) {\n $this->handleBatchError($e, $objectType, $crmIds);\n }\n }\n\n private function validateBatchSize(string $objectType, array $crmIds): void\n {\n if (count($crmIds) > 100) {\n throw new \\InvalidArgumentException(\"Batch size cannot exceed 100 {$objectType}\");\n }\n }\n\n private function createBatchConfiguration(string $objectType): array\n {\n $configurations = [\n 'deals' => [\n 'batchReadRequest' => new \\HubSpot\\Client\\Crm\\Deals\\Model\\BatchReadInputSimplePublicObjectId(),\n 'inputClass' => \\HubSpot\\Client\\Crm\\Deals\\Model\\SimplePublicObjectId::class,\n 'api' => $this->getNewInstance()->crm()->deals()->batchApi(),\n ],\n 'companies' => [\n 'batchReadRequest' => new \\HubSpot\\Client\\Crm\\Companies\\Model\\BatchReadInputSimplePublicObjectId(),\n 'inputClass' => \\HubSpot\\Client\\Crm\\Companies\\Model\\SimplePublicObjectId::class,\n 'api' => $this->getNewInstance()->crm()->companies()->batchApi(),\n ],\n 'contacts' => [\n 'batchReadRequest' => new \\HubSpot\\Client\\Crm\\Contacts\\Model\\BatchReadInputSimplePublicObjectId(),\n 'inputClass' => \\HubSpot\\Client\\Crm\\Contacts\\Model\\SimplePublicObjectId::class,\n 'api' => $this->getNewInstance()->crm()->contacts()->batchApi(),\n ],\n ];\n\n if (! isset($configurations[$objectType])) {\n throw new \\InvalidArgumentException(\"Unsupported object type: {$objectType}\");\n }\n\n return $configurations[$objectType];\n }\n\n private function prepareBatchRequest(array $batchConfig, array $crmIds, array $fields): object\n {\n $batchReadRequest = $batchConfig['batchReadRequest'];\n $inputClass = $batchConfig['inputClass'];\n\n $inputs = array_map(function ($crmId) use ($inputClass) {\n $input = new $inputClass();\n $input->setId($crmId);\n\n return $input;\n }, $crmIds);\n\n $batchReadRequest->setInputs($inputs);\n $batchReadRequest->setProperties($fields);\n\n return $batchReadRequest;\n }\n\n private function validateApiResponse($response, string $objectType): void\n {\n if (! $response) {\n throw new CrmException(\"HubSpot API returned null response for {$objectType} batch read\");\n }\n }\n\n private function processApiResults($response): array\n {\n $results = [];\n $responseResults = $response->getResults();\n\n if ($responseResults) {\n foreach ($responseResults as $object) {\n if ($object && $object->getId()) {\n $results[$object->getId()] = [\n 'id' => $object->getId(),\n 'properties' => $object->getProperties() ?: [],\n ];\n }\n }\n }\n\n return $results;\n }\n\n private function logBatchResults(string $objectType, array $crmIds, array $results): void\n {\n $this->log->info(\"[HubSpot] Batch fetched {$objectType}\", [\n 'requested_count' => count($crmIds),\n 'returned_count' => count($results),\n 'crm_ids' => $crmIds,\n ]);\n }\n\n private function handleBatchError(\\Throwable $e, string $objectType, array $crmIds): void\n {\n $errorMessage = $e->getMessage() ?: 'Unknown error';\n $errorTrace = $e->getTraceAsString() ?: 'No trace available';\n\n $this->log->error(\"[HubSpot] Failed to batch fetch {$objectType}\", [\n 'crm_ids' => $crmIds,\n 'error' => $errorMessage,\n 'trace' => $errorTrace,\n ]);\n\n throw new CrmException(\"Failed to batch fetch {$objectType}: \" . $errorMessage);\n }\n\n /**\n * Batch read multiple opportunities by their CRM IDs\n *\n * @param array<string> $crmIds Array of HubSpot deal IDs (max 100)\n * @param array<string> $fields Array of property names to fetch\n *\n * @return array<string, array> Array keyed by CRM ID with opportunity data\n */\n public function getOpportunitiesByIds(array $crmIds, array $fields): array\n {\n return $this->batchReadObjects('deals', $crmIds, $fields);\n }\n\n /**\n * Batch read multiple companies by their CRM IDs\n *\n * @param array<string> $crmIds Array of HubSpot company IDs (max 100)\n * @param array<string> $fields Array of property names to fetch\n *\n * @return array<string, array> Array keyed by CRM ID with company data\n */\n public function getCompaniesByIds(array $crmIds, array $fields): array\n {\n return $this->batchReadObjects('companies', $crmIds, $fields);\n }\n\n /**\n * Batch read multiple contacts by their CRM IDs\n *\n * @param array<string> $crmIds Array of HubSpot contact IDs (max 100)\n * @param array<string> $fields Array of property names to fetch\n *\n * @return array<string, array> Array keyed by CRM ID with contact data\n */\n public function getContactsByIds(array $crmIds, array $fields): array\n {\n return $this->batchReadObjects('contacts', $crmIds, $fields);\n }\n\n /**\n * @throws CompanyApiException\n * @throws CrmException\n */\n public function getAccountById(string $crmId, array $fields): array\n {\n try {\n $company = $this->getNewInstance()->crm()->companies()->basicApi()->getById(\n $crmId,\n implode(',', $fields),\n );\n } catch (CompanyApiException $e) {\n $this->log->info('[Hubspot] Failed to fetch account', [\n 'crm_id' => $crmId,\n 'reason' => $e->getMessage(),\n ]);\n\n throw $e;\n }\n\n if (! $company instanceof CompaniesWithAssociations) {\n throw new CrmException('Account not found');\n }\n\n return [\n 'id' => $company->getId(),\n 'properties' => $company->getProperties(),\n ];\n }\n\n /**\n * @throws ContactApiException\n * @throws CrmException\n */\n public function getContactById(string $crmId, array $fields): array\n {\n try {\n $contact = $this->getNewInstance()->crm()->contacts()->basicApi()->getById(\n $crmId,\n implode(',', $fields)\n );\n } catch (ContactApiException $e) {\n $this->log->info('[Hubspot] Failed to fetch contact', [\n 'crm_id' => $crmId,\n 'reason' => $e->getMessage(),\n ]);\n\n throw $e;\n }\n\n if (! $contact instanceof ContactsWithAssociations) {\n throw new CrmException('Contact not found');\n }\n\n return [\n 'id' => $contact->getId(),\n 'properties' => $contact->getProperties(),\n ];\n }\n\n /**\n * This is email search request that Hubspot offers as GET (more generous quota)\n */\n public function getContactByEmail(string $email, array $fields = []): array\n {\n try {\n $contact = $this->getNewInstance()->crm()->contacts()->basicApi()->getById(\n $email,\n implode(',', $fields),\n null,\n false,\n 'email'\n );\n\n return [\n 'id' => $contact->getId(),\n 'properties' => $contact->getProperties(),\n ];\n } catch (ContactApiException $e) {\n $this->log->info('[Hubspot] Failed to fetch contact', [\n 'email' => $email,\n 'reason' => $e->getMessage(),\n ]);\n\n return [];\n }\n }\n\n /**\n * @throws CrmException\n */\n public function fetchProperty(string $objectType, string $propertyId): Property\n {\n $result = $this->getNewInstance()->crm()->properties()->coreApi()->getByName($objectType, $propertyId);\n\n if (! $result instanceof Property) {\n $this->log->error('[Hubspot] Failed to fetch property', [\n 'object_type' => $objectType,\n 'property_id' => $propertyId,\n 'reason' => $result->getMessage(),\n ]);\n\n throw new CrmException('Failed to fetch property');\n }\n\n return $result;\n }\n\n /**\n * @return array<CrmFieldOption>\n */\n public function fetchPropertyOptions(string $objectType, string $propertyId): array\n {\n /** @var array<CrmFieldOption> */\n return $this->fetchProperty($objectType, $propertyId)->getOptions();\n }\n\n /**\n * @return array<array{id:string, label:string, deleted:bool}>\n */\n public function fetchCallDispositions(): array\n {\n /** @var Response $response */\n $response = $this->getInstance()->engagements()->getCallDispositions();\n\n /**\n * @var array<array{\n * id:string,\n * label:string,\n * deleted: bool\n * }>\n */\n return $response->toArray();\n }\n\n /**\n * @return array<CrmFieldOption>\n */\n public function fetchOpportunityPipelineStages(): array\n {\n $stages = [];\n $apiResponse = $this->getNewInstance()->crm()->pipelines()->pipelinesApi()->getAll('deals');\n\n if ($apiResponse instanceof Error) {\n $this->log->error('[Hubspot] Failed to fetch opportunity pipelines', [\n 'reason' => $apiResponse->getMessage(),\n ]);\n\n return [];\n }\n\n foreach ($apiResponse->getResults() as $pipeline) {\n $pipelineStages = array_map(\n static function (PipelineStage $stage) {\n return [\n 'id' => $stage->getId(),\n 'label' => $stage->getLabel(),\n ];\n },\n $pipeline->getStages()\n );\n\n $stages = array_merge($stages, $pipelineStages);\n }\n\n return $stages;\n }\n\n public function fetchOpportunityPipelines(): array\n {\n $pipelines = [];\n\n try {\n $apiResponse = $this->makeRequest('/crm/v3/pipelines/deals');\n } catch (\\Exception $e) {\n $this->log->info('[Hubspot] Failed to fetch opportunity pipelines', [\n 'reason' => $e->getMessage(),\n ]);\n\n return [];\n }\n\n $response = $apiResponse->toArray();\n\n foreach ($response['results'] as $pipeline) {\n $pipelines[] = [\n 'id' => $pipeline['id'],\n 'label' => $pipeline['label'],\n ];\n }\n\n return $pipelines;\n }\n\n /**\n * @return array<CrmFieldOption>\n */\n public function fetchMeetingOutcomeFieldOptions(Field $field): array\n {\n return $field->getCrmProviderId() === 'meetingOutcome'\n ? $this->fetchMeetingOutcomeTypes()\n : $this->fetchCallActivityTypes();\n }\n\n public function fetchMeetingOutcomeTypes(): array\n {\n return $this->extractMeetingTypeOptions(\n 'https://api.hubapi.com/crm/v3/properties/meeting/hs_meeting_outcome'\n );\n }\n\n public function fetchCallActivityTypes(): array\n {\n return $this->extractMeetingTypeOptions(\n 'https://api.hubapi.com/crm/v3/properties/call/hs_activity_type'\n );\n }\n\n private function extractMeetingTypeOptions(string $endpoint): array\n {\n /** @var Response $response */\n $response = $this->getInstance()\n ->getClient()\n ->request('GET', $endpoint);\n\n /**\n * @var array<array{\n * value: string,\n * label: string,\n * displayOrder: int\n * }> $optionData\n */\n $optionData = $response->toArray()['options'] ?? [];\n\n $options = [];\n foreach ($optionData as $item) {\n $options[] = [\n 'id' => $item['value'],\n 'value' => $item['value'],\n 'label' => $item['label'],\n 'display_order' => $item['displayOrder'],\n ];\n }\n\n return $options;\n }\n\n /**\n * @return array<CrmFieldOption>\n */\n public function fetchDispositionFieldOptions(): array\n {\n $options = [];\n\n $dispositions = $this->fetchCallDispositions();\n\n foreach ($dispositions as $disposition) {\n if ($disposition['deleted'] !== false) {\n continue;\n }\n\n $option['value'] = $disposition['id'];\n $option['id'] = $disposition['id'];\n $option['label'] = $disposition['label'];\n\n $options[] = $option;\n }\n\n return $options;\n }\n\n /**\n * @return array<CrmFieldOption>\n */\n public function fetchOpportunityFieldOptions(Field $field): array\n {\n if ($field->isStageField()) {\n return $this->fetchOpportunityPipelineStages();\n }\n\n if ($field->isPipelineField()) {\n return $this->fetchOpportunityPipelines();\n }\n\n return $this->fetchPropertyOptions('deals', $field->getCrmProviderId());\n }\n\n /**\n * @throws BadRequest\n * @throws HubspotException\n */\n public function makeRequest(string $endpoint, $method = 'GET', $payload = [], ?string $queryString = null)\n {\n $endpoint = self::BASE_URL . $endpoint;\n\n if ($method === 'GET') {\n $response = $this->getInstance()->getClient()?->request(\n method: $method,\n endpoint: $endpoint,\n query_string: $queryString\n );\n } else {\n $response = $this->getInstance()->getClient()->request($method, $endpoint, [\n 'json' => ($payload),\n ]);\n }\n\n $max = $response->getHeaderLine('X-HubSpot-RateLimit-Max'); // \"110\"\n $remaining = $response->getHeaderLine('X-HubSpot-RateLimit-Remaining'); // \"109\"\n $interval = $response->getHeaderLine('X-HubSpot-RateLimit-Interval-Milliseconds'); // \"10000\"\n $body = json_decode((string) $response->getBody(), true);\n\n \\Illuminate\\Support\\Facades\\Log::channel('custom_channel')->info('$max ' . PHP_EOL . print_r($max, true));\n \\Illuminate\\Support\\Facades\\Log::channel('custom_channel')->info('$remaining ' . PHP_EOL . print_r($remaining, true));\n \\Illuminate\\Support\\Facades\\Log::channel('custom_channel')->info('$interval ' . PHP_EOL . print_r($interval, true));\n \\Illuminate\\Support\\Facades\\Log::channel('custom_channel')->info('$body ' . PHP_EOL . print_r($body, true));\n\n return $response;\n }\n\n /**\n * @throws BadRequest\n * @throws HubspotException\n */\n public function createMeeting(array $payload): Response\n {\n $endpoint = '/crm/v3/objects/meetings';\n\n return $this->makeRequest($endpoint, 'POST', $payload);\n }\n\n /**\n * @throws BadRequest\n * @throws HubspotException\n */\n public function updateMeeting(string $meetingId, array $payload): Response\n {\n $endpoint = '/crm/v3/objects/meetings/' . $meetingId;\n\n return $this->makeRequest($endpoint, 'PATCH', $payload);\n }\n\n /**\n * @throws \\Exception\n */\n public function createNote(\n string $body,\n string $ownerId,\n int $timestamp,\n string $objectId,\n NoteObject $noteObject\n ): ?string {\n try {\n $noteInput = new SimplePublicObjectInput([\n 'properties' => [\n 'hs_note_body' => $body,\n 'hubspot_owner_id' => $ownerId,\n 'hs_timestamp' => $timestamp,\n ],\n ]);\n\n // Create note\n $note = $this->getNewInstance()->crm()->objects()->basicApi()->create('note', $noteInput);\n\n $this->getNewInstance()->crm()->objects()->associationsApi()->create(\n 'note',\n $note->getId(),\n $this->getNoteObject($noteObject),\n $objectId,\n $this->getNoteAssociationType($noteObject),\n );\n\n return $note->getId();\n } catch (\\Exception $e) {\n $this->log->error('[Hubspot] Failed to create note', [\n 'objectId' => $objectId,\n 'noteObject' => $noteObject->getObjectType(),\n 'reason' => $e->getMessage(),\n ]);\n\n \\Sentry::captureException($e);\n }\n\n return null;\n }\n\n public function updateEngagement(string $objectId, array $engagement, array $metadata): void\n {\n $this->getInstance()->engagements()->update($objectId, $engagement, $metadata);\n }\n\n public function getEngagementData(string $engagementId): array\n {\n $engagement = $this->getInstance()->engagements()->get($engagementId);\n\n return $engagement->toArray();\n }\n\n public function createEngagement(array $engagement, array $associations, array $metadata): Response\n {\n return $this->getInstance()\n ->engagements()\n ->create($engagement, $associations, $metadata);\n }\n\n public function isUnauthorizedException(\\Exception $e): bool\n {\n // Check for specific HubSpot API exception types first\n if ($e instanceof BadRequest) {\n // BadRequest can contain 401 status codes\n return $e->getCode() === 401;\n }\n\n // Check for HTTP client exceptions with status codes\n if ($e instanceof \\GuzzleHttp\\Exception\\RequestException && $e->hasResponse()) {\n $response = $e->getResponse();\n if ($response !== null) {\n return $response->getStatusCode() === 401;\n }\n }\n\n // Check for Guzzle HTTP exceptions\n if ($e instanceof \\GuzzleHttp\\Exception\\ClientException) {\n return $e->getCode() === 401;\n }\n\n // Fallback to string matching as last resort, but be more specific\n $message = strtolower($e->getMessage());\n\n return str_contains($message, '401 unauthorized') ||\n str_contains($message, 'http 401') ||\n str_contains($message, 'status code 401') ||\n (preg_match('/\\b401\\b/', $message) && str_contains($message, 'unauthorized'));\n }\n\n /**\n * Validates and refreshes the access token if needed before API requests.\n * This ensures long-running processes don't fail due to token expiration.\n *\n * @throws SocialAccountTokenInvalidException\n */\n public function ensureValidToken(): void\n {\n if ($this->oauthAccount === null) {\n return;\n }\n\n $newToken = $this->tokenManager->ensureValidToken($this->oauthAccount);\n if ($newToken !== null) {\n $this->accessToken = $newToken;\n }\n }\n\n public function getConfig()\n {\n return $this->config;\n }\n\n // returns only active (archived=false)\n public function getOwners(): array\n {\n return $this->getNewInstance()->crm()->owners()->getAll();\n }\n\n /**\n * @param bool $archived\n *\n * @return array<Owner>|[]\n */\n public function getOwnersArchived(bool $archived = true): array\n {\n $endpoint = '/crm/v3/owners';\n $queryParams = [\n 'archived' => $archived ? 'true' : 'false',\n ];\n $queryString = http_build_query($queryParams);\n\n $owners = [];\n\n try {\n $response = $this->makeRequest(endpoint: $endpoint, queryString: $queryString);\n $responseData = $response?->toArray();\n\n foreach ($responseData['results'] as $result) {\n try {\n $owners[] = Owner::create($result);\n } catch (Throwable $e) {\n $this->log->error('[HubSpot] Failed to process owner data', [\n 'result' => $result,\n 'error' => $e->getMessage(),\n ]);\n\n continue;\n }\n }\n } catch (Throwable $e) {\n $this->log->error('HubSpot] Failed to fetch owners', [\n 'archived' => $archived,\n 'error' => $e->getMessage(),\n ]);\n\n return [];\n }\n\n return $owners;\n }\n\n public function getMeeting(string $engagementId): ObjectWithAssociations\n {\n return $this->getNewInstance()->crm()->objects()->basicApi()\n ->getById('meeting', $engagementId, null, 'contact,company,deal');\n }\n\n public function deleteEngagement(string $engagementId): void\n {\n $this->getInstance()->engagements()->delete((int) $engagementId);\n }\n\n public function getAssociationsData(array $ids, string $fromObject, string $toObject): array\n {\n $associationData = [];\n $idChunks = array_chunk($ids, self::ASSOCIATIONS_BATCH_SIZE_LIMIT);\n\n foreach ($idChunks as $idChunk) {\n try {\n $batchInput = new \\HubSpot\\Client\\Crm\\Associations\\Model\\BatchInputPublicObjectId();\n $batchInput->setInputs(array_map(function ($id) {\n $publicObjectId = new \\HubSpot\\Client\\Crm\\Associations\\Model\\PublicObjectId();\n $publicObjectId->setId($id);\n\n return $publicObjectId;\n }, $idChunk));\n\n $associatedObjectsData = $this\n ->getNewInstance()\n ->crm()\n ->associations()\n ->batchApi()\n ->read($fromObject, $toObject, $batchInput);\n\n if ($associatedObjectsData instanceof \\HubSpot\\Client\\Crm\\Associations\\Model\\BatchResponsePublicAssociationMulti) {\n foreach ($associatedObjectsData->getResults() as $association) {\n $from = $association->getFrom()->getId();\n $toAssociations = $association->getTo();\n\n if (! empty($toAssociations)) {\n $associationData[$from] = array_map(function ($item) {\n return $item->getId();\n }, $toAssociations);\n }\n }\n }\n } catch (\\Exception $e) {\n $this->log->error('[Hubspot] Failed to fetch associations', [\n 'from_object' => $fromObject,\n 'to_object' => $toObject,\n 'reason' => $e->getMessage(),\n ]);\n }\n }\n\n return $associationData;\n }\n\n /**\n * @throws \\Exception\n */\n private function getNoteAssociationType(NoteObject $noteObject): string\n {\n return match($noteObject) {\n NoteObject::Opportunity => 'note_to_deal',\n NoteObject::Lead, NoteObject::Contact => 'note_to_contact', // or 'note_to_lead' if your portal supports it\n NoteObject::Account => 'note_to_company',\n NoteObject::Call, NoteObject::Event => throw new \\Exception('Not supported'),\n };\n }\n\n /**\n * @throws \\Exception\n */\n private function getNoteObject(NoteObject $noteObject): string\n {\n return match($noteObject) {\n NoteObject::Opportunity => 'deal',\n NoteObject::Lead, NoteObject::Contact => 'contact',\n NoteObject::Account => 'company',\n NoteObject::Call, NoteObject::Event => throw new \\Exception('Not supported'),\n };\n }\n\n public function addAssociations(string $objectType, string $associationType, array $payload): Response\n {\n $endpoint = \"/crm/v4/associations/$objectType/$associationType/batch/create\";\n\n return $this->makeRequest($endpoint, 'POST', $payload);\n }\n\n public function removeAssociations(string $objectType, string $associationType, array $payload): Response\n {\n $endpoint = \"/crm/v4/associations/$objectType/$associationType/batch/archive\";\n\n return $this->makeRequest($endpoint, 'POST', $payload);\n }\n}","role_description":"text entry area","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"Project","depth":3,"on_screen":false,"role_description":"text"},{"role":"AXButton","text":"Project","depth":3,"bounds":{"left":0.011968086,"top":0.047885075,"width":0.024268618,"height":0.024740623},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"New File or Directory…","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.008643617,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Expand Selected","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.008643617,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Collapse All","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.008643617,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Options","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.008643617,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Hide","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.008643617,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false}]...
|
9067406159853117642
|
6666989725209466980
|
click
|
accessibility
|
NULL
|
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
[2026-05-07 11:36:44] local.INFO: Jiminny\Console\Commands\Command::run Memory usage before starting command {"command":"crm:sync-opportunity","memoryBeforeCommandInMb":60.0,"memoryPeakBeforeCommandInMb":99.727} {"correlation_id":"462baf35-d312-4641-9896-e8b15e8fdb82","trace_id":"aeb070fe-166a-48bf-99c0-acfcbfb40c1b"}
[2026-05-07 11:36:44] local.INFO: [SocialAccountService] Fetching token {"socialAccountId":1499,"provider":"hubspot"} {"correlation_id":"462baf35-d312-4641-9896-e8b15e8fdb82","trace_id":"aeb070fe-166a-48bf-99c0-acfcbfb40c1b"}
[2026-05-07 11:36:44] local.INFO: [SocialAccountService] Token retrieved {"socialAccountId":1499,"provider":"hubspot"} {"correlation_id":"462baf35-d312-4641-9896-e8b15e8fdb82","trace_id":"aeb070fe-166a-48bf-99c0-acfcbfb40c1b"}
[2026-05-07 11:36:44] local.INFO: [EncryptedTokenManager] Generating access token. {"mode":"legacy"} {"correlation_id":"462baf35-d312-4641-9896-e8b15e8fdb82","trace_id":"aeb070fe-166a-48bf-99c0-acfcbfb40c1b"}
[2026-05-07 11:36:44] local.INFO: [HubSpot] Syncing opportunities using strategy: lastModified {"team":2} {"correlation_id":"462baf35-d312-4641-9896-e8b15e8fdb82","trace_id":"aeb070fe-166a-48bf-99c0-acfcbfb40c1b"}
[2026-05-07 11:36:45] local.INFO: [Hubspot] Pagination completed {"team_id":2,"endpoint":"[URL_WITH_CREDENTIALS] CrmFieldOption array{id:string, label:string, value?:string}
*/
class Client extends BaseClient implements HubspotClientInterface
{
public const string MIN_API_VERSION = '2';
public const string BASE_URL = '[URL_WITH_CREDENTIALS] T
* @param callable(): T $apiCall
* @return T
*
* @throws RateLimitException
*/
private function executeRequest(callable $apiCall)
{
if (! $this->rateLimiter->canMakeRequest($this->config)) {
$retryAfter = $this->rateLimiter->requestAvailableIn($this->config);
$this->log->warning('[Hubspot] Rate limit exceeded, deferring request', [
'team_id' => $this->config->team_id,
'config_id' => $this->config->getId(),
'retry_after' => $retryAfter,
]);
throw new RateLimitException(
'Hubspot rate limit reached for configuration ' . $this->config->getId(),
$retryAfter,
);
}
$this->rateLimiter->incrementRequestCount($this->config);
try {
return $apiCall();
} catch (Throwable $e) {
if ($this->isHubspotRateLimit($e)) {
$retryAfter = $this->parseRetryAfter($e);
$this->log->warning('[Hubspot] Received 429 from API', [
'team_id' => $this->config->team_id,
'config_id' => $this->config->getId(),
'retry_after' => $retryAfter,
'reason' => $e->getMessage(),
]);
throw new RateLimitException('Hubspot returned 429', $retryAfter, $e);
}
throw $e;
}
}
private function isHubspotRateLimit(Throwable $e): bool
{
return method_exists($e, 'getCode') && (int) $e->getCode() === 429;
}
private function parseRetryAfter(Throwable $e): int
{
if (method_exists($e, 'getResponseHeaders')) {
$headers = $e->getResponseHeaders() ?: [];
$value = $headers['Retry-After'] ?? $headers['retry-after'] ?? null;
if (is_array($value)) {
$value = $value[0] ?? null;
}
if (is_numeric($value)) {
return (int) $value;
}
}
return 10;
}
public function getMinimumApiVersion(): string
{
return self::MIN_API_VERSION;
}
public function getInstance(): Factory
{
return new Factory([
'key' => $this->accessToken,
'oauth2' => true,
'base_url' => $this->baseUrl,
]);
}
public function getNewInstance(): Discovery
{
return \HubSpot\Factory::createWithAccessToken($this->accessToken);
}
/**
* Secondly and daily limits for Hubspot API
*
* Product Tier: Free & Starter | Professional & Enterprise | API add-on (any tier)
* Burst: 100/10 seconds | 150/10 seconds | 200/10 seconds
* Daily: 250,000 | 500,000 | 1,000,000
*
* Official documentation states: The search endpoints are rate limited to five requests per second.
* Since with 5 RPS were still hitting secondly rate limits we lowered it to 4
*/
public function getPaginatedData(array $payload, string $type, int $offset = 0): array
{
$total = 0;
$lastId = null;
$rows = [];
foreach ($this->getPaginatedDataGenerator($payload, $type, $offset, $total, $lastId) as $row) {
$rows[] = $row;
}
return ['results' => $rows, 'total' => $total, 'last_record' => $lastId];
}
/**
* @throws HubspotException
* @throws SocialAccountTokenInvalidException
* @throws BadRequest
*/
public function getPaginatedDataGenerator(
array $payload,
string $type,
int $offset = 0,
int &$total = 0,
?string &$lastRecordId = null
): \Generator {
return $this->paginationService->getPaginatedDataGenerator(
$this,
$payload,
$type,
$offset,
$total,
$lastRecordId
);
}
/**
* @throws DealApiException
* @throws CrmException
*/
public function getOpportunityById(string $crmId, array $fields): array
{
try {
$deal = $this->executeRequest(fn () => $this->getNewInstance()->crm()->deals()->basicApi()->getById(
$crmId,
implode(',', $fields),
'companies,contacts'
));
} catch (DealApiException $e) {
$this->log->info('[Hubspot] Failed to fetch opportunity', [
'crm_id' => $crmId,
'reason' => $e->getMessage(),
]);
throw $e;
}
if (! $deal instanceof DealWithAssociations) {
throw new CrmException('Deal not found');
}
return [
'id' => $deal->getId(),
'properties' => $deal->getProperties(),
'associations' => $deal->getAssociations(),
];
}
/**
* Generic batch read method for HubSpot objects
*
* @param string $objectType The object type ('deals', 'companies', 'contacts')
* @param array<string> $crmIds Array of HubSpot object IDs (max 100)
* @param array<string> $fields Array of property names to fetch
*
* @return array<string, array> Array keyed by CRM ID with object data
*/
private function batchReadObjects(string $objectType, array $crmIds, array $fields): array
{
if (empty($crmIds)) {
return [];
}
$this->validateBatchSize($objectType, $crmIds);
$this->ensureValidToken();
try {
$batchConfig = $this->createBatchConfiguration($objectType);
$batchReadRequest = $this->prepareBatchRequest($batchConfig, $crmIds, $fields);
$response = $batchConfig['api']->read($batchReadRequest);
$this->validateApiResponse($response, $objectType);
$results = $this->processApiResults($response);
$this->logBatchResults($objectType, $crmIds, $results);
return $results;
} catch (\Throwable $e) {
$this->handleBatchError($e, $objectType, $crmIds);
}
}
private function validateBatchSize(string $objectType, array $crmIds): void
{
if (count($crmIds) > 100) {
throw new \InvalidArgumentException("Batch size cannot exceed 100 {$objectType}");
}
}
private function createBatchConfiguration(string $objectType): array
{
$configurations = [
'deals' => [
'batchReadRequest' => new \HubSpot\Client\Crm\Deals\Model\BatchReadInputSimplePublicObjectId(),
'inputClass' => \HubSpot\Client\Crm\Deals\Model\SimplePublicObjectId::class,
'api' => $this->getNewInstance()->crm()->deals()->batchApi(),
],
'companies' => [
'batchReadRequest' => new \HubSpot\Client\Crm\Companies\Model\BatchReadInputSimplePublicObjectId(),
'inputClass' => \HubSpot\Client\Crm\Companies\Model\SimplePublicObjectId::class,
'api' => $this->getNewInstance()->crm()->companies()->batchApi(),
],
'contacts' => [
'batchReadRequest' => new \HubSpot\Client\Crm\Contacts\Model\BatchReadInputSimplePublicObjectId(),
'inputClass' => \HubSpot\Client\Crm\Contacts\Model\SimplePublicObjectId::class,
'api' => $this->getNewInstance()->crm()->contacts()->batchApi(),
],
];
if (! isset($configurations[$objectType])) {
throw new \InvalidArgumentException("Unsupported object type: {$objectType}");
}
return $configurations[$objectType];
}
private function prepareBatchRequest(array $batchConfig, array $crmIds, array $fields): object
{
$batchReadRequest = $batchConfig['batchReadRequest'];
$inputClass = $batchConfig['inputClass'];
$inputs = array_map(function ($crmId) use ($inputClass) {
$input = new $inputClass();
$input->setId($crmId);
return $input;
}, $crmIds);
$batchReadRequest->setInputs($inputs);
$batchReadRequest->setProperties($fields);
return $batchReadRequest;
}
private function validateApiResponse($response, string $objectType): void
{
if (! $response) {
throw new CrmException("HubSpot API returned null response for {$objectType} batch read");
}
}
private function processApiResults($response): array
{
$results = [];
$responseResults = $response->getResults();
if ($responseResults) {
foreach ($responseResults as $object) {
if ($object && $object->getId()) {
$results[$object->getId()] = [
'id' => $object->getId(),
'properties' => $object->getProperties() ?: [],
];
}
}
}
return $results;
}
private function logBatchResults(string $objectType, array $crmIds, array $results): void
{
$this->log->info("[HubSpot] Batch fetched {$objectType}", [
'requested_count' => count($crmIds),
'returned_count' => count($results),
'crm_ids' => $crmIds,
]);
}
private function handleBatchError(\Throwable $e, string $objectType, array $crmIds): void
{
$errorMessage = $e->getMessage() ?: 'Unknown error';
$errorTrace = $e->getTraceAsString() ?: 'No trace available';
$this->log->error("[HubSpot] Failed to batch fetch {$objectType}", [
'crm_ids' => $crmIds,
'error' => $errorMessage,
'trace' => $errorTrace,
]);
throw new CrmException("Failed to batch fetch {$objectType}: " . $errorMessage);
}
/**
* Batch read multiple opportunities by their CRM IDs
*
* @param array<string> $crmIds Array of HubSpot deal IDs (max 100)
* @param array<string> $fields Array of property names to fetch
*
* @return array<string, array> Array keyed by CRM ID with opportunity data
*/
public function getOpportunitiesByIds(array $crmIds, array $fields): array
{
return $this->batchReadObjects('deals', $crmIds, $fields);
}
/**
* Batch read multiple companies by their CRM IDs
*
* @param array<string> $crmIds Array of HubSpot company IDs (max 100)
* @param array<string> $fields Array of property names to fetch
*
* @return array<string, array> Array keyed by CRM ID with company data
*/
public function getCompaniesByIds(array $crmIds, array $fields): array
{
return $this->batchReadObjects('companies', $crmIds, $fields);
}
/**
* Batch read multiple contacts by their CRM IDs
*
* @param array<string> $crmIds Array of HubSpot contact IDs (max 100)
* @param array<string> $fields Array of property names to fetch
*
* @return array<string, array> Array keyed by CRM ID with contact data
*/
public function getContactsByIds(array $crmIds, array $fields): array
{
return $this->batchReadObjects('contacts', $crmIds, $fields);
}
/**
* @throws CompanyApiException
* @throws CrmException
*/
public function getAccountById(string $crmId, array $fields): array
{
try {
$company = $this->getNewInstance()->crm()->companies()->basicApi()->getById(
$crmId,
implode(',', $fields),
);
} catch (CompanyApiException $e) {
$this->log->info('[Hubspot] Failed to fetch account', [
'crm_id' => $crmId,
'reason' => $e->getMessage(),
]);
throw $e;
}
if (! $company instanceof CompaniesWithAssociations) {
throw new CrmException('Account not found');
}
return [
'id' => $company->getId(),
'properties' => $company->getProperties(),
];
}
/**
* @throws ContactApiException
* @throws CrmException
*/
public function getContactById(string $crmId, array $fields): array
{
try {
$contact = $this->getNewInstance()->crm()->contacts()->basicApi()->getById(
$crmId,
implode(',', $fields)
);
} catch (ContactApiException $e) {
$this->log->info('[Hubspot] Failed to fetch contact', [
'crm_id' => $crmId,
'reason' => $e->getMessage(),
]);
throw $e;
}
if (! $contact instanceof ContactsWithAssociations) {
throw new CrmException('Contact not found');
}
return [
'id' => $contact->getId(),
'properties' => $contact->getProperties(),
];
}
/**
* This is email search request that Hubspot offers as GET (more generous quota)
*/
public function getContactByEmail(string $email, array $fields = []): array
{
try {
$contact = $this->getNewInstance()->crm()->contacts()->basicApi()->getById(
$email,
implode(',', $fields),
null,
false,
'email'
);
return [
'id' => $contact->getId(),
'properties' => $contact->getProperties(),
];
} catch (ContactApiException $e) {
$this->log->info('[Hubspot] Failed to fetch contact', [
'email' => $email,
'reason' => $e->getMessage(),
]);
return [];
}
}
/**
* @throws CrmException
*/
public function fetchProperty(string $objectType, string $propertyId): Property
{
$result = $this->getNewInstance()->crm()->properties()->coreApi()->getByName($objectType, $propertyId);
if (! $result instanceof Property) {
$this->log->error('[Hubspot] Failed to fetch property', [
'object_type' => $objectType,
'property_id' => $propertyId,
'reason' => $result->getMessage(),
]);
throw new CrmException('Failed to fetch property');
}
return $result;
}
/**
* @return array<CrmFieldOption>
*/
public function fetchPropertyOptions(string $objectType, string $propertyId): array
{
/** @var array<CrmFieldOption> */
return $this->fetchProperty($objectType, $propertyId)->getOptions();
}
/**
* @return array<array{id:string, label:string, deleted:bool}>
*/
public function fetchCallDispositions(): array
{
/** @var Response $response */
$response = $this->getInstance()->engagements()->getCallDispositions();
/**
* @var array<array{
* id:string,
* label:string,
* deleted: bool
* }>
*/
return $response->toArray();
}
/**
* @return array<CrmFieldOption>
*/
public function fetchOpportunityPipelineStages(): array
{
$stages = [];
$apiResponse = $this->getNewInstance()->crm()->pipelines()->pipelinesApi()->getAll('deals');
if ($apiResponse instanceof Error) {
$this->log->error('[Hubspot] Failed to fetch opportunity pipelines', [
'reason' => $apiResponse->getMessage(),
]);
return [];
}
foreach ($apiResponse->getResults() as $pipeline) {
$pipelineStages = array_map(
static function (PipelineStage $stage) {
return [
'id' => $stage->getId(),
'label' => $stage->getLabel(),
];
},
$pipeline->getStages()
);
$stages = array_merge($stages, $pipelineStages);
}
return $stages;
}
public function fetchOpportunityPipelines(): array
{
$pipelines = [];
try {
$apiResponse = $this->makeRequest('/crm/v3/pipelines/deals');
} catch (\Exception $e) {
$this->log->info('[Hubspot] Failed to fetch opportunity pipelines', [
'reason' => $e->getMessage(),
]);
return [];
}
$response = $apiResponse->toArray();
foreach ($response['results'] as $pipeline) {
$pipelines[] = [
'id' => $pipeline['id'],
'label' => $pipeline['label'],
];
}
return $pipelines;
}
/**
* @return array<CrmFieldOption>
*/
public function fetchMeetingOutcomeFieldOptions(Field $field): array
{
return $field->getCrmProviderId() === 'meetingOutcome'
? $this->fetchMeetingOutcomeTypes()
: $this->fetchCallActivityTypes();
}
public function fetchMeetingOutcomeTypes(): array
{
return $this->extractMeetingTypeOptions(
'[URL_WITH_CREDENTIALS] Response $response */
$response = $this->getInstance()
->getClient()
->request('GET', $endpoint);
/**
* @var array<array{
* value: string,
* label: string,
* displayOrder: int
* }> $optionData
*/
$optionData = $response->toArray()['options'] ?? [];
$options = [];
foreach ($optionData as $item) {
$options[] = [
'id' => $item['value'],
'value' => $item['value'],
'label' => $item['label'],
'display_order' => $item['displayOrder'],
];
}
return $options;
}
/**
* @return array<CrmFieldOption>
*/
public function fetchDispositionFieldOptions(): array
{
$options = [];
$dispositions = $this->fetchCallDispositions();
foreach ($dispositions as $disposition) {
if ($disposition['deleted'] !== false) {
continue;
}
$option['value'] = $disposition['id'];
$option['id'] = $disposition['id'];
$option['label'] = $disposition['label'];
$options[] = $option;
}
return $options;
}
/**
* @return array<CrmFieldOption>
*/
public function fetchOpportunityFieldOptions(Field $field): array
{
if ($field->isStageField()) {
return $this->fetchOpportunityPipelineStages();
}
if ($field->isPipelineField()) {
return $this->fetchOpportunityPipelines();
}
return $this->fetchPropertyOptions('deals', $field->getCrmProviderId());
}
/**
* @throws BadRequest
* @throws HubspotException
*/
public function makeRequest(string $endpoint, $method = 'GET', $payload = [], ?string $queryString = null)
{
$endpoint = self::BASE_URL . $endpoint;
if ($method === 'GET') {
$response = $this->getInstance()->getClient()?->request(
method: $method,
endpoint: $endpoint,
query_string: $queryString
);
} else {
$response = $this->getInstance()->getClient()->request($method, $endpoint, [
'json' => ($payload),
]);
}
$max = $response->getHeaderLine('X-HubSpot-RateLimit-Max'); // "110"
$remaining = $response->getHeaderLine('X-HubSpot-RateLimit-Remaining'); // "109"
$interval = $response->getHeaderLine('X-HubSpot-RateLimit-Interval-Milliseconds'); // "10000"
$body = json_decode((string) $response->getBody(), true);
\Illuminate\Support\Facades\Log::channel('custom_channel')->info('$max ' . PHP_EOL . print_r($max, true));
\Illuminate\Support\Facades\Log::channel('custom_channel')->info('$remaining ' . PHP_EOL . print_r($remaining, true));
\Illuminate\Support\Facades\Log::channel('custom_channel')->info('$interval ' . PHP_EOL . print_r($interval, true));
\Illuminate\Support\Facades\Log::channel('custom_channel')->info('$body ' . PHP_EOL . print_r($body, true));
return $response;
}
/**
* @throws BadRequest
* @throws HubspotException
*/
public function createMeeting(array $payload): Response
{
$endpoint = '/crm/v3/objects/meetings';
return $this->makeRequest($endpoint, 'POST', $payload);
}
/**
* @throws BadRequest
* @throws HubspotException
*/
public function updateMeeting(string $meetingId, array $payload): Response
{
$endpoint = '/crm/v3/objects/meetings/' . $meetingId;
return $this->makeRequest($endpoint, 'PATCH', $payload);
}
/**
* @throws \Exception
*/
public function createNote(
string $body,
string $ownerId,
int $timestamp,
string $objectId,
NoteObject $noteObject
): ?string {
try {
$noteInput = new SimplePublicObjectInput([
'properties' => [
'hs_note_body' => $body,
'hubspot_owner_id' => $ownerId,
'hs_timestamp' => $timestamp,
],
]);
// Create note
$note = $this->getNewInstance()->crm()->objects()->basicApi()->create('note', $noteInput);
$this->getNewInstance()->crm()->objects()->associationsApi()->create(
'note',
$note->getId(),
$this->getNoteObject($noteObject),
$objectId,
$this->getNoteAssociationType($noteObject),
);
return $note->getId();
} catch (\Exception $e) {
$this->log->error('[Hubspot] Failed to create note', [
'objectId' => $objectId,
'noteObject' => $noteObject->getObjectType(),
'reason' => $e->getMessage(),
]);
\Sentry::captureException($e);
}
return null;
}
public function updateEngagement(string $objectId, array $engagement, array $metadata): void
{
$this->getInstance()->engagements()->update($objectId, $engagement, $metadata);
}
public function getEngagementData(string $engagementId): array
{
$engagement = $this->getInstance()->engagements()->get($engagementId);
return $engagement->toArray();
}
public function createEngagement(array $engagement, array $associations, array $metadata): Response
{
return $this->getInstance()
->engagements()
->create($engagement, $associations, $metadata);
}
public function isUnauthorizedException(\Exception $e): bool
{
// Check for specific HubSpot API exception types first
if ($e instanceof BadRequest) {
// BadRequest can contain 401 status codes
return $e->getCode() === 401;
}
// Check for HTTP client exceptions with status codes
if ($e instanceof \GuzzleHttp\Exception\RequestException && $e->hasResponse()) {
$response = $e->getResponse();
if ($response !== null) {
return $response->getStatusCode() === 401;
}
}
// Check for Guzzle HTTP exceptions
if ($e instanceof \GuzzleHttp\Exception\ClientException) {
return $e->getCode() === 401;
}
// Fallback to string matching as last resort, but be more specific
$message = strtolower($e->getMessage());
return str_contains($message, '401 unauthorized') ||
str_contains($message, 'http 401') ||
str_contains($message, 'status code 401') ||
(preg_match('/\b401\b/', $message) && str_contains($message, 'unauthorized'));
}
/**
* Validates and refreshes the access token if needed before API requests.
* This ensures long-running processes don't fail due to token expiration.
*
* @throws SocialAccountTokenInvalidException
*/
public function ensureValidToken(): void
{
if ($this->oauthAccount === null) {
return;
}
$newToken = $this->tokenManager->ensureValidToken($this->oauthAccount);
if ($newToken !== null) {
$this->accessToken = $newToken;
}
}
public function getConfig()
{
return $this->config;
}
// returns only active (archived=false)
public function getOwners(): array
{
return $this->getNewInstance()->crm()->owners()->getAll();
}
/**
* @param bool $archived
*
* @return array<Owner>|[]
*/
public function getOwnersArchived(bool $archived = true): array
{
$endpoint = '/crm/v3/owners';
$queryParams = [
'archived' => $archived ? 'true' : 'false',
];
$queryString = http_build_query($queryParams);
$owners = [];
try {
$response = $this->makeRequest(endpoint: $endpoint, queryString: $queryString);
$responseData = $response?->toArray();
foreach ($responseData['results'] as $result) {
try {
$owners[] = Owner::create($result);
} catch (Throwable $e) {
$this->log->error('[HubSpot] Failed to process owner data', [
'result' => $result,
'error' => $e->getMessage(),
]);
continue;
}
}
} catch (Throwable $e) {
$this->log->error('HubSpot] Failed to fetch owners', [
'archived' => $archived,
'error' => $e->getMessage(),
]);
return [];
}
return $owners;
}
public function getMeeting(string $engagementId): ObjectWithAssociations
{
return $this->getNewInstance()->crm()->objects()->basicApi()
->getById('meeting', $engagementId, null, 'contact,company,deal');
}
public function deleteEngagement(string $engagementId): void
{
$this->getInstance()->engagements()->delete((int) $engagementId);
}
public function getAssociationsData(array $ids, string $fromObject, string $toObject): array
{
$associationData = [];
$idChunks = array_chunk($ids, self::ASSOCIATIONS_BATCH_SIZE_LIMIT);
foreach ($idChunks as $idChunk) {
try {
$batchInput = new \HubSpot\Client\Crm\Associations\Model\BatchInputPublicObjectId();
$batchInput->setInputs(array_map(function ($id) {
$publicObjectId = new \HubSpot\Client\Crm\Associations\Model\PublicObjectId();
$publicObjectId->setId($id);
return $publicObjectId;
}, $idChunk));
$associatedObjectsData = $this
->getNewInstance()
->crm()
->associations()
->batchApi()
->read($fromObject, $toObject, $batchInput);
if ($associatedObjectsData instanceof \HubSpot\Client\Crm\Associations\Model\BatchResponsePublicAssociationMulti) {
foreach ($associatedObjectsData->getResults() as $association) {
$from = $association->getFrom()->getId();
$toAssociations = $association->getTo();
if (! empty($toAssociations)) {
$associationData[$from] = array_map(function ($item) {
return $item->getId();
}, $toAssociations);
}
}
}
} catch (\Exception $e) {
$this->log->error('[Hubspot] Failed to fetch associations', [
'from_object' => $fromObject,
'to_object' => $toObject,
'reason' => $e->getMessage(),
]);
}
}
return $associationData;
}
/**
* @throws \Exception
*/
private function getNoteAssociationType(NoteObject $noteObject): string
{
return match($noteObject) {
NoteObject::Opportunity => 'note_to_deal',
NoteObject::Lead, NoteObject::Contact => 'note_to_contact', // or 'note_to_lead' if your portal supports it
NoteObject::Account => 'note_to_company',
NoteObject::Call, NoteObject::Event => throw new \Exception('Not supported'),
};
}
/**
* @throws \Exception
*/
private function getNoteObject(NoteObject $noteObject): string
{
return match($noteObject) {
NoteObject::Opportunity => 'deal',
NoteObject::Lead, NoteObject::Contact => 'contact',
NoteObject::Account => 'company',
NoteObject::Call, NoteObject::Event => throw new \Exception('Not supported'),
};
}
public function addAssociations(string $objectType, string $associationType, array $payload): Response
{
$endpoint = "/crm/v4/associations/$objectType/$associationType/batch/create";
return $this->makeRequest($endpoint, 'POST', $payload);
}
public function removeAssociations(string $objectType, string $associationType, array $payload): Response
{
$endpoint = "/crm/v4/associations/$objectType/$associationType/batch/archive";
return $this->makeRequest($endpoint, 'POST', $payload);
}
}
Project
Project
New File or Directory…
Expand Selected
Collapse All
Options
Hide...
|
NULL
|
NULL
|
NULL
|
NULL
|
|
2740
|
111
|
41
|
2026-05-07T11:38:05.752756+00:00
|
/Users/lukas/.screenpipe/data/data/2026-05-07/1778 /Users/lukas/.screenpipe/data/data/2026-05-07/1778153885752_m1.jpg...
|
PhpStorm
|
faVsco.js – custom.log
|
True
|
NULL
|
monitor_1
|
NULL
|
NULL
|
NULL
|
NULL
|
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
Editor for custom.log
Sync Changes
Hide This Notification
Code changed:
Hide
2
60
Previous Highlighted Error
Next Highlighted Error
<?php
declare(strict_types=1);
namespace Jiminny\Services\Crm\Hubspot;
use HubSpot\Client\Crm\Deals\ApiException as DealApiException;
use HubSpot\Client\Crm\Contacts\ApiException as ContactApiException;
use HubSpot\Client\Crm\Companies\ApiException as CompanyApiException;
use HubSpot\Client\Crm\Contacts\Model\SimplePublicObjectWithAssociations as ContactsWithAssociations;
use HubSpot\Client\Crm\Companies\Model\SimplePublicObjectWithAssociations as CompaniesWithAssociations;
use HubSpot\Client\Crm\Deals\Model\SimplePublicObjectWithAssociations as DealWithAssociations;
use HubSpot\Client\Crm\Objects\Model\SimplePublicObjectInput;
use HubSpot\Client\Crm\Objects\Model\SimplePublicObjectWithAssociations as ObjectWithAssociations;
use HubSpot\Client\Crm\Pipelines\Model\Error;
use HubSpot\Client\Crm\Pipelines\Model\PipelineStage;
use HubSpot\Client\Crm\Properties\Model\Property;
use HubSpot\Discovery\Discovery;
use Jiminny\Component\Utility\Service\ProviderRateLimiter;
use Jiminny\Exceptions\CrmException;
use Jiminny\Exceptions\RateLimitException;
use Jiminny\Exceptions\SocialAccountTokenInvalidException;
use Jiminny\Jobs\Crm\NoteObject;
use Jiminny\Models\Crm\Field;
use Jiminny\Services\Crm\BaseClient;
use Jiminny\Services\Crm\Hubspot\DTO\Response\Owner;
use Jiminny\Services\SocialAccountService;
use SevenShores\Hubspot\Exceptions\BadRequest;
use SevenShores\Hubspot\Exceptions\HubspotException;
use SevenShores\Hubspot\Factory;
use SevenShores\Hubspot\Http\Response;
use Jiminny\Services\Crm\Hubspot\Pagination\HubspotPaginationService;
use Throwable;
/**
* @phpstan-type CrmFieldOption array{id:string, label:string, value?:string}
*/
class Client extends BaseClient implements HubspotClientInterface
{
public const string MIN_API_VERSION = '2';
public const string BASE_URL = '[URL_WITH_CREDENTIALS] T
* @param callable(): T $apiCall
* @return T
*
* @throws RateLimitException
*/
private function executeRequest(callable $apiCall)
{
if (! $this->rateLimiter->canMakeRequest($this->config)) {
$retryAfter = $this->rateLimiter->requestAvailableIn($this->config);
$this->log->warning('[Hubspot] Rate limit exceeded, deferring request', [
'team_id' => $this->config->team_id,
'config_id' => $this->config->getId(),
'retry_after' => $retryAfter,
]);
throw new RateLimitException(
'Hubspot rate limit reached for configuration ' . $this->config->getId(),
$retryAfter,
);
}
$this->rateLimiter->incrementRequestCount($this->config);
try {
return $apiCall();
} catch (Throwable $e) {
if ($this->isHubspotRateLimit($e)) {
$retryAfter = $this->parseRetryAfter($e);
$this->log->warning('[Hubspot] Received 429 from API', [
'team_id' => $this->config->team_id,
'config_id' => $this->config->getId(),
'retry_after' => $retryAfter,
'reason' => $e->getMessage(),
]);
throw new RateLimitException('Hubspot returned 429', $retryAfter, $e);
}
throw $e;
}
}
private function isHubspotRateLimit(Throwable $e): bool
{
return method_exists($e, 'getCode') && (int) $e->getCode() === 429;
}
private function parseRetryAfter(Throwable $e): int
{
if (method_exists($e, 'getResponseHeaders')) {
$headers = $e->getResponseHeaders() ?: [];
$value = $headers['Retry-After'] ?? $headers['retry-after'] ?? null;
if (is_array($value)) {
$value = $value[0] ?? null;
}
if (is_numeric($value)) {
return (int) $value;
}
}
return 10;
}
public function getMinimumApiVersion(): string
{
return self::MIN_API_VERSION;
}
public function getInstance(): Factory
{
return new Factory([
'key' => $this->accessToken,
'oauth2' => true,
'base_url' => $this->baseUrl,
]);
}
public function getNewInstance(): Discovery
{
return \HubSpot\Factory::createWithAccessToken($this->accessToken);
}
/**
* Secondly and daily limits for Hubspot API
*
* Product Tier: Free & Starter | Professional & Enterprise | API add-on (any tier)
* Burst: 100/10 seconds | 150/10 seconds | 200/10 seconds
* Daily: 250,000 | 500,000 | 1,000,000
*
* Official documentation states: The search endpoints are rate limited to five requests per second.
* Since with 5 RPS were still hitting secondly rate limits we lowered it to 4
*/
public function getPaginatedData(array $payload, string $type, int $offset = 0): array
{
$total = 0;
$lastId = null;
$rows = [];
foreach ($this->getPaginatedDataGenerator($payload, $type, $offset, $total, $lastId) as $row) {
$rows[] = $row;
}
return ['results' => $rows, 'total' => $total, 'last_record' => $lastId];
}
/**
* @throws HubspotException
* @throws SocialAccountTokenInvalidException
* @throws BadRequest
*/
public function getPaginatedDataGenerator(
array $payload,
string $type,
int $offset = 0,
int &$total = 0,
?string &$lastRecordId = null
): \Generator {
return $this->paginationService->getPaginatedDataGenerator(
$this,
$payload,
$type,
$offset,
$total,
$lastRecordId
);
}
/**
* @throws DealApiException
* @throws CrmException
*/
public function getOpportunityById(string $crmId, array $fields): array
{
try {
$deal = $this->executeRequest(fn () => $this->getNewInstance()->crm()->deals()->basicApi()->getById(
$crmId,
implode(',', $fields),
'companies,contacts'
));
} catch (DealApiException $e) {
$this->log->info('[Hubspot] Failed to fetch opportunity', [
'crm_id' => $crmId,
'reason' => $e->getMessage(),
]);
throw $e;
}
if (! $deal instanceof DealWithAssociations) {
throw new CrmException('Deal not found');
}
return [
'id' => $deal->getId(),
'properties' => $deal->getProperties(),
'associations' => $deal->getAssociations(),
];
}
/**
* Generic batch read method for HubSpot objects
*
* @param string $objectType The object type ('deals', 'companies', 'contacts')
* @param array<string> $crmIds Array of HubSpot object IDs (max 100)
* @param array<string> $fields Array of property names to fetch
*
* @return array<string, array> Array keyed by CRM ID with object data
*/
private function batchReadObjects(string $objectType, array $crmIds, array $fields): array
{
if (empty($crmIds)) {
return [];
}
$this->validateBatchSize($objectType, $crmIds);
$this->ensureValidToken();
try {
$batchConfig = $this->createBatchConfiguration($objectType);
$batchReadRequest = $this->prepareBatchRequest($batchConfig, $crmIds, $fields);
$response = $batchConfig['api']->read($batchReadRequest);
$this->validateApiResponse($response, $objectType);
$results = $this->processApiResults($response);
$this->logBatchResults($objectType, $crmIds, $results);
return $results;
} catch (\Throwable $e) {
$this->handleBatchError($e, $objectType, $crmIds);
}
}
private function validateBatchSize(string $objectType, array $crmIds): void
{
if (count($crmIds) > 100) {
throw new \InvalidArgumentException("Batch size cannot exceed 100 {$objectType}");
}
}
private function createBatchConfiguration(string $objectType): array
{
$configurations = [
'deals' => [
'batchReadRequest' => new \HubSpot\Client\Crm\Deals\Model\BatchReadInputSimplePublicObjectId(),
'inputClass' => \HubSpot\Client\Crm\Deals\Model\SimplePublicObjectId::class,
'api' => $this->getNewInstance()->crm()->deals()->batchApi(),
],
'companies' => [
'batchReadRequest' => new \HubSpot\Client\Crm\Companies\Model\BatchReadInputSimplePublicObjectId(),
'inputClass' => \HubSpot\Client\Crm\Companies\Model\SimplePublicObjectId::class,
'api' => $this->getNewInstance()->crm()->companies()->batchApi(),
],
'contacts' => [
'batchReadRequest' => new \HubSpot\Client\Crm\Contacts\Model\BatchReadInputSimplePublicObjectId(),
'inputClass' => \HubSpot\Client\Crm\Contacts\Model\SimplePublicObjectId::class,
'api' => $this->getNewInstance()->crm()->contacts()->batchApi(),
],
];
if (! isset($configurations[$objectType])) {
throw new \InvalidArgumentException("Unsupported object type: {$objectType}");
}
return $configurations[$objectType];
}
private function prepareBatchRequest(array $batchConfig, array $crmIds, array $fields): object
{
$batchReadRequest = $batchConfig['batchReadRequest'];
$inputClass = $batchConfig['inputClass'];
$inputs = array_map(function ($crmId) use ($inputClass) {
$input = new $inputClass();
$input->setId($crmId);
return $input;
}, $crmIds);
$batchReadRequest->setInputs($inputs);
$batchReadRequest->setProperties($fields);
return $batchReadRequest;
}
private function validateApiResponse($response, string $objectType): void
{
if (! $response) {
throw new CrmException("HubSpot API returned null response for {$objectType} batch read");
}
}
private function processApiResults($response): array
{
$results = [];
$responseResults = $response->getResults();
if ($responseResults) {
foreach ($responseResults as $object) {
if ($object && $object->getId()) {
$results[$object->getId()] = [
'id' => $object->getId(),
'properties' => $object->getProperties() ?: [],
];
}
}
}
return $results;
}
private function logBatchResults(string $objectType, array $crmIds, array $results): void
{
$this->log->info("[HubSpot] Batch fetched {$objectType}", [
'requested_count' => count($crmIds),
'returned_count' => count($results),
'crm_ids' => $crmIds,
]);
}
private function handleBatchError(\Throwable $e, string $objectType, array $crmIds): void
{
$errorMessage = $e->getMessage() ?: 'Unknown error';
$errorTrace = $e->getTraceAsString() ?: 'No trace available';
$this->log->error("[HubSpot] Failed to batch fetch {$objectType}", [
'crm_ids' => $crmIds,
'error' => $errorMessage,
'trace' => $errorTrace,
]);
throw new CrmException("Failed to batch fetch {$objectType}: " . $errorMessage);
}
/**
* Batch read multiple opportunities by their CRM IDs
*
* @param array<string> $crmIds Array of HubSpot deal IDs (max 100)
* @param array<string> $fields Array of property names to fetch
*
* @return array<string, array> Array keyed by CRM ID with opportunity data
*/
public function getOpportunitiesByIds(array $crmIds, array $fields): array
{
return $this->batchReadObjects('deals', $crmIds, $fields);
}
/**
* Batch read multiple companies by their CRM IDs
*
* @param array<string> $crmIds Array of HubSpot company IDs (max 100)
* @param array<string> $fields Array of property names to fetch
*
* @return array<string, array> Array keyed by CRM ID with company data
*/
public function getCompaniesByIds(array $crmIds, array $fields): array
{
return $this->batchReadObjects('companies', $crmIds, $fields);
}
/**
* Batch read multiple contacts by their CRM IDs
*
* @param array<string> $crmIds Array of HubSpot contact IDs (max 100)
* @param array<string> $fields Array of property names to fetch
*
* @return array<string, array> Array keyed by CRM ID with contact data
*/
public function getContactsByIds(array $crmIds, array $fields): array
{
return $this->batchReadObjects('contacts', $crmIds, $fields);
}
/**
* @throws CompanyApiException
* @throws CrmException
*/
public function getAccountById(string $crmId, array $fields): array
{
try {
$company = $this->getNewInstance()->crm()->companies()->basicApi()->getById(
$crmId,
implode(',', $fields),
);
} catch (CompanyApiException $e) {
$this->log->info('[Hubspot] Failed to fetch account', [
'crm_id' => $crmId,
'reason' => $e->getMessage(),
]);
throw $e;
}
if (! $company instanceof CompaniesWithAssociations) {
throw new CrmException('Account not found');
}
return [
'id' => $company->getId(),
'properties' => $company->getProperties(),
];
}
/**
* @throws ContactApiException
* @throws CrmException
*/
public function getContactById(string $crmId, array $fields): array
{
try {
$contact = $this->getNewInstance()->crm()->contacts()->basicApi()->getById(
$crmId,
implode(',', $fields)
);
} catch (ContactApiException $e) {
$this->log->info('[Hubspot] Failed to fetch contact', [
'crm_id' => $crmId,
'reason' => $e->getMessage(),
]);
throw $e;
}
if (! $contact instanceof ContactsWithAssociations) {
throw new CrmException('Contact not found');
}
return [
'id' => $contact->getId(),
'properties' => $contact->getProperties(),
];
}
/**
* This is email search request that Hubspot offers as GET (more generous quota)
*/
public function getContactByEmail(string $email, array $fields = []): array
{
try {
$contact = $this->getNewInstance()->crm()->contacts()->basicApi()->getById(
$email,
implode(',', $fields),
null,
false,
'email'
);
return [
'id' => $contact->getId(),
'properties' => $contact->getProperties(),
];
} catch (ContactApiException $e) {
$this->log->info('[Hubspot] Failed to fetch contact', [
'email' => $email,
'reason' => $e->getMessage(),
]);
return [];
}
}
/**
* @throws CrmException
*/
public function fetchProperty(string $objectType, string $propertyId): Property
{
$result = $this->getNewInstance()->crm()->properties()->coreApi()->getByName($objectType, $propertyId);
if (! $result instanceof Property) {
$this->log->error('[Hubspot] Failed to fetch property', [
'object_type' => $objectType,
'property_id' => $propertyId,
'reason' => $result->getMessage(),
]);
throw new CrmException('Failed to fetch property');
}
return $result;
}
/**
* @return array<CrmFieldOption>
*/
public function fetchPropertyOptions(string $objectType, string $propertyId): array
{
/** @var array<CrmFieldOption> */
return $this->fetchProperty($objectType, $propertyId)->getOptions();
}
/**
* @return array<array{id:string, label:string, deleted:bool}>
*/
public function fetchCallDispositions(): array
{
/** @var Response $response */
$response = $this->getInstance()->engagements()->getCallDispositions();
/**
* @var array<array{
* id:string,
* label:string,
* deleted: bool
* }>
*/
return $response->toArray();
}
/**
* @return array<CrmFieldOption>
*/
public function fetchOpportunityPipelineStages(): array
{
$stages = [];
$apiResponse = $this->getNewInstance()->crm()->pipelines()->pipelinesApi()->getAll('deals');
if ($apiResponse instanceof Error) {
$this->log->error('[Hubspot] Failed to fetch opportunity pipelines', [
'reason' => $apiResponse->getMessage(),
]);
return [];
}
foreach ($apiResponse->getResults() as $pipeline) {
$pipelineStages = array_map(
static function (PipelineStage $stage) {
return [
'id' => $stage->getId(),
'label' => $stage->getLabel(),
];
},
$pipeline->getStages()
);
$stages = array_merge($stages, $pipelineStages);
}
return $stages;
}
public function fetchOpportunityPipelines(): array
{
$pipelines = [];
try {
$apiResponse = $this->makeRequest('/crm/v3/pipelines/deals');
} catch (\Exception $e) {
$this->log->info('[Hubspot] Failed to fetch opportunity pipelines', [
'reason' => $e->getMessage(),
]);
return [];
}
$response = $apiResponse->toArray();
foreach ($response['results'] as $pipeline) {
$pipelines[] = [
'id' => $pipeline['id'],
'label' => $pipeline['label'],
];
}
return $pipelines;
}
/**
* @return array<CrmFieldOption>
*/
public function fetchMeetingOutcomeFieldOptions(Field $field): array
{
return $field->getCrmProviderId() === 'meetingOutcome'
? $this->fetchMeetingOutcomeTypes()
: $this->fetchCallActivityTypes();
}
public function fetchMeetingOutcomeTypes(): array
{
return $this->extractMeetingTypeOptions(
'[URL_WITH_CREDENTIALS] Response $response */
$response = $this->getInstance()
->getClient()
->request('GET', $endpoint);
/**
* @var array<array{
* value: string,
* label: string,
* displayOrder: int
* }> $optionData
*/
$optionData = $response->toArray()['options'] ?? [];
$options = [];
foreach ($optionData as $item) {
$options[] = [
'id' => $item['value'],
'value' => $item['value'],
'label' => $item['label'],
'display_order' => $item['displayOrder'],
];
}
return $options;
}
/**
* @return array<CrmFieldOption>
*/
public function fetchDispositionFieldOptions(): array
{
$options = [];
$dispositions = $this->fetchCallDispositions();
foreach ($dispositions as $disposition) {
if ($disposition['deleted'] !== false) {
continue;
}
$option['value'] = $disposition['id'];
$option['id'] = $disposition['id'];
$option['label'] = $disposition['label'];
$options[] = $option;
}
return $options;
}
/**
* @return array<CrmFieldOption>
*/
public function fetchOpportunityFieldOptions(Field $field): array
{
if ($field->isStageField()) {
return $this->fetchOpportunityPipelineStages();
}
if ($field->isPipelineField()) {
return $this->fetchOpportunityPipelines();
}
return $this->fetchPropertyOptions('deals', $field->getCrmProviderId());
}
/**
* @throws BadRequest
* @throws HubspotException
*/
public function makeRequest(string $endpoint, $method = 'GET', $payload = [], ?string $queryString = null)
{
$endpoint = self::BASE_URL . $endpoint;
if ($method === 'GET') {
$response = $this->getInstance()->getClient()?->request(
method: $method,
endpoint: $endpoint,
query_string: $queryString
);
} else {
$response = $this->getInstance()->getClient()->request($method, $endpoint, [
'json' => ($payload),
]);
}
$max = $response->getHeaderLine('X-HubSpot-RateLimit-Max'); // "110"
$remaining = $response->getHeaderLine('X-HubSpot-RateLimit-Remaining'); // "109"
$interval = $response->getHeaderLine('X-HubSpot-RateLimit-Interval-Milliseconds'); // "10000"
$body = json_decode((string) $response->getBody(), true);
\Illuminate\Support\Facades\Log::channel('custom_channel')->info('$max ' . PHP_EOL . print_r($max, true));
\Illuminate\Support\Facades\Log::channel('custom_channel')->info('$remaining ' . PHP_EOL . print_r($remaining, true));
\Illuminate\Support\Facades\Log::channel('custom_channel')->info('$interval ' . PHP_EOL . print_r($interval, true));
\Illuminate\Support\Facades\Log::channel('custom_channel')->info('$body ' . PHP_EOL . print_r($body, true));
return $response;
}
/**
* @throws BadRequest
* @throws HubspotException
*/
public function createMeeting(array $payload): Response
{
$endpoint = '/crm/v3/objects/meetings';
return $this->makeRequest($endpoint, 'POST', $payload);
}
/**
* @throws BadRequest
* @throws HubspotException
*/
public function updateMeeting(string $meetingId, array $payload): Response
{
$endpoint = '/crm/v3/objects/meetings/' . $meetingId;
return $this->makeRequest($endpoint, 'PATCH', $payload);
}
/**
* @throws \Exception
*/
public function createNote(
string $body,
string $ownerId,
int $timestamp,
string $objectId,
NoteObject $noteObject
): ?string {
try {
$noteInput = new SimplePublicObjectInput([
'properties' => [
'hs_note_body' => $body,
'hubspot_owner_id' => $ownerId,
'hs_timestamp' => $timestamp,
],
]);
// Create note
$note = $this->getNewInstance()->crm()->objects()->basicApi()->create('note', $noteInput);
$this->getNewInstance()->crm()->objects()->associationsApi()->create(
'note',
$note->getId(),
$this->getNoteObject($noteObject),
$objectId,
$this->getNoteAssociationType($noteObject),
);
return $note->getId();
} catch (\Exception $e) {
$this->log->error('[Hubspot] Failed to create note', [
'objectId' => $objectId,
'noteObject' => $noteObject->getObjectType(),
'reason' => $e->getMessage(),
]);
\Sentry::captureException($e);
}
return null;
}
public function updateEngagement(string $objectId, array $engagement, array $metadata): void
{
$this->getInstance()->engagements()->update($objectId, $engagement, $metadata);
}
public function getEngagementData(string $engagementId): array
{
$engagement = $this->getInstance()->engagements()->get($engagementId);
return $engagement->toArray();
}
public function createEngagement(array $engagement, array $associations, array $metadata): Response
{
return $this->getInstance()
->engagements()
->create($engagement, $associations, $metadata);
}
public function isUnauthorizedException(\Exception $e): bool
{
// Check for specific HubSpot API exception types first
if ($e instanceof BadRequest) {
// BadRequest can contain 401 status codes
return $e->getCode() === 401;
}
// Check for HTTP client exceptions with status codes
if ($e instanceof \GuzzleHttp\Exception\RequestException && $e->hasResponse()) {
$response = $e->getResponse();
if ($response !== null) {
return $response->getStatusCode() === 401;
}
}
// Check for Guzzle HTTP exceptions
if ($e instanceof \GuzzleHttp\Exception\ClientException) {
return $e->getCode() === 401;
}
// Fallback to string matching as last resort, but be more specific
$message = strtolower($e->getMessage());
return str_contains($message, '401 unauthorized') ||
str_contains($message, 'http 401') ||
str_contains($message, 'status code 401') ||
(preg_match('/\b401\b/', $message) && str_contains($message, 'unauthorized'));
}
/**
* Validates and refreshes the access token if needed before API requests.
* This ensures long-running processes don't fail due to token expiration.
*
* @throws SocialAccountTokenInvalidException
*/
public function ensureValidToken(): void
{
if ($this->oauthAccount === null) {
return;
}
$newToken = $this->tokenManager->ensureValidToken($this->oauthAccount);
if ($newToken !== null) {
$this->accessToken = $newToken;
}
}
public function getConfig()
{
return $this->config;
}
// returns only active (archived=false)
public function getOwners(): array
{
return $this->getNewInstance()->crm()->owners()->getAll();
}
/**
* @param bool $archived
*
* @return array<Owner>|[]
*/
public function getOwnersArchived(bool $archived = true): array
{
$endpoint = '/crm/v3/owners';
$queryParams = [
'archived' => $archived ? 'true' : 'false',
];
$queryString = http_build_query($queryParams);
$owners = [];
try {
$response = $this->makeRequest(endpoint: $endpoint, queryString: $queryString);
$responseData = $response?->toArray();
foreach ($responseData['results'] as $result) {
try {
$owners[] = Owner::create($result);
} catch (Throwable $e) {
$this->log->error('[HubSpot] Failed to process owner data', [
'result' => $result,
'error' => $e->getMessage(),
]);
continue;
}
}
} catch (Throwable $e) {
$this->log->error('HubSpot] Failed to fetch owners', [
'archived' => $archived,
'error' => $e->getMessage(),
]);
return [];
}
return $owners;
}
public function getMeeting(string $engagementId): ObjectWithAssociations
{
return $this->getNewInstance()->crm()->objects()->basicApi()
->getById('meeting', $engagementId, null, 'contact,company,deal');
}
public function deleteEngagement(string $engagementId): void
{
$this->getInstance()->engagements()->delete((int) $engagementId);
}
public function getAssociationsData(array $ids, string $fromObject, string $toObject): array
{
$associationData = [];
$idChunks = array_chunk($ids, self::ASSOCIATIONS_BATCH_SIZE_LIMIT);
foreach ($idChunks as $idChunk) {
try {
$batchInput = new \HubSpot\Client\Crm\Associations\Model\BatchInputPublicObjectId();
$batchInput->setInputs(array_map(function ($id) {
$publicObjectId = new \HubSpot\Client\Crm\Associations\Model\PublicObjectId();
$publicObjectId->setId($id);
return $publicObjectId;
}, $idChunk));
$associatedObjectsData = $this
->getNewInstance()
->crm()
->associations()
->batchApi()
->read($fromObject, $toObject, $batchInput);
if ($associatedObjectsData instanceof \HubSpot\Client\Crm\Associations\Model\BatchResponsePublicAssociationMulti) {
foreach ($associatedObjectsData->getResults() as $association) {
$from = $association->getFrom()->getId();
$toAssociations = $association->getTo();
if (! empty($toAssociations)) {
$associationData[$from] = array_map(function ($item) {
return $item->getId();
}, $toAssociations);
}
}
}
} catch (\Exception $e) {
$this->log->error('[Hubspot] Failed to fetch associations', [
'from_object' => $fromObject,
'to_object' => $toObject,
'reason' => $e->getMessage(),
]);
}
}
return $associationData;
}
/**
* @throws \Exception
*/
private function getNoteAssociationType(NoteObject $noteObject): string
{
return match($noteObject) {
NoteObject::Opportunity => 'note_to_deal',
NoteObject::Lead, NoteObject::Contact => 'note_to_contact', // or 'note_to_lead' if your portal supports it
NoteObject::Account => 'note_to_company',
NoteObject::Call, NoteObject::Event => throw new \Exception('Not supported'),
};
}
/**
* @throws \Exception
*/
private function getNoteObject(NoteObject $noteObject): string
{
return match($noteObject) {
NoteObject::Opportunity => 'deal',
NoteObject::Lead, NoteObject::Contact => 'contact',
NoteObject::Account => 'company',
NoteObject::Call, NoteObject::Event => throw new \Exception('Not supported'),
};
}
public function addAssociations(string $objectType, string $associationType, array $payload): Response
{
$endpoint = "/crm/v4/associations/$objectType/$associationType/batch/create";
return $this->makeRequest($endpoint, 'POST', $payload);
}
public function removeAssociations(string $objectType, string $associationType, array $payload): Response
{
$endpoint = "/crm/v4/associations/$objectType/$associationType/batch/archive";
return $this->makeRequest($endpoint, 'POST', $payload);
}
}
Project
Project
New File or Directory…
Expand Selected
Collapse All
Options
Hide...
|
[{"role":"AXButton","text" [{"role":"AXButton","text":"Project: faVsco.js, menu","depth":5,"on_screen":true,"help_text":"~/jiminny/app","role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"master, menu","depth":5,"on_screen":true,"help_text":"Git Branch: master","role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Start Listening for PHP Debug Connections","depth":5,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"AskJiminnyReportActivityServiceTest","depth":6,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Run 'AskJiminnyReportActivityServiceTest'","depth":6,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Debug 'AskJiminnyReportActivityServiceTest'","depth":6,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"More Actions","depth":6,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"JetBrains AI","depth":5,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Search Everywhere","depth":5,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"IDE and Project Settings","depth":5,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Sync Changes","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.018055556,"height":0.026666667},"on_screen":false,"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Hide This Notification","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.018055556,"height":0.026666667},"on_screen":false,"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Code changed:","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.088194445,"height":0.027777778},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Hide","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.018055556,"height":0.026666667},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXTextArea","text":"Editor for custom.log","depth":4,"on_screen":true,"role_description":"text entry area","is_enabled":true,"is_focused":true,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Sync Changes","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.018055556,"height":0.026666667},"on_screen":false,"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Hide This Notification","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.018055556,"height":0.026666667},"on_screen":false,"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Code changed:","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.088194445,"height":0.027777778},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Hide","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.018055556,"height":0.026666667},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"2","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.016666668,"height":0.02111111},"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"60","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.021527778,"height":0.02111111},"on_screen":false,"role_description":"text"},{"role":"AXButton","text":"Previous Highlighted Error","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.015277778,"height":0.025555555},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Next Highlighted Error","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.014583333,"height":0.025555555},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXTextArea","text":"<?php\n\ndeclare(strict_types=1);\n\nnamespace Jiminny\\Services\\Crm\\Hubspot;\n\nuse HubSpot\\Client\\Crm\\Deals\\ApiException as DealApiException;\nuse HubSpot\\Client\\Crm\\Contacts\\ApiException as ContactApiException;\nuse HubSpot\\Client\\Crm\\Companies\\ApiException as CompanyApiException;\nuse HubSpot\\Client\\Crm\\Contacts\\Model\\SimplePublicObjectWithAssociations as ContactsWithAssociations;\nuse HubSpot\\Client\\Crm\\Companies\\Model\\SimplePublicObjectWithAssociations as CompaniesWithAssociations;\nuse HubSpot\\Client\\Crm\\Deals\\Model\\SimplePublicObjectWithAssociations as DealWithAssociations;\nuse HubSpot\\Client\\Crm\\Objects\\Model\\SimplePublicObjectInput;\nuse HubSpot\\Client\\Crm\\Objects\\Model\\SimplePublicObjectWithAssociations as ObjectWithAssociations;\nuse HubSpot\\Client\\Crm\\Pipelines\\Model\\Error;\nuse HubSpot\\Client\\Crm\\Pipelines\\Model\\PipelineStage;\nuse HubSpot\\Client\\Crm\\Properties\\Model\\Property;\nuse HubSpot\\Discovery\\Discovery;\nuse Jiminny\\Component\\Utility\\Service\\ProviderRateLimiter;\nuse Jiminny\\Exceptions\\CrmException;\nuse Jiminny\\Exceptions\\RateLimitException;\nuse Jiminny\\Exceptions\\SocialAccountTokenInvalidException;\nuse Jiminny\\Jobs\\Crm\\NoteObject;\nuse Jiminny\\Models\\Crm\\Field;\nuse Jiminny\\Services\\Crm\\BaseClient;\nuse Jiminny\\Services\\Crm\\Hubspot\\DTO\\Response\\Owner;\nuse Jiminny\\Services\\SocialAccountService;\nuse SevenShores\\Hubspot\\Exceptions\\BadRequest;\nuse SevenShores\\Hubspot\\Exceptions\\HubspotException;\nuse SevenShores\\Hubspot\\Factory;\nuse SevenShores\\Hubspot\\Http\\Response;\nuse Jiminny\\Services\\Crm\\Hubspot\\Pagination\\HubspotPaginationService;\nuse Throwable;\n\n/**\n * @phpstan-type CrmFieldOption array{id:string, label:string, value?:string}\n */\nclass Client extends BaseClient implements HubspotClientInterface\n{\n public const string MIN_API_VERSION = '2';\n\n public const string BASE_URL = 'https://api.hubapi.com';\n\n public const int ASSOCIATIONS_BATCH_SIZE_LIMIT = 1000;\n\n private HubspotPaginationService $paginationService;\n private HubspotTokenManager $tokenManager;\n private ProviderRateLimiter $rateLimiter;\n\n public function __construct(\n SocialAccountService $socialAccountService,\n HubspotPaginationService $paginationService,\n HubspotTokenManager $tokenManager,\n ProviderRateLimiter $rateLimiter,\n ) {\n parent::__construct($socialAccountService);\n $this->paginationService = $paginationService;\n $this->tokenManager = $tokenManager;\n $this->rateLimiter = $rateLimiter;\n\n $this->setBaseUrl(self::BASE_URL);\n $this->setVersion(self::MIN_API_VERSION);\n }\n\n /**\n * Single entry point for every HubSpot API call. Enforces the per-portal\n * rate limit configured in the rate_limits table (morphed to the current\n * Configuration) and reacts to a real 429 from HubSpot by translating it\n * into a RateLimitException carrying Retry-After.\n *\n * Wrap any outbound HubSpot call (SDK or raw HTTP) like:\n *\n * $this->executeRequest(fn () => $this->getNewInstance()->crm()->...);\n *\n * @template T\n * @param callable(): T $apiCall\n * @return T\n *\n * @throws RateLimitException\n */\n private function executeRequest(callable $apiCall)\n {\n if (! $this->rateLimiter->canMakeRequest($this->config)) {\n $retryAfter = $this->rateLimiter->requestAvailableIn($this->config);\n\n $this->log->warning('[Hubspot] Rate limit exceeded, deferring request', [\n 'team_id' => $this->config->team_id,\n 'config_id' => $this->config->getId(),\n 'retry_after' => $retryAfter,\n ]);\n\n throw new RateLimitException(\n 'Hubspot rate limit reached for configuration ' . $this->config->getId(),\n $retryAfter,\n );\n }\n\n $this->rateLimiter->incrementRequestCount($this->config);\n\n try {\n return $apiCall();\n } catch (Throwable $e) {\n if ($this->isHubspotRateLimit($e)) {\n $retryAfter = $this->parseRetryAfter($e);\n\n $this->log->warning('[Hubspot] Received 429 from API', [\n 'team_id' => $this->config->team_id,\n 'config_id' => $this->config->getId(),\n 'retry_after' => $retryAfter,\n 'reason' => $e->getMessage(),\n ]);\n\n throw new RateLimitException('Hubspot returned 429', $retryAfter, $e);\n }\n\n throw $e;\n }\n }\n\n private function isHubspotRateLimit(Throwable $e): bool\n {\n return method_exists($e, 'getCode') && (int) $e->getCode() === 429;\n }\n\n private function parseRetryAfter(Throwable $e): int\n {\n if (method_exists($e, 'getResponseHeaders')) {\n $headers = $e->getResponseHeaders() ?: [];\n $value = $headers['Retry-After'] ?? $headers['retry-after'] ?? null;\n if (is_array($value)) {\n $value = $value[0] ?? null;\n }\n if (is_numeric($value)) {\n return (int) $value;\n }\n }\n\n return 10;\n }\n\n public function getMinimumApiVersion(): string\n {\n return self::MIN_API_VERSION;\n }\n\n public function getInstance(): Factory\n {\n return new Factory([\n 'key' => $this->accessToken,\n 'oauth2' => true,\n 'base_url' => $this->baseUrl,\n ]);\n }\n\n public function getNewInstance(): Discovery\n {\n return \\HubSpot\\Factory::createWithAccessToken($this->accessToken);\n }\n\n /**\n * Secondly and daily limits for Hubspot API\n *\n * Product Tier: Free & Starter | Professional & Enterprise | API add-on (any tier)\n * Burst: 100/10 seconds | 150/10 seconds | 200/10 seconds\n * Daily: 250,000 | 500,000 | 1,000,000\n *\n * Official documentation states: The search endpoints are rate limited to five requests per second.\n * Since with 5 RPS were still hitting secondly rate limits we lowered it to 4\n */\n public function getPaginatedData(array $payload, string $type, int $offset = 0): array\n {\n $total = 0;\n $lastId = null;\n $rows = [];\n foreach ($this->getPaginatedDataGenerator($payload, $type, $offset, $total, $lastId) as $row) {\n $rows[] = $row;\n }\n\n return ['results' => $rows, 'total' => $total, 'last_record' => $lastId];\n }\n\n /**\n * @throws HubspotException\n * @throws SocialAccountTokenInvalidException\n * @throws BadRequest\n */\n public function getPaginatedDataGenerator(\n array $payload,\n string $type,\n int $offset = 0,\n int &$total = 0,\n ?string &$lastRecordId = null\n ): \\Generator {\n return $this->paginationService->getPaginatedDataGenerator(\n $this,\n $payload,\n $type,\n $offset,\n $total,\n $lastRecordId\n );\n }\n\n /**\n * @throws DealApiException\n * @throws CrmException\n */\n public function getOpportunityById(string $crmId, array $fields): array\n {\n try {\n $deal = $this->executeRequest(fn () => $this->getNewInstance()->crm()->deals()->basicApi()->getById(\n $crmId,\n implode(',', $fields),\n 'companies,contacts'\n ));\n } catch (DealApiException $e) {\n $this->log->info('[Hubspot] Failed to fetch opportunity', [\n 'crm_id' => $crmId,\n 'reason' => $e->getMessage(),\n ]);\n\n throw $e;\n }\n\n if (! $deal instanceof DealWithAssociations) {\n throw new CrmException('Deal not found');\n }\n\n return [\n 'id' => $deal->getId(),\n 'properties' => $deal->getProperties(),\n 'associations' => $deal->getAssociations(),\n ];\n }\n\n /**\n * Generic batch read method for HubSpot objects\n *\n * @param string $objectType The object type ('deals', 'companies', 'contacts')\n * @param array<string> $crmIds Array of HubSpot object IDs (max 100)\n * @param array<string> $fields Array of property names to fetch\n *\n * @return array<string, array> Array keyed by CRM ID with object data\n */\n private function batchReadObjects(string $objectType, array $crmIds, array $fields): array\n {\n if (empty($crmIds)) {\n return [];\n }\n\n $this->validateBatchSize($objectType, $crmIds);\n $this->ensureValidToken();\n\n try {\n $batchConfig = $this->createBatchConfiguration($objectType);\n $batchReadRequest = $this->prepareBatchRequest($batchConfig, $crmIds, $fields);\n $response = $batchConfig['api']->read($batchReadRequest);\n\n $this->validateApiResponse($response, $objectType);\n\n $results = $this->processApiResults($response);\n $this->logBatchResults($objectType, $crmIds, $results);\n\n return $results;\n } catch (\\Throwable $e) {\n $this->handleBatchError($e, $objectType, $crmIds);\n }\n }\n\n private function validateBatchSize(string $objectType, array $crmIds): void\n {\n if (count($crmIds) > 100) {\n throw new \\InvalidArgumentException(\"Batch size cannot exceed 100 {$objectType}\");\n }\n }\n\n private function createBatchConfiguration(string $objectType): array\n {\n $configurations = [\n 'deals' => [\n 'batchReadRequest' => new \\HubSpot\\Client\\Crm\\Deals\\Model\\BatchReadInputSimplePublicObjectId(),\n 'inputClass' => \\HubSpot\\Client\\Crm\\Deals\\Model\\SimplePublicObjectId::class,\n 'api' => $this->getNewInstance()->crm()->deals()->batchApi(),\n ],\n 'companies' => [\n 'batchReadRequest' => new \\HubSpot\\Client\\Crm\\Companies\\Model\\BatchReadInputSimplePublicObjectId(),\n 'inputClass' => \\HubSpot\\Client\\Crm\\Companies\\Model\\SimplePublicObjectId::class,\n 'api' => $this->getNewInstance()->crm()->companies()->batchApi(),\n ],\n 'contacts' => [\n 'batchReadRequest' => new \\HubSpot\\Client\\Crm\\Contacts\\Model\\BatchReadInputSimplePublicObjectId(),\n 'inputClass' => \\HubSpot\\Client\\Crm\\Contacts\\Model\\SimplePublicObjectId::class,\n 'api' => $this->getNewInstance()->crm()->contacts()->batchApi(),\n ],\n ];\n\n if (! isset($configurations[$objectType])) {\n throw new \\InvalidArgumentException(\"Unsupported object type: {$objectType}\");\n }\n\n return $configurations[$objectType];\n }\n\n private function prepareBatchRequest(array $batchConfig, array $crmIds, array $fields): object\n {\n $batchReadRequest = $batchConfig['batchReadRequest'];\n $inputClass = $batchConfig['inputClass'];\n\n $inputs = array_map(function ($crmId) use ($inputClass) {\n $input = new $inputClass();\n $input->setId($crmId);\n\n return $input;\n }, $crmIds);\n\n $batchReadRequest->setInputs($inputs);\n $batchReadRequest->setProperties($fields);\n\n return $batchReadRequest;\n }\n\n private function validateApiResponse($response, string $objectType): void\n {\n if (! $response) {\n throw new CrmException(\"HubSpot API returned null response for {$objectType} batch read\");\n }\n }\n\n private function processApiResults($response): array\n {\n $results = [];\n $responseResults = $response->getResults();\n\n if ($responseResults) {\n foreach ($responseResults as $object) {\n if ($object && $object->getId()) {\n $results[$object->getId()] = [\n 'id' => $object->getId(),\n 'properties' => $object->getProperties() ?: [],\n ];\n }\n }\n }\n\n return $results;\n }\n\n private function logBatchResults(string $objectType, array $crmIds, array $results): void\n {\n $this->log->info(\"[HubSpot] Batch fetched {$objectType}\", [\n 'requested_count' => count($crmIds),\n 'returned_count' => count($results),\n 'crm_ids' => $crmIds,\n ]);\n }\n\n private function handleBatchError(\\Throwable $e, string $objectType, array $crmIds): void\n {\n $errorMessage = $e->getMessage() ?: 'Unknown error';\n $errorTrace = $e->getTraceAsString() ?: 'No trace available';\n\n $this->log->error(\"[HubSpot] Failed to batch fetch {$objectType}\", [\n 'crm_ids' => $crmIds,\n 'error' => $errorMessage,\n 'trace' => $errorTrace,\n ]);\n\n throw new CrmException(\"Failed to batch fetch {$objectType}: \" . $errorMessage);\n }\n\n /**\n * Batch read multiple opportunities by their CRM IDs\n *\n * @param array<string> $crmIds Array of HubSpot deal IDs (max 100)\n * @param array<string> $fields Array of property names to fetch\n *\n * @return array<string, array> Array keyed by CRM ID with opportunity data\n */\n public function getOpportunitiesByIds(array $crmIds, array $fields): array\n {\n return $this->batchReadObjects('deals', $crmIds, $fields);\n }\n\n /**\n * Batch read multiple companies by their CRM IDs\n *\n * @param array<string> $crmIds Array of HubSpot company IDs (max 100)\n * @param array<string> $fields Array of property names to fetch\n *\n * @return array<string, array> Array keyed by CRM ID with company data\n */\n public function getCompaniesByIds(array $crmIds, array $fields): array\n {\n return $this->batchReadObjects('companies', $crmIds, $fields);\n }\n\n /**\n * Batch read multiple contacts by their CRM IDs\n *\n * @param array<string> $crmIds Array of HubSpot contact IDs (max 100)\n * @param array<string> $fields Array of property names to fetch\n *\n * @return array<string, array> Array keyed by CRM ID with contact data\n */\n public function getContactsByIds(array $crmIds, array $fields): array\n {\n return $this->batchReadObjects('contacts', $crmIds, $fields);\n }\n\n /**\n * @throws CompanyApiException\n * @throws CrmException\n */\n public function getAccountById(string $crmId, array $fields): array\n {\n try {\n $company = $this->getNewInstance()->crm()->companies()->basicApi()->getById(\n $crmId,\n implode(',', $fields),\n );\n } catch (CompanyApiException $e) {\n $this->log->info('[Hubspot] Failed to fetch account', [\n 'crm_id' => $crmId,\n 'reason' => $e->getMessage(),\n ]);\n\n throw $e;\n }\n\n if (! $company instanceof CompaniesWithAssociations) {\n throw new CrmException('Account not found');\n }\n\n return [\n 'id' => $company->getId(),\n 'properties' => $company->getProperties(),\n ];\n }\n\n /**\n * @throws ContactApiException\n * @throws CrmException\n */\n public function getContactById(string $crmId, array $fields): array\n {\n try {\n $contact = $this->getNewInstance()->crm()->contacts()->basicApi()->getById(\n $crmId,\n implode(',', $fields)\n );\n } catch (ContactApiException $e) {\n $this->log->info('[Hubspot] Failed to fetch contact', [\n 'crm_id' => $crmId,\n 'reason' => $e->getMessage(),\n ]);\n\n throw $e;\n }\n\n if (! $contact instanceof ContactsWithAssociations) {\n throw new CrmException('Contact not found');\n }\n\n return [\n 'id' => $contact->getId(),\n 'properties' => $contact->getProperties(),\n ];\n }\n\n /**\n * This is email search request that Hubspot offers as GET (more generous quota)\n */\n public function getContactByEmail(string $email, array $fields = []): array\n {\n try {\n $contact = $this->getNewInstance()->crm()->contacts()->basicApi()->getById(\n $email,\n implode(',', $fields),\n null,\n false,\n 'email'\n );\n\n return [\n 'id' => $contact->getId(),\n 'properties' => $contact->getProperties(),\n ];\n } catch (ContactApiException $e) {\n $this->log->info('[Hubspot] Failed to fetch contact', [\n 'email' => $email,\n 'reason' => $e->getMessage(),\n ]);\n\n return [];\n }\n }\n\n /**\n * @throws CrmException\n */\n public function fetchProperty(string $objectType, string $propertyId): Property\n {\n $result = $this->getNewInstance()->crm()->properties()->coreApi()->getByName($objectType, $propertyId);\n\n if (! $result instanceof Property) {\n $this->log->error('[Hubspot] Failed to fetch property', [\n 'object_type' => $objectType,\n 'property_id' => $propertyId,\n 'reason' => $result->getMessage(),\n ]);\n\n throw new CrmException('Failed to fetch property');\n }\n\n return $result;\n }\n\n /**\n * @return array<CrmFieldOption>\n */\n public function fetchPropertyOptions(string $objectType, string $propertyId): array\n {\n /** @var array<CrmFieldOption> */\n return $this->fetchProperty($objectType, $propertyId)->getOptions();\n }\n\n /**\n * @return array<array{id:string, label:string, deleted:bool}>\n */\n public function fetchCallDispositions(): array\n {\n /** @var Response $response */\n $response = $this->getInstance()->engagements()->getCallDispositions();\n\n /**\n * @var array<array{\n * id:string,\n * label:string,\n * deleted: bool\n * }>\n */\n return $response->toArray();\n }\n\n /**\n * @return array<CrmFieldOption>\n */\n public function fetchOpportunityPipelineStages(): array\n {\n $stages = [];\n $apiResponse = $this->getNewInstance()->crm()->pipelines()->pipelinesApi()->getAll('deals');\n\n if ($apiResponse instanceof Error) {\n $this->log->error('[Hubspot] Failed to fetch opportunity pipelines', [\n 'reason' => $apiResponse->getMessage(),\n ]);\n\n return [];\n }\n\n foreach ($apiResponse->getResults() as $pipeline) {\n $pipelineStages = array_map(\n static function (PipelineStage $stage) {\n return [\n 'id' => $stage->getId(),\n 'label' => $stage->getLabel(),\n ];\n },\n $pipeline->getStages()\n );\n\n $stages = array_merge($stages, $pipelineStages);\n }\n\n return $stages;\n }\n\n public function fetchOpportunityPipelines(): array\n {\n $pipelines = [];\n\n try {\n $apiResponse = $this->makeRequest('/crm/v3/pipelines/deals');\n } catch (\\Exception $e) {\n $this->log->info('[Hubspot] Failed to fetch opportunity pipelines', [\n 'reason' => $e->getMessage(),\n ]);\n\n return [];\n }\n\n $response = $apiResponse->toArray();\n\n foreach ($response['results'] as $pipeline) {\n $pipelines[] = [\n 'id' => $pipeline['id'],\n 'label' => $pipeline['label'],\n ];\n }\n\n return $pipelines;\n }\n\n /**\n * @return array<CrmFieldOption>\n */\n public function fetchMeetingOutcomeFieldOptions(Field $field): array\n {\n return $field->getCrmProviderId() === 'meetingOutcome'\n ? $this->fetchMeetingOutcomeTypes()\n : $this->fetchCallActivityTypes();\n }\n\n public function fetchMeetingOutcomeTypes(): array\n {\n return $this->extractMeetingTypeOptions(\n 'https://api.hubapi.com/crm/v3/properties/meeting/hs_meeting_outcome'\n );\n }\n\n public function fetchCallActivityTypes(): array\n {\n return $this->extractMeetingTypeOptions(\n 'https://api.hubapi.com/crm/v3/properties/call/hs_activity_type'\n );\n }\n\n private function extractMeetingTypeOptions(string $endpoint): array\n {\n /** @var Response $response */\n $response = $this->getInstance()\n ->getClient()\n ->request('GET', $endpoint);\n\n /**\n * @var array<array{\n * value: string,\n * label: string,\n * displayOrder: int\n * }> $optionData\n */\n $optionData = $response->toArray()['options'] ?? [];\n\n $options = [];\n foreach ($optionData as $item) {\n $options[] = [\n 'id' => $item['value'],\n 'value' => $item['value'],\n 'label' => $item['label'],\n 'display_order' => $item['displayOrder'],\n ];\n }\n\n return $options;\n }\n\n /**\n * @return array<CrmFieldOption>\n */\n public function fetchDispositionFieldOptions(): array\n {\n $options = [];\n\n $dispositions = $this->fetchCallDispositions();\n\n foreach ($dispositions as $disposition) {\n if ($disposition['deleted'] !== false) {\n continue;\n }\n\n $option['value'] = $disposition['id'];\n $option['id'] = $disposition['id'];\n $option['label'] = $disposition['label'];\n\n $options[] = $option;\n }\n\n return $options;\n }\n\n /**\n * @return array<CrmFieldOption>\n */\n public function fetchOpportunityFieldOptions(Field $field): array\n {\n if ($field->isStageField()) {\n return $this->fetchOpportunityPipelineStages();\n }\n\n if ($field->isPipelineField()) {\n return $this->fetchOpportunityPipelines();\n }\n\n return $this->fetchPropertyOptions('deals', $field->getCrmProviderId());\n }\n\n /**\n * @throws BadRequest\n * @throws HubspotException\n */\n public function makeRequest(string $endpoint, $method = 'GET', $payload = [], ?string $queryString = null)\n {\n $endpoint = self::BASE_URL . $endpoint;\n\n if ($method === 'GET') {\n $response = $this->getInstance()->getClient()?->request(\n method: $method,\n endpoint: $endpoint,\n query_string: $queryString\n );\n } else {\n $response = $this->getInstance()->getClient()->request($method, $endpoint, [\n 'json' => ($payload),\n ]);\n }\n\n $max = $response->getHeaderLine('X-HubSpot-RateLimit-Max'); // \"110\"\n $remaining = $response->getHeaderLine('X-HubSpot-RateLimit-Remaining'); // \"109\"\n $interval = $response->getHeaderLine('X-HubSpot-RateLimit-Interval-Milliseconds'); // \"10000\"\n $body = json_decode((string) $response->getBody(), true);\n\n \\Illuminate\\Support\\Facades\\Log::channel('custom_channel')->info('$max ' . PHP_EOL . print_r($max, true));\n \\Illuminate\\Support\\Facades\\Log::channel('custom_channel')->info('$remaining ' . PHP_EOL . print_r($remaining, true));\n \\Illuminate\\Support\\Facades\\Log::channel('custom_channel')->info('$interval ' . PHP_EOL . print_r($interval, true));\n \\Illuminate\\Support\\Facades\\Log::channel('custom_channel')->info('$body ' . PHP_EOL . print_r($body, true));\n\n return $response;\n }\n\n /**\n * @throws BadRequest\n * @throws HubspotException\n */\n public function createMeeting(array $payload): Response\n {\n $endpoint = '/crm/v3/objects/meetings';\n\n return $this->makeRequest($endpoint, 'POST', $payload);\n }\n\n /**\n * @throws BadRequest\n * @throws HubspotException\n */\n public function updateMeeting(string $meetingId, array $payload): Response\n {\n $endpoint = '/crm/v3/objects/meetings/' . $meetingId;\n\n return $this->makeRequest($endpoint, 'PATCH', $payload);\n }\n\n /**\n * @throws \\Exception\n */\n public function createNote(\n string $body,\n string $ownerId,\n int $timestamp,\n string $objectId,\n NoteObject $noteObject\n ): ?string {\n try {\n $noteInput = new SimplePublicObjectInput([\n 'properties' => [\n 'hs_note_body' => $body,\n 'hubspot_owner_id' => $ownerId,\n 'hs_timestamp' => $timestamp,\n ],\n ]);\n\n // Create note\n $note = $this->getNewInstance()->crm()->objects()->basicApi()->create('note', $noteInput);\n\n $this->getNewInstance()->crm()->objects()->associationsApi()->create(\n 'note',\n $note->getId(),\n $this->getNoteObject($noteObject),\n $objectId,\n $this->getNoteAssociationType($noteObject),\n );\n\n return $note->getId();\n } catch (\\Exception $e) {\n $this->log->error('[Hubspot] Failed to create note', [\n 'objectId' => $objectId,\n 'noteObject' => $noteObject->getObjectType(),\n 'reason' => $e->getMessage(),\n ]);\n\n \\Sentry::captureException($e);\n }\n\n return null;\n }\n\n public function updateEngagement(string $objectId, array $engagement, array $metadata): void\n {\n $this->getInstance()->engagements()->update($objectId, $engagement, $metadata);\n }\n\n public function getEngagementData(string $engagementId): array\n {\n $engagement = $this->getInstance()->engagements()->get($engagementId);\n\n return $engagement->toArray();\n }\n\n public function createEngagement(array $engagement, array $associations, array $metadata): Response\n {\n return $this->getInstance()\n ->engagements()\n ->create($engagement, $associations, $metadata);\n }\n\n public function isUnauthorizedException(\\Exception $e): bool\n {\n // Check for specific HubSpot API exception types first\n if ($e instanceof BadRequest) {\n // BadRequest can contain 401 status codes\n return $e->getCode() === 401;\n }\n\n // Check for HTTP client exceptions with status codes\n if ($e instanceof \\GuzzleHttp\\Exception\\RequestException && $e->hasResponse()) {\n $response = $e->getResponse();\n if ($response !== null) {\n return $response->getStatusCode() === 401;\n }\n }\n\n // Check for Guzzle HTTP exceptions\n if ($e instanceof \\GuzzleHttp\\Exception\\ClientException) {\n return $e->getCode() === 401;\n }\n\n // Fallback to string matching as last resort, but be more specific\n $message = strtolower($e->getMessage());\n\n return str_contains($message, '401 unauthorized') ||\n str_contains($message, 'http 401') ||\n str_contains($message, 'status code 401') ||\n (preg_match('/\\b401\\b/', $message) && str_contains($message, 'unauthorized'));\n }\n\n /**\n * Validates and refreshes the access token if needed before API requests.\n * This ensures long-running processes don't fail due to token expiration.\n *\n * @throws SocialAccountTokenInvalidException\n */\n public function ensureValidToken(): void\n {\n if ($this->oauthAccount === null) {\n return;\n }\n\n $newToken = $this->tokenManager->ensureValidToken($this->oauthAccount);\n if ($newToken !== null) {\n $this->accessToken = $newToken;\n }\n }\n\n public function getConfig()\n {\n return $this->config;\n }\n\n // returns only active (archived=false)\n public function getOwners(): array\n {\n return $this->getNewInstance()->crm()->owners()->getAll();\n }\n\n /**\n * @param bool $archived\n *\n * @return array<Owner>|[]\n */\n public function getOwnersArchived(bool $archived = true): array\n {\n $endpoint = '/crm/v3/owners';\n $queryParams = [\n 'archived' => $archived ? 'true' : 'false',\n ];\n $queryString = http_build_query($queryParams);\n\n $owners = [];\n\n try {\n $response = $this->makeRequest(endpoint: $endpoint, queryString: $queryString);\n $responseData = $response?->toArray();\n\n foreach ($responseData['results'] as $result) {\n try {\n $owners[] = Owner::create($result);\n } catch (Throwable $e) {\n $this->log->error('[HubSpot] Failed to process owner data', [\n 'result' => $result,\n 'error' => $e->getMessage(),\n ]);\n\n continue;\n }\n }\n } catch (Throwable $e) {\n $this->log->error('HubSpot] Failed to fetch owners', [\n 'archived' => $archived,\n 'error' => $e->getMessage(),\n ]);\n\n return [];\n }\n\n return $owners;\n }\n\n public function getMeeting(string $engagementId): ObjectWithAssociations\n {\n return $this->getNewInstance()->crm()->objects()->basicApi()\n ->getById('meeting', $engagementId, null, 'contact,company,deal');\n }\n\n public function deleteEngagement(string $engagementId): void\n {\n $this->getInstance()->engagements()->delete((int) $engagementId);\n }\n\n public function getAssociationsData(array $ids, string $fromObject, string $toObject): array\n {\n $associationData = [];\n $idChunks = array_chunk($ids, self::ASSOCIATIONS_BATCH_SIZE_LIMIT);\n\n foreach ($idChunks as $idChunk) {\n try {\n $batchInput = new \\HubSpot\\Client\\Crm\\Associations\\Model\\BatchInputPublicObjectId();\n $batchInput->setInputs(array_map(function ($id) {\n $publicObjectId = new \\HubSpot\\Client\\Crm\\Associations\\Model\\PublicObjectId();\n $publicObjectId->setId($id);\n\n return $publicObjectId;\n }, $idChunk));\n\n $associatedObjectsData = $this\n ->getNewInstance()\n ->crm()\n ->associations()\n ->batchApi()\n ->read($fromObject, $toObject, $batchInput);\n\n if ($associatedObjectsData instanceof \\HubSpot\\Client\\Crm\\Associations\\Model\\BatchResponsePublicAssociationMulti) {\n foreach ($associatedObjectsData->getResults() as $association) {\n $from = $association->getFrom()->getId();\n $toAssociations = $association->getTo();\n\n if (! empty($toAssociations)) {\n $associationData[$from] = array_map(function ($item) {\n return $item->getId();\n }, $toAssociations);\n }\n }\n }\n } catch (\\Exception $e) {\n $this->log->error('[Hubspot] Failed to fetch associations', [\n 'from_object' => $fromObject,\n 'to_object' => $toObject,\n 'reason' => $e->getMessage(),\n ]);\n }\n }\n\n return $associationData;\n }\n\n /**\n * @throws \\Exception\n */\n private function getNoteAssociationType(NoteObject $noteObject): string\n {\n return match($noteObject) {\n NoteObject::Opportunity => 'note_to_deal',\n NoteObject::Lead, NoteObject::Contact => 'note_to_contact', // or 'note_to_lead' if your portal supports it\n NoteObject::Account => 'note_to_company',\n NoteObject::Call, NoteObject::Event => throw new \\Exception('Not supported'),\n };\n }\n\n /**\n * @throws \\Exception\n */\n private function getNoteObject(NoteObject $noteObject): string\n {\n return match($noteObject) {\n NoteObject::Opportunity => 'deal',\n NoteObject::Lead, NoteObject::Contact => 'contact',\n NoteObject::Account => 'company',\n NoteObject::Call, NoteObject::Event => throw new \\Exception('Not supported'),\n };\n }\n\n public function addAssociations(string $objectType, string $associationType, array $payload): Response\n {\n $endpoint = \"/crm/v4/associations/$objectType/$associationType/batch/create\";\n\n return $this->makeRequest($endpoint, 'POST', $payload);\n }\n\n public function removeAssociations(string $objectType, string $associationType, array $payload): Response\n {\n $endpoint = \"/crm/v4/associations/$objectType/$associationType/batch/archive\";\n\n return $this->makeRequest($endpoint, 'POST', $payload);\n }\n}","depth":4,"on_screen":true,"value":"<?php\n\ndeclare(strict_types=1);\n\nnamespace Jiminny\\Services\\Crm\\Hubspot;\n\nuse HubSpot\\Client\\Crm\\Deals\\ApiException as DealApiException;\nuse HubSpot\\Client\\Crm\\Contacts\\ApiException as ContactApiException;\nuse HubSpot\\Client\\Crm\\Companies\\ApiException as CompanyApiException;\nuse HubSpot\\Client\\Crm\\Contacts\\Model\\SimplePublicObjectWithAssociations as ContactsWithAssociations;\nuse HubSpot\\Client\\Crm\\Companies\\Model\\SimplePublicObjectWithAssociations as CompaniesWithAssociations;\nuse HubSpot\\Client\\Crm\\Deals\\Model\\SimplePublicObjectWithAssociations as DealWithAssociations;\nuse HubSpot\\Client\\Crm\\Objects\\Model\\SimplePublicObjectInput;\nuse HubSpot\\Client\\Crm\\Objects\\Model\\SimplePublicObjectWithAssociations as ObjectWithAssociations;\nuse HubSpot\\Client\\Crm\\Pipelines\\Model\\Error;\nuse HubSpot\\Client\\Crm\\Pipelines\\Model\\PipelineStage;\nuse HubSpot\\Client\\Crm\\Properties\\Model\\Property;\nuse HubSpot\\Discovery\\Discovery;\nuse Jiminny\\Component\\Utility\\Service\\ProviderRateLimiter;\nuse Jiminny\\Exceptions\\CrmException;\nuse Jiminny\\Exceptions\\RateLimitException;\nuse Jiminny\\Exceptions\\SocialAccountTokenInvalidException;\nuse Jiminny\\Jobs\\Crm\\NoteObject;\nuse Jiminny\\Models\\Crm\\Field;\nuse Jiminny\\Services\\Crm\\BaseClient;\nuse Jiminny\\Services\\Crm\\Hubspot\\DTO\\Response\\Owner;\nuse Jiminny\\Services\\SocialAccountService;\nuse SevenShores\\Hubspot\\Exceptions\\BadRequest;\nuse SevenShores\\Hubspot\\Exceptions\\HubspotException;\nuse SevenShores\\Hubspot\\Factory;\nuse SevenShores\\Hubspot\\Http\\Response;\nuse Jiminny\\Services\\Crm\\Hubspot\\Pagination\\HubspotPaginationService;\nuse Throwable;\n\n/**\n * @phpstan-type CrmFieldOption array{id:string, label:string, value?:string}\n */\nclass Client extends BaseClient implements HubspotClientInterface\n{\n public const string MIN_API_VERSION = '2';\n\n public const string BASE_URL = 'https://api.hubapi.com';\n\n public const int ASSOCIATIONS_BATCH_SIZE_LIMIT = 1000;\n\n private HubspotPaginationService $paginationService;\n private HubspotTokenManager $tokenManager;\n private ProviderRateLimiter $rateLimiter;\n\n public function __construct(\n SocialAccountService $socialAccountService,\n HubspotPaginationService $paginationService,\n HubspotTokenManager $tokenManager,\n ProviderRateLimiter $rateLimiter,\n ) {\n parent::__construct($socialAccountService);\n $this->paginationService = $paginationService;\n $this->tokenManager = $tokenManager;\n $this->rateLimiter = $rateLimiter;\n\n $this->setBaseUrl(self::BASE_URL);\n $this->setVersion(self::MIN_API_VERSION);\n }\n\n /**\n * Single entry point for every HubSpot API call. Enforces the per-portal\n * rate limit configured in the rate_limits table (morphed to the current\n * Configuration) and reacts to a real 429 from HubSpot by translating it\n * into a RateLimitException carrying Retry-After.\n *\n * Wrap any outbound HubSpot call (SDK or raw HTTP) like:\n *\n * $this->executeRequest(fn () => $this->getNewInstance()->crm()->...);\n *\n * @template T\n * @param callable(): T $apiCall\n * @return T\n *\n * @throws RateLimitException\n */\n private function executeRequest(callable $apiCall)\n {\n if (! $this->rateLimiter->canMakeRequest($this->config)) {\n $retryAfter = $this->rateLimiter->requestAvailableIn($this->config);\n\n $this->log->warning('[Hubspot] Rate limit exceeded, deferring request', [\n 'team_id' => $this->config->team_id,\n 'config_id' => $this->config->getId(),\n 'retry_after' => $retryAfter,\n ]);\n\n throw new RateLimitException(\n 'Hubspot rate limit reached for configuration ' . $this->config->getId(),\n $retryAfter,\n );\n }\n\n $this->rateLimiter->incrementRequestCount($this->config);\n\n try {\n return $apiCall();\n } catch (Throwable $e) {\n if ($this->isHubspotRateLimit($e)) {\n $retryAfter = $this->parseRetryAfter($e);\n\n $this->log->warning('[Hubspot] Received 429 from API', [\n 'team_id' => $this->config->team_id,\n 'config_id' => $this->config->getId(),\n 'retry_after' => $retryAfter,\n 'reason' => $e->getMessage(),\n ]);\n\n throw new RateLimitException('Hubspot returned 429', $retryAfter, $e);\n }\n\n throw $e;\n }\n }\n\n private function isHubspotRateLimit(Throwable $e): bool\n {\n return method_exists($e, 'getCode') && (int) $e->getCode() === 429;\n }\n\n private function parseRetryAfter(Throwable $e): int\n {\n if (method_exists($e, 'getResponseHeaders')) {\n $headers = $e->getResponseHeaders() ?: [];\n $value = $headers['Retry-After'] ?? $headers['retry-after'] ?? null;\n if (is_array($value)) {\n $value = $value[0] ?? null;\n }\n if (is_numeric($value)) {\n return (int) $value;\n }\n }\n\n return 10;\n }\n\n public function getMinimumApiVersion(): string\n {\n return self::MIN_API_VERSION;\n }\n\n public function getInstance(): Factory\n {\n return new Factory([\n 'key' => $this->accessToken,\n 'oauth2' => true,\n 'base_url' => $this->baseUrl,\n ]);\n }\n\n public function getNewInstance(): Discovery\n {\n return \\HubSpot\\Factory::createWithAccessToken($this->accessToken);\n }\n\n /**\n * Secondly and daily limits for Hubspot API\n *\n * Product Tier: Free & Starter | Professional & Enterprise | API add-on (any tier)\n * Burst: 100/10 seconds | 150/10 seconds | 200/10 seconds\n * Daily: 250,000 | 500,000 | 1,000,000\n *\n * Official documentation states: The search endpoints are rate limited to five requests per second.\n * Since with 5 RPS were still hitting secondly rate limits we lowered it to 4\n */\n public function getPaginatedData(array $payload, string $type, int $offset = 0): array\n {\n $total = 0;\n $lastId = null;\n $rows = [];\n foreach ($this->getPaginatedDataGenerator($payload, $type, $offset, $total, $lastId) as $row) {\n $rows[] = $row;\n }\n\n return ['results' => $rows, 'total' => $total, 'last_record' => $lastId];\n }\n\n /**\n * @throws HubspotException\n * @throws SocialAccountTokenInvalidException\n * @throws BadRequest\n */\n public function getPaginatedDataGenerator(\n array $payload,\n string $type,\n int $offset = 0,\n int &$total = 0,\n ?string &$lastRecordId = null\n ): \\Generator {\n return $this->paginationService->getPaginatedDataGenerator(\n $this,\n $payload,\n $type,\n $offset,\n $total,\n $lastRecordId\n );\n }\n\n /**\n * @throws DealApiException\n * @throws CrmException\n */\n public function getOpportunityById(string $crmId, array $fields): array\n {\n try {\n $deal = $this->executeRequest(fn () => $this->getNewInstance()->crm()->deals()->basicApi()->getById(\n $crmId,\n implode(',', $fields),\n 'companies,contacts'\n ));\n } catch (DealApiException $e) {\n $this->log->info('[Hubspot] Failed to fetch opportunity', [\n 'crm_id' => $crmId,\n 'reason' => $e->getMessage(),\n ]);\n\n throw $e;\n }\n\n if (! $deal instanceof DealWithAssociations) {\n throw new CrmException('Deal not found');\n }\n\n return [\n 'id' => $deal->getId(),\n 'properties' => $deal->getProperties(),\n 'associations' => $deal->getAssociations(),\n ];\n }\n\n /**\n * Generic batch read method for HubSpot objects\n *\n * @param string $objectType The object type ('deals', 'companies', 'contacts')\n * @param array<string> $crmIds Array of HubSpot object IDs (max 100)\n * @param array<string> $fields Array of property names to fetch\n *\n * @return array<string, array> Array keyed by CRM ID with object data\n */\n private function batchReadObjects(string $objectType, array $crmIds, array $fields): array\n {\n if (empty($crmIds)) {\n return [];\n }\n\n $this->validateBatchSize($objectType, $crmIds);\n $this->ensureValidToken();\n\n try {\n $batchConfig = $this->createBatchConfiguration($objectType);\n $batchReadRequest = $this->prepareBatchRequest($batchConfig, $crmIds, $fields);\n $response = $batchConfig['api']->read($batchReadRequest);\n\n $this->validateApiResponse($response, $objectType);\n\n $results = $this->processApiResults($response);\n $this->logBatchResults($objectType, $crmIds, $results);\n\n return $results;\n } catch (\\Throwable $e) {\n $this->handleBatchError($e, $objectType, $crmIds);\n }\n }\n\n private function validateBatchSize(string $objectType, array $crmIds): void\n {\n if (count($crmIds) > 100) {\n throw new \\InvalidArgumentException(\"Batch size cannot exceed 100 {$objectType}\");\n }\n }\n\n private function createBatchConfiguration(string $objectType): array\n {\n $configurations = [\n 'deals' => [\n 'batchReadRequest' => new \\HubSpot\\Client\\Crm\\Deals\\Model\\BatchReadInputSimplePublicObjectId(),\n 'inputClass' => \\HubSpot\\Client\\Crm\\Deals\\Model\\SimplePublicObjectId::class,\n 'api' => $this->getNewInstance()->crm()->deals()->batchApi(),\n ],\n 'companies' => [\n 'batchReadRequest' => new \\HubSpot\\Client\\Crm\\Companies\\Model\\BatchReadInputSimplePublicObjectId(),\n 'inputClass' => \\HubSpot\\Client\\Crm\\Companies\\Model\\SimplePublicObjectId::class,\n 'api' => $this->getNewInstance()->crm()->companies()->batchApi(),\n ],\n 'contacts' => [\n 'batchReadRequest' => new \\HubSpot\\Client\\Crm\\Contacts\\Model\\BatchReadInputSimplePublicObjectId(),\n 'inputClass' => \\HubSpot\\Client\\Crm\\Contacts\\Model\\SimplePublicObjectId::class,\n 'api' => $this->getNewInstance()->crm()->contacts()->batchApi(),\n ],\n ];\n\n if (! isset($configurations[$objectType])) {\n throw new \\InvalidArgumentException(\"Unsupported object type: {$objectType}\");\n }\n\n return $configurations[$objectType];\n }\n\n private function prepareBatchRequest(array $batchConfig, array $crmIds, array $fields): object\n {\n $batchReadRequest = $batchConfig['batchReadRequest'];\n $inputClass = $batchConfig['inputClass'];\n\n $inputs = array_map(function ($crmId) use ($inputClass) {\n $input = new $inputClass();\n $input->setId($crmId);\n\n return $input;\n }, $crmIds);\n\n $batchReadRequest->setInputs($inputs);\n $batchReadRequest->setProperties($fields);\n\n return $batchReadRequest;\n }\n\n private function validateApiResponse($response, string $objectType): void\n {\n if (! $response) {\n throw new CrmException(\"HubSpot API returned null response for {$objectType} batch read\");\n }\n }\n\n private function processApiResults($response): array\n {\n $results = [];\n $responseResults = $response->getResults();\n\n if ($responseResults) {\n foreach ($responseResults as $object) {\n if ($object && $object->getId()) {\n $results[$object->getId()] = [\n 'id' => $object->getId(),\n 'properties' => $object->getProperties() ?: [],\n ];\n }\n }\n }\n\n return $results;\n }\n\n private function logBatchResults(string $objectType, array $crmIds, array $results): void\n {\n $this->log->info(\"[HubSpot] Batch fetched {$objectType}\", [\n 'requested_count' => count($crmIds),\n 'returned_count' => count($results),\n 'crm_ids' => $crmIds,\n ]);\n }\n\n private function handleBatchError(\\Throwable $e, string $objectType, array $crmIds): void\n {\n $errorMessage = $e->getMessage() ?: 'Unknown error';\n $errorTrace = $e->getTraceAsString() ?: 'No trace available';\n\n $this->log->error(\"[HubSpot] Failed to batch fetch {$objectType}\", [\n 'crm_ids' => $crmIds,\n 'error' => $errorMessage,\n 'trace' => $errorTrace,\n ]);\n\n throw new CrmException(\"Failed to batch fetch {$objectType}: \" . $errorMessage);\n }\n\n /**\n * Batch read multiple opportunities by their CRM IDs\n *\n * @param array<string> $crmIds Array of HubSpot deal IDs (max 100)\n * @param array<string> $fields Array of property names to fetch\n *\n * @return array<string, array> Array keyed by CRM ID with opportunity data\n */\n public function getOpportunitiesByIds(array $crmIds, array $fields): array\n {\n return $this->batchReadObjects('deals', $crmIds, $fields);\n }\n\n /**\n * Batch read multiple companies by their CRM IDs\n *\n * @param array<string> $crmIds Array of HubSpot company IDs (max 100)\n * @param array<string> $fields Array of property names to fetch\n *\n * @return array<string, array> Array keyed by CRM ID with company data\n */\n public function getCompaniesByIds(array $crmIds, array $fields): array\n {\n return $this->batchReadObjects('companies', $crmIds, $fields);\n }\n\n /**\n * Batch read multiple contacts by their CRM IDs\n *\n * @param array<string> $crmIds Array of HubSpot contact IDs (max 100)\n * @param array<string> $fields Array of property names to fetch\n *\n * @return array<string, array> Array keyed by CRM ID with contact data\n */\n public function getContactsByIds(array $crmIds, array $fields): array\n {\n return $this->batchReadObjects('contacts', $crmIds, $fields);\n }\n\n /**\n * @throws CompanyApiException\n * @throws CrmException\n */\n public function getAccountById(string $crmId, array $fields): array\n {\n try {\n $company = $this->getNewInstance()->crm()->companies()->basicApi()->getById(\n $crmId,\n implode(',', $fields),\n );\n } catch (CompanyApiException $e) {\n $this->log->info('[Hubspot] Failed to fetch account', [\n 'crm_id' => $crmId,\n 'reason' => $e->getMessage(),\n ]);\n\n throw $e;\n }\n\n if (! $company instanceof CompaniesWithAssociations) {\n throw new CrmException('Account not found');\n }\n\n return [\n 'id' => $company->getId(),\n 'properties' => $company->getProperties(),\n ];\n }\n\n /**\n * @throws ContactApiException\n * @throws CrmException\n */\n public function getContactById(string $crmId, array $fields): array\n {\n try {\n $contact = $this->getNewInstance()->crm()->contacts()->basicApi()->getById(\n $crmId,\n implode(',', $fields)\n );\n } catch (ContactApiException $e) {\n $this->log->info('[Hubspot] Failed to fetch contact', [\n 'crm_id' => $crmId,\n 'reason' => $e->getMessage(),\n ]);\n\n throw $e;\n }\n\n if (! $contact instanceof ContactsWithAssociations) {\n throw new CrmException('Contact not found');\n }\n\n return [\n 'id' => $contact->getId(),\n 'properties' => $contact->getProperties(),\n ];\n }\n\n /**\n * This is email search request that Hubspot offers as GET (more generous quota)\n */\n public function getContactByEmail(string $email, array $fields = []): array\n {\n try {\n $contact = $this->getNewInstance()->crm()->contacts()->basicApi()->getById(\n $email,\n implode(',', $fields),\n null,\n false,\n 'email'\n );\n\n return [\n 'id' => $contact->getId(),\n 'properties' => $contact->getProperties(),\n ];\n } catch (ContactApiException $e) {\n $this->log->info('[Hubspot] Failed to fetch contact', [\n 'email' => $email,\n 'reason' => $e->getMessage(),\n ]);\n\n return [];\n }\n }\n\n /**\n * @throws CrmException\n */\n public function fetchProperty(string $objectType, string $propertyId): Property\n {\n $result = $this->getNewInstance()->crm()->properties()->coreApi()->getByName($objectType, $propertyId);\n\n if (! $result instanceof Property) {\n $this->log->error('[Hubspot] Failed to fetch property', [\n 'object_type' => $objectType,\n 'property_id' => $propertyId,\n 'reason' => $result->getMessage(),\n ]);\n\n throw new CrmException('Failed to fetch property');\n }\n\n return $result;\n }\n\n /**\n * @return array<CrmFieldOption>\n */\n public function fetchPropertyOptions(string $objectType, string $propertyId): array\n {\n /** @var array<CrmFieldOption> */\n return $this->fetchProperty($objectType, $propertyId)->getOptions();\n }\n\n /**\n * @return array<array{id:string, label:string, deleted:bool}>\n */\n public function fetchCallDispositions(): array\n {\n /** @var Response $response */\n $response = $this->getInstance()->engagements()->getCallDispositions();\n\n /**\n * @var array<array{\n * id:string,\n * label:string,\n * deleted: bool\n * }>\n */\n return $response->toArray();\n }\n\n /**\n * @return array<CrmFieldOption>\n */\n public function fetchOpportunityPipelineStages(): array\n {\n $stages = [];\n $apiResponse = $this->getNewInstance()->crm()->pipelines()->pipelinesApi()->getAll('deals');\n\n if ($apiResponse instanceof Error) {\n $this->log->error('[Hubspot] Failed to fetch opportunity pipelines', [\n 'reason' => $apiResponse->getMessage(),\n ]);\n\n return [];\n }\n\n foreach ($apiResponse->getResults() as $pipeline) {\n $pipelineStages = array_map(\n static function (PipelineStage $stage) {\n return [\n 'id' => $stage->getId(),\n 'label' => $stage->getLabel(),\n ];\n },\n $pipeline->getStages()\n );\n\n $stages = array_merge($stages, $pipelineStages);\n }\n\n return $stages;\n }\n\n public function fetchOpportunityPipelines(): array\n {\n $pipelines = [];\n\n try {\n $apiResponse = $this->makeRequest('/crm/v3/pipelines/deals');\n } catch (\\Exception $e) {\n $this->log->info('[Hubspot] Failed to fetch opportunity pipelines', [\n 'reason' => $e->getMessage(),\n ]);\n\n return [];\n }\n\n $response = $apiResponse->toArray();\n\n foreach ($response['results'] as $pipeline) {\n $pipelines[] = [\n 'id' => $pipeline['id'],\n 'label' => $pipeline['label'],\n ];\n }\n\n return $pipelines;\n }\n\n /**\n * @return array<CrmFieldOption>\n */\n public function fetchMeetingOutcomeFieldOptions(Field $field): array\n {\n return $field->getCrmProviderId() === 'meetingOutcome'\n ? $this->fetchMeetingOutcomeTypes()\n : $this->fetchCallActivityTypes();\n }\n\n public function fetchMeetingOutcomeTypes(): array\n {\n return $this->extractMeetingTypeOptions(\n 'https://api.hubapi.com/crm/v3/properties/meeting/hs_meeting_outcome'\n );\n }\n\n public function fetchCallActivityTypes(): array\n {\n return $this->extractMeetingTypeOptions(\n 'https://api.hubapi.com/crm/v3/properties/call/hs_activity_type'\n );\n }\n\n private function extractMeetingTypeOptions(string $endpoint): array\n {\n /** @var Response $response */\n $response = $this->getInstance()\n ->getClient()\n ->request('GET', $endpoint);\n\n /**\n * @var array<array{\n * value: string,\n * label: string,\n * displayOrder: int\n * }> $optionData\n */\n $optionData = $response->toArray()['options'] ?? [];\n\n $options = [];\n foreach ($optionData as $item) {\n $options[] = [\n 'id' => $item['value'],\n 'value' => $item['value'],\n 'label' => $item['label'],\n 'display_order' => $item['displayOrder'],\n ];\n }\n\n return $options;\n }\n\n /**\n * @return array<CrmFieldOption>\n */\n public function fetchDispositionFieldOptions(): array\n {\n $options = [];\n\n $dispositions = $this->fetchCallDispositions();\n\n foreach ($dispositions as $disposition) {\n if ($disposition['deleted'] !== false) {\n continue;\n }\n\n $option['value'] = $disposition['id'];\n $option['id'] = $disposition['id'];\n $option['label'] = $disposition['label'];\n\n $options[] = $option;\n }\n\n return $options;\n }\n\n /**\n * @return array<CrmFieldOption>\n */\n public function fetchOpportunityFieldOptions(Field $field): array\n {\n if ($field->isStageField()) {\n return $this->fetchOpportunityPipelineStages();\n }\n\n if ($field->isPipelineField()) {\n return $this->fetchOpportunityPipelines();\n }\n\n return $this->fetchPropertyOptions('deals', $field->getCrmProviderId());\n }\n\n /**\n * @throws BadRequest\n * @throws HubspotException\n */\n public function makeRequest(string $endpoint, $method = 'GET', $payload = [], ?string $queryString = null)\n {\n $endpoint = self::BASE_URL . $endpoint;\n\n if ($method === 'GET') {\n $response = $this->getInstance()->getClient()?->request(\n method: $method,\n endpoint: $endpoint,\n query_string: $queryString\n );\n } else {\n $response = $this->getInstance()->getClient()->request($method, $endpoint, [\n 'json' => ($payload),\n ]);\n }\n\n $max = $response->getHeaderLine('X-HubSpot-RateLimit-Max'); // \"110\"\n $remaining = $response->getHeaderLine('X-HubSpot-RateLimit-Remaining'); // \"109\"\n $interval = $response->getHeaderLine('X-HubSpot-RateLimit-Interval-Milliseconds'); // \"10000\"\n $body = json_decode((string) $response->getBody(), true);\n\n \\Illuminate\\Support\\Facades\\Log::channel('custom_channel')->info('$max ' . PHP_EOL . print_r($max, true));\n \\Illuminate\\Support\\Facades\\Log::channel('custom_channel')->info('$remaining ' . PHP_EOL . print_r($remaining, true));\n \\Illuminate\\Support\\Facades\\Log::channel('custom_channel')->info('$interval ' . PHP_EOL . print_r($interval, true));\n \\Illuminate\\Support\\Facades\\Log::channel('custom_channel')->info('$body ' . PHP_EOL . print_r($body, true));\n\n return $response;\n }\n\n /**\n * @throws BadRequest\n * @throws HubspotException\n */\n public function createMeeting(array $payload): Response\n {\n $endpoint = '/crm/v3/objects/meetings';\n\n return $this->makeRequest($endpoint, 'POST', $payload);\n }\n\n /**\n * @throws BadRequest\n * @throws HubspotException\n */\n public function updateMeeting(string $meetingId, array $payload): Response\n {\n $endpoint = '/crm/v3/objects/meetings/' . $meetingId;\n\n return $this->makeRequest($endpoint, 'PATCH', $payload);\n }\n\n /**\n * @throws \\Exception\n */\n public function createNote(\n string $body,\n string $ownerId,\n int $timestamp,\n string $objectId,\n NoteObject $noteObject\n ): ?string {\n try {\n $noteInput = new SimplePublicObjectInput([\n 'properties' => [\n 'hs_note_body' => $body,\n 'hubspot_owner_id' => $ownerId,\n 'hs_timestamp' => $timestamp,\n ],\n ]);\n\n // Create note\n $note = $this->getNewInstance()->crm()->objects()->basicApi()->create('note', $noteInput);\n\n $this->getNewInstance()->crm()->objects()->associationsApi()->create(\n 'note',\n $note->getId(),\n $this->getNoteObject($noteObject),\n $objectId,\n $this->getNoteAssociationType($noteObject),\n );\n\n return $note->getId();\n } catch (\\Exception $e) {\n $this->log->error('[Hubspot] Failed to create note', [\n 'objectId' => $objectId,\n 'noteObject' => $noteObject->getObjectType(),\n 'reason' => $e->getMessage(),\n ]);\n\n \\Sentry::captureException($e);\n }\n\n return null;\n }\n\n public function updateEngagement(string $objectId, array $engagement, array $metadata): void\n {\n $this->getInstance()->engagements()->update($objectId, $engagement, $metadata);\n }\n\n public function getEngagementData(string $engagementId): array\n {\n $engagement = $this->getInstance()->engagements()->get($engagementId);\n\n return $engagement->toArray();\n }\n\n public function createEngagement(array $engagement, array $associations, array $metadata): Response\n {\n return $this->getInstance()\n ->engagements()\n ->create($engagement, $associations, $metadata);\n }\n\n public function isUnauthorizedException(\\Exception $e): bool\n {\n // Check for specific HubSpot API exception types first\n if ($e instanceof BadRequest) {\n // BadRequest can contain 401 status codes\n return $e->getCode() === 401;\n }\n\n // Check for HTTP client exceptions with status codes\n if ($e instanceof \\GuzzleHttp\\Exception\\RequestException && $e->hasResponse()) {\n $response = $e->getResponse();\n if ($response !== null) {\n return $response->getStatusCode() === 401;\n }\n }\n\n // Check for Guzzle HTTP exceptions\n if ($e instanceof \\GuzzleHttp\\Exception\\ClientException) {\n return $e->getCode() === 401;\n }\n\n // Fallback to string matching as last resort, but be more specific\n $message = strtolower($e->getMessage());\n\n return str_contains($message, '401 unauthorized') ||\n str_contains($message, 'http 401') ||\n str_contains($message, 'status code 401') ||\n (preg_match('/\\b401\\b/', $message) && str_contains($message, 'unauthorized'));\n }\n\n /**\n * Validates and refreshes the access token if needed before API requests.\n * This ensures long-running processes don't fail due to token expiration.\n *\n * @throws SocialAccountTokenInvalidException\n */\n public function ensureValidToken(): void\n {\n if ($this->oauthAccount === null) {\n return;\n }\n\n $newToken = $this->tokenManager->ensureValidToken($this->oauthAccount);\n if ($newToken !== null) {\n $this->accessToken = $newToken;\n }\n }\n\n public function getConfig()\n {\n return $this->config;\n }\n\n // returns only active (archived=false)\n public function getOwners(): array\n {\n return $this->getNewInstance()->crm()->owners()->getAll();\n }\n\n /**\n * @param bool $archived\n *\n * @return array<Owner>|[]\n */\n public function getOwnersArchived(bool $archived = true): array\n {\n $endpoint = '/crm/v3/owners';\n $queryParams = [\n 'archived' => $archived ? 'true' : 'false',\n ];\n $queryString = http_build_query($queryParams);\n\n $owners = [];\n\n try {\n $response = $this->makeRequest(endpoint: $endpoint, queryString: $queryString);\n $responseData = $response?->toArray();\n\n foreach ($responseData['results'] as $result) {\n try {\n $owners[] = Owner::create($result);\n } catch (Throwable $e) {\n $this->log->error('[HubSpot] Failed to process owner data', [\n 'result' => $result,\n 'error' => $e->getMessage(),\n ]);\n\n continue;\n }\n }\n } catch (Throwable $e) {\n $this->log->error('HubSpot] Failed to fetch owners', [\n 'archived' => $archived,\n 'error' => $e->getMessage(),\n ]);\n\n return [];\n }\n\n return $owners;\n }\n\n public function getMeeting(string $engagementId): ObjectWithAssociations\n {\n return $this->getNewInstance()->crm()->objects()->basicApi()\n ->getById('meeting', $engagementId, null, 'contact,company,deal');\n }\n\n public function deleteEngagement(string $engagementId): void\n {\n $this->getInstance()->engagements()->delete((int) $engagementId);\n }\n\n public function getAssociationsData(array $ids, string $fromObject, string $toObject): array\n {\n $associationData = [];\n $idChunks = array_chunk($ids, self::ASSOCIATIONS_BATCH_SIZE_LIMIT);\n\n foreach ($idChunks as $idChunk) {\n try {\n $batchInput = new \\HubSpot\\Client\\Crm\\Associations\\Model\\BatchInputPublicObjectId();\n $batchInput->setInputs(array_map(function ($id) {\n $publicObjectId = new \\HubSpot\\Client\\Crm\\Associations\\Model\\PublicObjectId();\n $publicObjectId->setId($id);\n\n return $publicObjectId;\n }, $idChunk));\n\n $associatedObjectsData = $this\n ->getNewInstance()\n ->crm()\n ->associations()\n ->batchApi()\n ->read($fromObject, $toObject, $batchInput);\n\n if ($associatedObjectsData instanceof \\HubSpot\\Client\\Crm\\Associations\\Model\\BatchResponsePublicAssociationMulti) {\n foreach ($associatedObjectsData->getResults() as $association) {\n $from = $association->getFrom()->getId();\n $toAssociations = $association->getTo();\n\n if (! empty($toAssociations)) {\n $associationData[$from] = array_map(function ($item) {\n return $item->getId();\n }, $toAssociations);\n }\n }\n }\n } catch (\\Exception $e) {\n $this->log->error('[Hubspot] Failed to fetch associations', [\n 'from_object' => $fromObject,\n 'to_object' => $toObject,\n 'reason' => $e->getMessage(),\n ]);\n }\n }\n\n return $associationData;\n }\n\n /**\n * @throws \\Exception\n */\n private function getNoteAssociationType(NoteObject $noteObject): string\n {\n return match($noteObject) {\n NoteObject::Opportunity => 'note_to_deal',\n NoteObject::Lead, NoteObject::Contact => 'note_to_contact', // or 'note_to_lead' if your portal supports it\n NoteObject::Account => 'note_to_company',\n NoteObject::Call, NoteObject::Event => throw new \\Exception('Not supported'),\n };\n }\n\n /**\n * @throws \\Exception\n */\n private function getNoteObject(NoteObject $noteObject): string\n {\n return match($noteObject) {\n NoteObject::Opportunity => 'deal',\n NoteObject::Lead, NoteObject::Contact => 'contact',\n NoteObject::Account => 'company',\n NoteObject::Call, NoteObject::Event => throw new \\Exception('Not supported'),\n };\n }\n\n public function addAssociations(string $objectType, string $associationType, array $payload): Response\n {\n $endpoint = \"/crm/v4/associations/$objectType/$associationType/batch/create\";\n\n return $this->makeRequest($endpoint, 'POST', $payload);\n }\n\n public function removeAssociations(string $objectType, string $associationType, array $payload): Response\n {\n $endpoint = \"/crm/v4/associations/$objectType/$associationType/batch/archive\";\n\n return $this->makeRequest($endpoint, 'POST', $payload);\n }\n}","role_description":"text entry area","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"Project","depth":3,"on_screen":false,"role_description":"text"},{"role":"AXButton","text":"Project","depth":3,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"New File or Directory…","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.018055556,"height":0.026666667},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Expand Selected","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.018055556,"height":0.026666667},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Collapse All","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.018055556,"height":0.026666667},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Options","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.018055556,"height":0.026666667},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Hide","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.018055556,"height":0.026666667},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false}]...
|
-188086479456983350
|
6378757184196315236
|
click
|
accessibility
|
NULL
|
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
Editor for custom.log
Sync Changes
Hide This Notification
Code changed:
Hide
2
60
Previous Highlighted Error
Next Highlighted Error
<?php
declare(strict_types=1);
namespace Jiminny\Services\Crm\Hubspot;
use HubSpot\Client\Crm\Deals\ApiException as DealApiException;
use HubSpot\Client\Crm\Contacts\ApiException as ContactApiException;
use HubSpot\Client\Crm\Companies\ApiException as CompanyApiException;
use HubSpot\Client\Crm\Contacts\Model\SimplePublicObjectWithAssociations as ContactsWithAssociations;
use HubSpot\Client\Crm\Companies\Model\SimplePublicObjectWithAssociations as CompaniesWithAssociations;
use HubSpot\Client\Crm\Deals\Model\SimplePublicObjectWithAssociations as DealWithAssociations;
use HubSpot\Client\Crm\Objects\Model\SimplePublicObjectInput;
use HubSpot\Client\Crm\Objects\Model\SimplePublicObjectWithAssociations as ObjectWithAssociations;
use HubSpot\Client\Crm\Pipelines\Model\Error;
use HubSpot\Client\Crm\Pipelines\Model\PipelineStage;
use HubSpot\Client\Crm\Properties\Model\Property;
use HubSpot\Discovery\Discovery;
use Jiminny\Component\Utility\Service\ProviderRateLimiter;
use Jiminny\Exceptions\CrmException;
use Jiminny\Exceptions\RateLimitException;
use Jiminny\Exceptions\SocialAccountTokenInvalidException;
use Jiminny\Jobs\Crm\NoteObject;
use Jiminny\Models\Crm\Field;
use Jiminny\Services\Crm\BaseClient;
use Jiminny\Services\Crm\Hubspot\DTO\Response\Owner;
use Jiminny\Services\SocialAccountService;
use SevenShores\Hubspot\Exceptions\BadRequest;
use SevenShores\Hubspot\Exceptions\HubspotException;
use SevenShores\Hubspot\Factory;
use SevenShores\Hubspot\Http\Response;
use Jiminny\Services\Crm\Hubspot\Pagination\HubspotPaginationService;
use Throwable;
/**
* @phpstan-type CrmFieldOption array{id:string, label:string, value?:string}
*/
class Client extends BaseClient implements HubspotClientInterface
{
public const string MIN_API_VERSION = '2';
public const string BASE_URL = '[URL_WITH_CREDENTIALS] T
* @param callable(): T $apiCall
* @return T
*
* @throws RateLimitException
*/
private function executeRequest(callable $apiCall)
{
if (! $this->rateLimiter->canMakeRequest($this->config)) {
$retryAfter = $this->rateLimiter->requestAvailableIn($this->config);
$this->log->warning('[Hubspot] Rate limit exceeded, deferring request', [
'team_id' => $this->config->team_id,
'config_id' => $this->config->getId(),
'retry_after' => $retryAfter,
]);
throw new RateLimitException(
'Hubspot rate limit reached for configuration ' . $this->config->getId(),
$retryAfter,
);
}
$this->rateLimiter->incrementRequestCount($this->config);
try {
return $apiCall();
} catch (Throwable $e) {
if ($this->isHubspotRateLimit($e)) {
$retryAfter = $this->parseRetryAfter($e);
$this->log->warning('[Hubspot] Received 429 from API', [
'team_id' => $this->config->team_id,
'config_id' => $this->config->getId(),
'retry_after' => $retryAfter,
'reason' => $e->getMessage(),
]);
throw new RateLimitException('Hubspot returned 429', $retryAfter, $e);
}
throw $e;
}
}
private function isHubspotRateLimit(Throwable $e): bool
{
return method_exists($e, 'getCode') && (int) $e->getCode() === 429;
}
private function parseRetryAfter(Throwable $e): int
{
if (method_exists($e, 'getResponseHeaders')) {
$headers = $e->getResponseHeaders() ?: [];
$value = $headers['Retry-After'] ?? $headers['retry-after'] ?? null;
if (is_array($value)) {
$value = $value[0] ?? null;
}
if (is_numeric($value)) {
return (int) $value;
}
}
return 10;
}
public function getMinimumApiVersion(): string
{
return self::MIN_API_VERSION;
}
public function getInstance(): Factory
{
return new Factory([
'key' => $this->accessToken,
'oauth2' => true,
'base_url' => $this->baseUrl,
]);
}
public function getNewInstance(): Discovery
{
return \HubSpot\Factory::createWithAccessToken($this->accessToken);
}
/**
* Secondly and daily limits for Hubspot API
*
* Product Tier: Free & Starter | Professional & Enterprise | API add-on (any tier)
* Burst: 100/10 seconds | 150/10 seconds | 200/10 seconds
* Daily: 250,000 | 500,000 | 1,000,000
*
* Official documentation states: The search endpoints are rate limited to five requests per second.
* Since with 5 RPS were still hitting secondly rate limits we lowered it to 4
*/
public function getPaginatedData(array $payload, string $type, int $offset = 0): array
{
$total = 0;
$lastId = null;
$rows = [];
foreach ($this->getPaginatedDataGenerator($payload, $type, $offset, $total, $lastId) as $row) {
$rows[] = $row;
}
return ['results' => $rows, 'total' => $total, 'last_record' => $lastId];
}
/**
* @throws HubspotException
* @throws SocialAccountTokenInvalidException
* @throws BadRequest
*/
public function getPaginatedDataGenerator(
array $payload,
string $type,
int $offset = 0,
int &$total = 0,
?string &$lastRecordId = null
): \Generator {
return $this->paginationService->getPaginatedDataGenerator(
$this,
$payload,
$type,
$offset,
$total,
$lastRecordId
);
}
/**
* @throws DealApiException
* @throws CrmException
*/
public function getOpportunityById(string $crmId, array $fields): array
{
try {
$deal = $this->executeRequest(fn () => $this->getNewInstance()->crm()->deals()->basicApi()->getById(
$crmId,
implode(',', $fields),
'companies,contacts'
));
} catch (DealApiException $e) {
$this->log->info('[Hubspot] Failed to fetch opportunity', [
'crm_id' => $crmId,
'reason' => $e->getMessage(),
]);
throw $e;
}
if (! $deal instanceof DealWithAssociations) {
throw new CrmException('Deal not found');
}
return [
'id' => $deal->getId(),
'properties' => $deal->getProperties(),
'associations' => $deal->getAssociations(),
];
}
/**
* Generic batch read method for HubSpot objects
*
* @param string $objectType The object type ('deals', 'companies', 'contacts')
* @param array<string> $crmIds Array of HubSpot object IDs (max 100)
* @param array<string> $fields Array of property names to fetch
*
* @return array<string, array> Array keyed by CRM ID with object data
*/
private function batchReadObjects(string $objectType, array $crmIds, array $fields): array
{
if (empty($crmIds)) {
return [];
}
$this->validateBatchSize($objectType, $crmIds);
$this->ensureValidToken();
try {
$batchConfig = $this->createBatchConfiguration($objectType);
$batchReadRequest = $this->prepareBatchRequest($batchConfig, $crmIds, $fields);
$response = $batchConfig['api']->read($batchReadRequest);
$this->validateApiResponse($response, $objectType);
$results = $this->processApiResults($response);
$this->logBatchResults($objectType, $crmIds, $results);
return $results;
} catch (\Throwable $e) {
$this->handleBatchError($e, $objectType, $crmIds);
}
}
private function validateBatchSize(string $objectType, array $crmIds): void
{
if (count($crmIds) > 100) {
throw new \InvalidArgumentException("Batch size cannot exceed 100 {$objectType}");
}
}
private function createBatchConfiguration(string $objectType): array
{
$configurations = [
'deals' => [
'batchReadRequest' => new \HubSpot\Client\Crm\Deals\Model\BatchReadInputSimplePublicObjectId(),
'inputClass' => \HubSpot\Client\Crm\Deals\Model\SimplePublicObjectId::class,
'api' => $this->getNewInstance()->crm()->deals()->batchApi(),
],
'companies' => [
'batchReadRequest' => new \HubSpot\Client\Crm\Companies\Model\BatchReadInputSimplePublicObjectId(),
'inputClass' => \HubSpot\Client\Crm\Companies\Model\SimplePublicObjectId::class,
'api' => $this->getNewInstance()->crm()->companies()->batchApi(),
],
'contacts' => [
'batchReadRequest' => new \HubSpot\Client\Crm\Contacts\Model\BatchReadInputSimplePublicObjectId(),
'inputClass' => \HubSpot\Client\Crm\Contacts\Model\SimplePublicObjectId::class,
'api' => $this->getNewInstance()->crm()->contacts()->batchApi(),
],
];
if (! isset($configurations[$objectType])) {
throw new \InvalidArgumentException("Unsupported object type: {$objectType}");
}
return $configurations[$objectType];
}
private function prepareBatchRequest(array $batchConfig, array $crmIds, array $fields): object
{
$batchReadRequest = $batchConfig['batchReadRequest'];
$inputClass = $batchConfig['inputClass'];
$inputs = array_map(function ($crmId) use ($inputClass) {
$input = new $inputClass();
$input->setId($crmId);
return $input;
}, $crmIds);
$batchReadRequest->setInputs($inputs);
$batchReadRequest->setProperties($fields);
return $batchReadRequest;
}
private function validateApiResponse($response, string $objectType): void
{
if (! $response) {
throw new CrmException("HubSpot API returned null response for {$objectType} batch read");
}
}
private function processApiResults($response): array
{
$results = [];
$responseResults = $response->getResults();
if ($responseResults) {
foreach ($responseResults as $object) {
if ($object && $object->getId()) {
$results[$object->getId()] = [
'id' => $object->getId(),
'properties' => $object->getProperties() ?: [],
];
}
}
}
return $results;
}
private function logBatchResults(string $objectType, array $crmIds, array $results): void
{
$this->log->info("[HubSpot] Batch fetched {$objectType}", [
'requested_count' => count($crmIds),
'returned_count' => count($results),
'crm_ids' => $crmIds,
]);
}
private function handleBatchError(\Throwable $e, string $objectType, array $crmIds): void
{
$errorMessage = $e->getMessage() ?: 'Unknown error';
$errorTrace = $e->getTraceAsString() ?: 'No trace available';
$this->log->error("[HubSpot] Failed to batch fetch {$objectType}", [
'crm_ids' => $crmIds,
'error' => $errorMessage,
'trace' => $errorTrace,
]);
throw new CrmException("Failed to batch fetch {$objectType}: " . $errorMessage);
}
/**
* Batch read multiple opportunities by their CRM IDs
*
* @param array<string> $crmIds Array of HubSpot deal IDs (max 100)
* @param array<string> $fields Array of property names to fetch
*
* @return array<string, array> Array keyed by CRM ID with opportunity data
*/
public function getOpportunitiesByIds(array $crmIds, array $fields): array
{
return $this->batchReadObjects('deals', $crmIds, $fields);
}
/**
* Batch read multiple companies by their CRM IDs
*
* @param array<string> $crmIds Array of HubSpot company IDs (max 100)
* @param array<string> $fields Array of property names to fetch
*
* @return array<string, array> Array keyed by CRM ID with company data
*/
public function getCompaniesByIds(array $crmIds, array $fields): array
{
return $this->batchReadObjects('companies', $crmIds, $fields);
}
/**
* Batch read multiple contacts by their CRM IDs
*
* @param array<string> $crmIds Array of HubSpot contact IDs (max 100)
* @param array<string> $fields Array of property names to fetch
*
* @return array<string, array> Array keyed by CRM ID with contact data
*/
public function getContactsByIds(array $crmIds, array $fields): array
{
return $this->batchReadObjects('contacts', $crmIds, $fields);
}
/**
* @throws CompanyApiException
* @throws CrmException
*/
public function getAccountById(string $crmId, array $fields): array
{
try {
$company = $this->getNewInstance()->crm()->companies()->basicApi()->getById(
$crmId,
implode(',', $fields),
);
} catch (CompanyApiException $e) {
$this->log->info('[Hubspot] Failed to fetch account', [
'crm_id' => $crmId,
'reason' => $e->getMessage(),
]);
throw $e;
}
if (! $company instanceof CompaniesWithAssociations) {
throw new CrmException('Account not found');
}
return [
'id' => $company->getId(),
'properties' => $company->getProperties(),
];
}
/**
* @throws ContactApiException
* @throws CrmException
*/
public function getContactById(string $crmId, array $fields): array
{
try {
$contact = $this->getNewInstance()->crm()->contacts()->basicApi()->getById(
$crmId,
implode(',', $fields)
);
} catch (ContactApiException $e) {
$this->log->info('[Hubspot] Failed to fetch contact', [
'crm_id' => $crmId,
'reason' => $e->getMessage(),
]);
throw $e;
}
if (! $contact instanceof ContactsWithAssociations) {
throw new CrmException('Contact not found');
}
return [
'id' => $contact->getId(),
'properties' => $contact->getProperties(),
];
}
/**
* This is email search request that Hubspot offers as GET (more generous quota)
*/
public function getContactByEmail(string $email, array $fields = []): array
{
try {
$contact = $this->getNewInstance()->crm()->contacts()->basicApi()->getById(
$email,
implode(',', $fields),
null,
false,
'email'
);
return [
'id' => $contact->getId(),
'properties' => $contact->getProperties(),
];
} catch (ContactApiException $e) {
$this->log->info('[Hubspot] Failed to fetch contact', [
'email' => $email,
'reason' => $e->getMessage(),
]);
return [];
}
}
/**
* @throws CrmException
*/
public function fetchProperty(string $objectType, string $propertyId): Property
{
$result = $this->getNewInstance()->crm()->properties()->coreApi()->getByName($objectType, $propertyId);
if (! $result instanceof Property) {
$this->log->error('[Hubspot] Failed to fetch property', [
'object_type' => $objectType,
'property_id' => $propertyId,
'reason' => $result->getMessage(),
]);
throw new CrmException('Failed to fetch property');
}
return $result;
}
/**
* @return array<CrmFieldOption>
*/
public function fetchPropertyOptions(string $objectType, string $propertyId): array
{
/** @var array<CrmFieldOption> */
return $this->fetchProperty($objectType, $propertyId)->getOptions();
}
/**
* @return array<array{id:string, label:string, deleted:bool}>
*/
public function fetchCallDispositions(): array
{
/** @var Response $response */
$response = $this->getInstance()->engagements()->getCallDispositions();
/**
* @var array<array{
* id:string,
* label:string,
* deleted: bool
* }>
*/
return $response->toArray();
}
/**
* @return array<CrmFieldOption>
*/
public function fetchOpportunityPipelineStages(): array
{
$stages = [];
$apiResponse = $this->getNewInstance()->crm()->pipelines()->pipelinesApi()->getAll('deals');
if ($apiResponse instanceof Error) {
$this->log->error('[Hubspot] Failed to fetch opportunity pipelines', [
'reason' => $apiResponse->getMessage(),
]);
return [];
}
foreach ($apiResponse->getResults() as $pipeline) {
$pipelineStages = array_map(
static function (PipelineStage $stage) {
return [
'id' => $stage->getId(),
'label' => $stage->getLabel(),
];
},
$pipeline->getStages()
);
$stages = array_merge($stages, $pipelineStages);
}
return $stages;
}
public function fetchOpportunityPipelines(): array
{
$pipelines = [];
try {
$apiResponse = $this->makeRequest('/crm/v3/pipelines/deals');
} catch (\Exception $e) {
$this->log->info('[Hubspot] Failed to fetch opportunity pipelines', [
'reason' => $e->getMessage(),
]);
return [];
}
$response = $apiResponse->toArray();
foreach ($response['results'] as $pipeline) {
$pipelines[] = [
'id' => $pipeline['id'],
'label' => $pipeline['label'],
];
}
return $pipelines;
}
/**
* @return array<CrmFieldOption>
*/
public function fetchMeetingOutcomeFieldOptions(Field $field): array
{
return $field->getCrmProviderId() === 'meetingOutcome'
? $this->fetchMeetingOutcomeTypes()
: $this->fetchCallActivityTypes();
}
public function fetchMeetingOutcomeTypes(): array
{
return $this->extractMeetingTypeOptions(
'[URL_WITH_CREDENTIALS] Response $response */
$response = $this->getInstance()
->getClient()
->request('GET', $endpoint);
/**
* @var array<array{
* value: string,
* label: string,
* displayOrder: int
* }> $optionData
*/
$optionData = $response->toArray()['options'] ?? [];
$options = [];
foreach ($optionData as $item) {
$options[] = [
'id' => $item['value'],
'value' => $item['value'],
'label' => $item['label'],
'display_order' => $item['displayOrder'],
];
}
return $options;
}
/**
* @return array<CrmFieldOption>
*/
public function fetchDispositionFieldOptions(): array
{
$options = [];
$dispositions = $this->fetchCallDispositions();
foreach ($dispositions as $disposition) {
if ($disposition['deleted'] !== false) {
continue;
}
$option['value'] = $disposition['id'];
$option['id'] = $disposition['id'];
$option['label'] = $disposition['label'];
$options[] = $option;
}
return $options;
}
/**
* @return array<CrmFieldOption>
*/
public function fetchOpportunityFieldOptions(Field $field): array
{
if ($field->isStageField()) {
return $this->fetchOpportunityPipelineStages();
}
if ($field->isPipelineField()) {
return $this->fetchOpportunityPipelines();
}
return $this->fetchPropertyOptions('deals', $field->getCrmProviderId());
}
/**
* @throws BadRequest
* @throws HubspotException
*/
public function makeRequest(string $endpoint, $method = 'GET', $payload = [], ?string $queryString = null)
{
$endpoint = self::BASE_URL . $endpoint;
if ($method === 'GET') {
$response = $this->getInstance()->getClient()?->request(
method: $method,
endpoint: $endpoint,
query_string: $queryString
);
} else {
$response = $this->getInstance()->getClient()->request($method, $endpoint, [
'json' => ($payload),
]);
}
$max = $response->getHeaderLine('X-HubSpot-RateLimit-Max'); // "110"
$remaining = $response->getHeaderLine('X-HubSpot-RateLimit-Remaining'); // "109"
$interval = $response->getHeaderLine('X-HubSpot-RateLimit-Interval-Milliseconds'); // "10000"
$body = json_decode((string) $response->getBody(), true);
\Illuminate\Support\Facades\Log::channel('custom_channel')->info('$max ' . PHP_EOL . print_r($max, true));
\Illuminate\Support\Facades\Log::channel('custom_channel')->info('$remaining ' . PHP_EOL . print_r($remaining, true));
\Illuminate\Support\Facades\Log::channel('custom_channel')->info('$interval ' . PHP_EOL . print_r($interval, true));
\Illuminate\Support\Facades\Log::channel('custom_channel')->info('$body ' . PHP_EOL . print_r($body, true));
return $response;
}
/**
* @throws BadRequest
* @throws HubspotException
*/
public function createMeeting(array $payload): Response
{
$endpoint = '/crm/v3/objects/meetings';
return $this->makeRequest($endpoint, 'POST', $payload);
}
/**
* @throws BadRequest
* @throws HubspotException
*/
public function updateMeeting(string $meetingId, array $payload): Response
{
$endpoint = '/crm/v3/objects/meetings/' . $meetingId;
return $this->makeRequest($endpoint, 'PATCH', $payload);
}
/**
* @throws \Exception
*/
public function createNote(
string $body,
string $ownerId,
int $timestamp,
string $objectId,
NoteObject $noteObject
): ?string {
try {
$noteInput = new SimplePublicObjectInput([
'properties' => [
'hs_note_body' => $body,
'hubspot_owner_id' => $ownerId,
'hs_timestamp' => $timestamp,
],
]);
// Create note
$note = $this->getNewInstance()->crm()->objects()->basicApi()->create('note', $noteInput);
$this->getNewInstance()->crm()->objects()->associationsApi()->create(
'note',
$note->getId(),
$this->getNoteObject($noteObject),
$objectId,
$this->getNoteAssociationType($noteObject),
);
return $note->getId();
} catch (\Exception $e) {
$this->log->error('[Hubspot] Failed to create note', [
'objectId' => $objectId,
'noteObject' => $noteObject->getObjectType(),
'reason' => $e->getMessage(),
]);
\Sentry::captureException($e);
}
return null;
}
public function updateEngagement(string $objectId, array $engagement, array $metadata): void
{
$this->getInstance()->engagements()->update($objectId, $engagement, $metadata);
}
public function getEngagementData(string $engagementId): array
{
$engagement = $this->getInstance()->engagements()->get($engagementId);
return $engagement->toArray();
}
public function createEngagement(array $engagement, array $associations, array $metadata): Response
{
return $this->getInstance()
->engagements()
->create($engagement, $associations, $metadata);
}
public function isUnauthorizedException(\Exception $e): bool
{
// Check for specific HubSpot API exception types first
if ($e instanceof BadRequest) {
// BadRequest can contain 401 status codes
return $e->getCode() === 401;
}
// Check for HTTP client exceptions with status codes
if ($e instanceof \GuzzleHttp\Exception\RequestException && $e->hasResponse()) {
$response = $e->getResponse();
if ($response !== null) {
return $response->getStatusCode() === 401;
}
}
// Check for Guzzle HTTP exceptions
if ($e instanceof \GuzzleHttp\Exception\ClientException) {
return $e->getCode() === 401;
}
// Fallback to string matching as last resort, but be more specific
$message = strtolower($e->getMessage());
return str_contains($message, '401 unauthorized') ||
str_contains($message, 'http 401') ||
str_contains($message, 'status code 401') ||
(preg_match('/\b401\b/', $message) && str_contains($message, 'unauthorized'));
}
/**
* Validates and refreshes the access token if needed before API requests.
* This ensures long-running processes don't fail due to token expiration.
*
* @throws SocialAccountTokenInvalidException
*/
public function ensureValidToken(): void
{
if ($this->oauthAccount === null) {
return;
}
$newToken = $this->tokenManager->ensureValidToken($this->oauthAccount);
if ($newToken !== null) {
$this->accessToken = $newToken;
}
}
public function getConfig()
{
return $this->config;
}
// returns only active (archived=false)
public function getOwners(): array
{
return $this->getNewInstance()->crm()->owners()->getAll();
}
/**
* @param bool $archived
*
* @return array<Owner>|[]
*/
public function getOwnersArchived(bool $archived = true): array
{
$endpoint = '/crm/v3/owners';
$queryParams = [
'archived' => $archived ? 'true' : 'false',
];
$queryString = http_build_query($queryParams);
$owners = [];
try {
$response = $this->makeRequest(endpoint: $endpoint, queryString: $queryString);
$responseData = $response?->toArray();
foreach ($responseData['results'] as $result) {
try {
$owners[] = Owner::create($result);
} catch (Throwable $e) {
$this->log->error('[HubSpot] Failed to process owner data', [
'result' => $result,
'error' => $e->getMessage(),
]);
continue;
}
}
} catch (Throwable $e) {
$this->log->error('HubSpot] Failed to fetch owners', [
'archived' => $archived,
'error' => $e->getMessage(),
]);
return [];
}
return $owners;
}
public function getMeeting(string $engagementId): ObjectWithAssociations
{
return $this->getNewInstance()->crm()->objects()->basicApi()
->getById('meeting', $engagementId, null, 'contact,company,deal');
}
public function deleteEngagement(string $engagementId): void
{
$this->getInstance()->engagements()->delete((int) $engagementId);
}
public function getAssociationsData(array $ids, string $fromObject, string $toObject): array
{
$associationData = [];
$idChunks = array_chunk($ids, self::ASSOCIATIONS_BATCH_SIZE_LIMIT);
foreach ($idChunks as $idChunk) {
try {
$batchInput = new \HubSpot\Client\Crm\Associations\Model\BatchInputPublicObjectId();
$batchInput->setInputs(array_map(function ($id) {
$publicObjectId = new \HubSpot\Client\Crm\Associations\Model\PublicObjectId();
$publicObjectId->setId($id);
return $publicObjectId;
}, $idChunk));
$associatedObjectsData = $this
->getNewInstance()
->crm()
->associations()
->batchApi()
->read($fromObject, $toObject, $batchInput);
if ($associatedObjectsData instanceof \HubSpot\Client\Crm\Associations\Model\BatchResponsePublicAssociationMulti) {
foreach ($associatedObjectsData->getResults() as $association) {
$from = $association->getFrom()->getId();
$toAssociations = $association->getTo();
if (! empty($toAssociations)) {
$associationData[$from] = array_map(function ($item) {
return $item->getId();
}, $toAssociations);
}
}
}
} catch (\Exception $e) {
$this->log->error('[Hubspot] Failed to fetch associations', [
'from_object' => $fromObject,
'to_object' => $toObject,
'reason' => $e->getMessage(),
]);
}
}
return $associationData;
}
/**
* @throws \Exception
*/
private function getNoteAssociationType(NoteObject $noteObject): string
{
return match($noteObject) {
NoteObject::Opportunity => 'note_to_deal',
NoteObject::Lead, NoteObject::Contact => 'note_to_contact', // or 'note_to_lead' if your portal supports it
NoteObject::Account => 'note_to_company',
NoteObject::Call, NoteObject::Event => throw new \Exception('Not supported'),
};
}
/**
* @throws \Exception
*/
private function getNoteObject(NoteObject $noteObject): string
{
return match($noteObject) {
NoteObject::Opportunity => 'deal',
NoteObject::Lead, NoteObject::Contact => 'contact',
NoteObject::Account => 'company',
NoteObject::Call, NoteObject::Event => throw new \Exception('Not supported'),
};
}
public function addAssociations(string $objectType, string $associationType, array $payload): Response
{
$endpoint = "/crm/v4/associations/$objectType/$associationType/batch/create";
return $this->makeRequest($endpoint, 'POST', $payload);
}
public function removeAssociations(string $objectType, string $associationType, array $payload): Response
{
$endpoint = "/crm/v4/associations/$objectType/$associationType/batch/archive";
return $this->makeRequest($endpoint, 'POST', $payload);
}
}
Project
Project
New File or Directory…
Expand Selected
Collapse All
Options
Hide...
|
NULL
|
NULL
|
NULL
|
NULL
|
|
2741
|
112
|
42
|
2026-05-07T11:38:05.777441+00:00
|
/Users/lukas/.screenpipe/data/data/2026-05-07/1778 /Users/lukas/.screenpipe/data/data/2026-05-07/1778153885777_m2.jpg...
|
PhpStorm
|
faVsco.js – custom.log
|
True
|
NULL
|
monitor_2
|
NULL
|
NULL
|
NULL
|
NULL
|
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
Editor for custom.log
Sync Changes
Hide This Notification
Code changed:
Hide
2
60
Previous Highlighted Error
Next Highlighted Error
<?php
declare(strict_types=1);
namespace Jiminny\Services\Crm\Hubspot;
use HubSpot\Client\Crm\Deals\ApiException as DealApiException;
use HubSpot\Client\Crm\Contacts\ApiException as ContactApiException;
use HubSpot\Client\Crm\Companies\ApiException as CompanyApiException;
use HubSpot\Client\Crm\Contacts\Model\SimplePublicObjectWithAssociations as ContactsWithAssociations;
use HubSpot\Client\Crm\Companies\Model\SimplePublicObjectWithAssociations as CompaniesWithAssociations;
use HubSpot\Client\Crm\Deals\Model\SimplePublicObjectWithAssociations as DealWithAssociations;
use HubSpot\Client\Crm\Objects\Model\SimplePublicObjectInput;
use HubSpot\Client\Crm\Objects\Model\SimplePublicObjectWithAssociations as ObjectWithAssociations;
use HubSpot\Client\Crm\Pipelines\Model\Error;
use HubSpot\Client\Crm\Pipelines\Model\PipelineStage;
use HubSpot\Client\Crm\Properties\Model\Property;
use HubSpot\Discovery\Discovery;
use Jiminny\Component\Utility\Service\ProviderRateLimiter;
use Jiminny\Exceptions\CrmException;
use Jiminny\Exceptions\RateLimitException;
use Jiminny\Exceptions\SocialAccountTokenInvalidException;
use Jiminny\Jobs\Crm\NoteObject;
use Jiminny\Models\Crm\Field;
use Jiminny\Services\Crm\BaseClient;
use Jiminny\Services\Crm\Hubspot\DTO\Response\Owner;
use Jiminny\Services\SocialAccountService;
use SevenShores\Hubspot\Exceptions\BadRequest;
use SevenShores\Hubspot\Exceptions\HubspotException;
use SevenShores\Hubspot\Factory;
use SevenShores\Hubspot\Http\Response;
use Jiminny\Services\Crm\Hubspot\Pagination\HubspotPaginationService;
use Throwable;
/**
* @phpstan-type CrmFieldOption array{id:string, label:string, value?:string}
*/
class Client extends BaseClient implements HubspotClientInterface
{
public const string MIN_API_VERSION = '2';
public const string BASE_URL = '[URL_WITH_CREDENTIALS] T
* @param callable(): T $apiCall
* @return T
*
* @throws RateLimitException
*/
private function executeRequest(callable $apiCall)
{
if (! $this->rateLimiter->canMakeRequest($this->config)) {
$retryAfter = $this->rateLimiter->requestAvailableIn($this->config);
$this->log->warning('[Hubspot] Rate limit exceeded, deferring request', [
'team_id' => $this->config->team_id,
'config_id' => $this->config->getId(),
'retry_after' => $retryAfter,
]);
throw new RateLimitException(
'Hubspot rate limit reached for configuration ' . $this->config->getId(),
$retryAfter,
);
}
$this->rateLimiter->incrementRequestCount($this->config);
try {
return $apiCall();
} catch (Throwable $e) {
if ($this->isHubspotRateLimit($e)) {
$retryAfter = $this->parseRetryAfter($e);
$this->log->warning('[Hubspot] Received 429 from API', [
'team_id' => $this->config->team_id,
'config_id' => $this->config->getId(),
'retry_after' => $retryAfter,
'reason' => $e->getMessage(),
]);
throw new RateLimitException('Hubspot returned 429', $retryAfter, $e);
}
throw $e;
}
}
private function isHubspotRateLimit(Throwable $e): bool
{
return method_exists($e, 'getCode') && (int) $e->getCode() === 429;
}
private function parseRetryAfter(Throwable $e): int
{
if (method_exists($e, 'getResponseHeaders')) {
$headers = $e->getResponseHeaders() ?: [];
$value = $headers['Retry-After'] ?? $headers['retry-after'] ?? null;
if (is_array($value)) {
$value = $value[0] ?? null;
}
if (is_numeric($value)) {
return (int) $value;
}
}
return 10;
}
public function getMinimumApiVersion(): string
{
return self::MIN_API_VERSION;
}
public function getInstance(): Factory
{
return new Factory([
'key' => $this->accessToken,
'oauth2' => true,
'base_url' => $this->baseUrl,
]);
}
public function getNewInstance(): Discovery
{
return \HubSpot\Factory::createWithAccessToken($this->accessToken);
}
/**
* Secondly and daily limits for Hubspot API
*
* Product Tier: Free & Starter | Professional & Enterprise | API add-on (any tier)
* Burst: 100/10 seconds | 150/10 seconds | 200/10 seconds
* Daily: 250,000 | 500,000 | 1,000,000
*
* Official documentation states: The search endpoints are rate limited to five requests per second.
* Since with 5 RPS were still hitting secondly rate limits we lowered it to 4
*/
public function getPaginatedData(array $payload, string $type, int $offset = 0): array
{
$total = 0;
$lastId = null;
$rows = [];
foreach ($this->getPaginatedDataGenerator($payload, $type, $offset, $total, $lastId) as $row) {
$rows[] = $row;
}
return ['results' => $rows, 'total' => $total, 'last_record' => $lastId];
}
/**
* @throws HubspotException
* @throws SocialAccountTokenInvalidException
* @throws BadRequest
*/
public function getPaginatedDataGenerator(
array $payload,
string $type,
int $offset = 0,
int &$total = 0,
?string &$lastRecordId = null
): \Generator {
return $this->paginationService->getPaginatedDataGenerator(
$this,
$payload,
$type,
$offset,
$total,
$lastRecordId
);
}
/**
* @throws DealApiException
* @throws CrmException
*/
public function getOpportunityById(string $crmId, array $fields): array
{
try {
$deal = $this->executeRequest(fn () => $this->getNewInstance()->crm()->deals()->basicApi()->getById(
$crmId,
implode(',', $fields),
'companies,contacts'
));
} catch (DealApiException $e) {
$this->log->info('[Hubspot] Failed to fetch opportunity', [
'crm_id' => $crmId,
'reason' => $e->getMessage(),
]);
throw $e;
}
if (! $deal instanceof DealWithAssociations) {
throw new CrmException('Deal not found');
}
return [
'id' => $deal->getId(),
'properties' => $deal->getProperties(),
'associations' => $deal->getAssociations(),
];
}
/**
* Generic batch read method for HubSpot objects
*
* @param string $objectType The object type ('deals', 'companies', 'contacts')
* @param array<string> $crmIds Array of HubSpot object IDs (max 100)
* @param array<string> $fields Array of property names to fetch
*
* @return array<string, array> Array keyed by CRM ID with object data
*/
private function batchReadObjects(string $objectType, array $crmIds, array $fields): array
{
if (empty($crmIds)) {
return [];
}
$this->validateBatchSize($objectType, $crmIds);
$this->ensureValidToken();
try {
$batchConfig = $this->createBatchConfiguration($objectType);
$batchReadRequest = $this->prepareBatchRequest($batchConfig, $crmIds, $fields);
$response = $batchConfig['api']->read($batchReadRequest);
$this->validateApiResponse($response, $objectType);
$results = $this->processApiResults($response);
$this->logBatchResults($objectType, $crmIds, $results);
return $results;
} catch (\Throwable $e) {
$this->handleBatchError($e, $objectType, $crmIds);
}
}
private function validateBatchSize(string $objectType, array $crmIds): void
{
if (count($crmIds) > 100) {
throw new \InvalidArgumentException("Batch size cannot exceed 100 {$objectType}");
}
}
private function createBatchConfiguration(string $objectType): array
{
$configurations = [
'deals' => [
'batchReadRequest' => new \HubSpot\Client\Crm\Deals\Model\BatchReadInputSimplePublicObjectId(),
'inputClass' => \HubSpot\Client\Crm\Deals\Model\SimplePublicObjectId::class,
'api' => $this->getNewInstance()->crm()->deals()->batchApi(),
],
'companies' => [
'batchReadRequest' => new \HubSpot\Client\Crm\Companies\Model\BatchReadInputSimplePublicObjectId(),
'inputClass' => \HubSpot\Client\Crm\Companies\Model\SimplePublicObjectId::class,
'api' => $this->getNewInstance()->crm()->companies()->batchApi(),
],
'contacts' => [
'batchReadRequest' => new \HubSpot\Client\Crm\Contacts\Model\BatchReadInputSimplePublicObjectId(),
'inputClass' => \HubSpot\Client\Crm\Contacts\Model\SimplePublicObjectId::class,
'api' => $this->getNewInstance()->crm()->contacts()->batchApi(),
],
];
if (! isset($configurations[$objectType])) {
throw new \InvalidArgumentException("Unsupported object type: {$objectType}");
}
return $configurations[$objectType];
}
private function prepareBatchRequest(array $batchConfig, array $crmIds, array $fields): object
{
$batchReadRequest = $batchConfig['batchReadRequest'];
$inputClass = $batchConfig['inputClass'];
$inputs = array_map(function ($crmId) use ($inputClass) {
$input = new $inputClass();
$input->setId($crmId);
return $input;
}, $crmIds);
$batchReadRequest->setInputs($inputs);
$batchReadRequest->setProperties($fields);
return $batchReadRequest;
}
private function validateApiResponse($response, string $objectType): void
{
if (! $response) {
throw new CrmException("HubSpot API returned null response for {$objectType} batch read");
}
}
private function processApiResults($response): array
{
$results = [];
$responseResults = $response->getResults();
if ($responseResults) {
foreach ($responseResults as $object) {
if ($object && $object->getId()) {
$results[$object->getId()] = [
'id' => $object->getId(),
'properties' => $object->getProperties() ?: [],
];
}
}
}
return $results;
}
private function logBatchResults(string $objectType, array $crmIds, array $results): void
{
$this->log->info("[HubSpot] Batch fetched {$objectType}", [
'requested_count' => count($crmIds),
'returned_count' => count($results),
'crm_ids' => $crmIds,
]);
}
private function handleBatchError(\Throwable $e, string $objectType, array $crmIds): void
{
$errorMessage = $e->getMessage() ?: 'Unknown error';
$errorTrace = $e->getTraceAsString() ?: 'No trace available';
$this->log->error("[HubSpot] Failed to batch fetch {$objectType}", [
'crm_ids' => $crmIds,
'error' => $errorMessage,
'trace' => $errorTrace,
]);
throw new CrmException("Failed to batch fetch {$objectType}: " . $errorMessage);
}
/**
* Batch read multiple opportunities by their CRM IDs
*
* @param array<string> $crmIds Array of HubSpot deal IDs (max 100)
* @param array<string> $fields Array of property names to fetch
*
* @return array<string, array> Array keyed by CRM ID with opportunity data
*/
public function getOpportunitiesByIds(array $crmIds, array $fields): array
{
return $this->batchReadObjects('deals', $crmIds, $fields);
}
/**
* Batch read multiple companies by their CRM IDs
*
* @param array<string> $crmIds Array of HubSpot company IDs (max 100)
* @param array<string> $fields Array of property names to fetch
*
* @return array<string, array> Array keyed by CRM ID with company data
*/
public function getCompaniesByIds(array $crmIds, array $fields): array
{
return $this->batchReadObjects('companies', $crmIds, $fields);
}
/**
* Batch read multiple contacts by their CRM IDs
*
* @param array<string> $crmIds Array of HubSpot contact IDs (max 100)
* @param array<string> $fields Array of property names to fetch
*
* @return array<string, array> Array keyed by CRM ID with contact data
*/
public function getContactsByIds(array $crmIds, array $fields): array
{
return $this->batchReadObjects('contacts', $crmIds, $fields);
}
/**
* @throws CompanyApiException
* @throws CrmException
*/
public function getAccountById(string $crmId, array $fields): array
{
try {
$company = $this->getNewInstance()->crm()->companies()->basicApi()->getById(
$crmId,
implode(',', $fields),
);
} catch (CompanyApiException $e) {
$this->log->info('[Hubspot] Failed to fetch account', [
'crm_id' => $crmId,
'reason' => $e->getMessage(),
]);
throw $e;
}
if (! $company instanceof CompaniesWithAssociations) {
throw new CrmException('Account not found');
}
return [
'id' => $company->getId(),
'properties' => $company->getProperties(),
];
}
/**
* @throws ContactApiException
* @throws CrmException
*/
public function getContactById(string $crmId, array $fields): array
{
try {
$contact = $this->getNewInstance()->crm()->contacts()->basicApi()->getById(
$crmId,
implode(',', $fields)
);
} catch (ContactApiException $e) {
$this->log->info('[Hubspot] Failed to fetch contact', [
'crm_id' => $crmId,
'reason' => $e->getMessage(),
]);
throw $e;
}
if (! $contact instanceof ContactsWithAssociations) {
throw new CrmException('Contact not found');
}
return [
'id' => $contact->getId(),
'properties' => $contact->getProperties(),
];
}
/**
* This is email search request that Hubspot offers as GET (more generous quota)
*/
public function getContactByEmail(string $email, array $fields = []): array
{
try {
$contact = $this->getNewInstance()->crm()->contacts()->basicApi()->getById(
$email,
implode(',', $fields),
null,
false,
'email'
);
return [
'id' => $contact->getId(),
'properties' => $contact->getProperties(),
];
} catch (ContactApiException $e) {
$this->log->info('[Hubspot] Failed to fetch contact', [
'email' => $email,
'reason' => $e->getMessage(),
]);
return [];
}
}
/**
* @throws CrmException
*/
public function fetchProperty(string $objectType, string $propertyId): Property
{
$result = $this->getNewInstance()->crm()->properties()->coreApi()->getByName($objectType, $propertyId);
if (! $result instanceof Property) {
$this->log->error('[Hubspot] Failed to fetch property', [
'object_type' => $objectType,
'property_id' => $propertyId,
'reason' => $result->getMessage(),
]);
throw new CrmException('Failed to fetch property');
}
return $result;
}
/**
* @return array<CrmFieldOption>
*/
public function fetchPropertyOptions(string $objectType, string $propertyId): array
{
/** @var array<CrmFieldOption> */
return $this->fetchProperty($objectType, $propertyId)->getOptions();
}
/**
* @return array<array{id:string, label:string, deleted:bool}>
*/
public function fetchCallDispositions(): array
{
/** @var Response $response */
$response = $this->getInstance()->engagements()->getCallDispositions();
/**
* @var array<array{
* id:string,
* label:string,
* deleted: bool
* }>
*/
return $response->toArray();
}
/**
* @return array<CrmFieldOption>
*/
public function fetchOpportunityPipelineStages(): array
{
$stages = [];
$apiResponse = $this->getNewInstance()->crm()->pipelines()->pipelinesApi()->getAll('deals');
if ($apiResponse instanceof Error) {
$this->log->error('[Hubspot] Failed to fetch opportunity pipelines', [
'reason' => $apiResponse->getMessage(),
]);
return [];
}
foreach ($apiResponse->getResults() as $pipeline) {
$pipelineStages = array_map(
static function (PipelineStage $stage) {
return [
'id' => $stage->getId(),
'label' => $stage->getLabel(),
];
},
$pipeline->getStages()
);
$stages = array_merge($stages, $pipelineStages);
}
return $stages;
}
public function fetchOpportunityPipelines(): array
{
$pipelines = [];
try {
$apiResponse = $this->makeRequest('/crm/v3/pipelines/deals');
} catch (\Exception $e) {
$this->log->info('[Hubspot] Failed to fetch opportunity pipelines', [
'reason' => $e->getMessage(),
]);
return [];
}
$response = $apiResponse->toArray();
foreach ($response['results'] as $pipeline) {
$pipelines[] = [
'id' => $pipeline['id'],
'label' => $pipeline['label'],
];
}
return $pipelines;
}
/**
* @return array<CrmFieldOption>
*/
public function fetchMeetingOutcomeFieldOptions(Field $field): array
{
return $field->getCrmProviderId() === 'meetingOutcome'
? $this->fetchMeetingOutcomeTypes()
: $this->fetchCallActivityTypes();
}
public function fetchMeetingOutcomeTypes(): array
{
return $this->extractMeetingTypeOptions(
'[URL_WITH_CREDENTIALS] Response $response */
$response = $this->getInstance()
->getClient()
->request('GET', $endpoint);
/**
* @var array<array{
* value: string,
* label: string,
* displayOrder: int
* }> $optionData
*/
$optionData = $response->toArray()['options'] ?? [];
$options = [];
foreach ($optionData as $item) {
$options[] = [
'id' => $item['value'],
'value' => $item['value'],
'label' => $item['label'],
'display_order' => $item['displayOrder'],
];
}
return $options;
}
/**
* @return array<CrmFieldOption>
*/
public function fetchDispositionFieldOptions(): array
{
$options = [];
$dispositions = $this->fetchCallDispositions();
foreach ($dispositions as $disposition) {
if ($disposition['deleted'] !== false) {
continue;
}
$option['value'] = $disposition['id'];
$option['id'] = $disposition['id'];
$option['label'] = $disposition['label'];
$options[] = $option;
}
return $options;
}
/**
* @return array<CrmFieldOption>
*/
public function fetchOpportunityFieldOptions(Field $field): array
{
if ($field->isStageField()) {
return $this->fetchOpportunityPipelineStages();
}
if ($field->isPipelineField()) {
return $this->fetchOpportunityPipelines();
}
return $this->fetchPropertyOptions('deals', $field->getCrmProviderId());
}
/**
* @throws BadRequest
* @throws HubspotException
*/
public function makeRequest(string $endpoint, $method = 'GET', $payload = [], ?string $queryString = null)
{
$endpoint = self::BASE_URL . $endpoint;
if ($method === 'GET') {
$response = $this->getInstance()->getClient()?->request(
method: $method,
endpoint: $endpoint,
query_string: $queryString
);
} else {
$response = $this->getInstance()->getClient()->request($method, $endpoint, [
'json' => ($payload),
]);
}
$max = $response->getHeaderLine('X-HubSpot-RateLimit-Max'); // "110"
$remaining = $response->getHeaderLine('X-HubSpot-RateLimit-Remaining'); // "109"
$interval = $response->getHeaderLine('X-HubSpot-RateLimit-Interval-Milliseconds'); // "10000"
$body = json_decode((string) $response->getBody(), true);
\Illuminate\Support\Facades\Log::channel('custom_channel')->info('$max ' . PHP_EOL . print_r($max, true));
\Illuminate\Support\Facades\Log::channel('custom_channel')->info('$remaining ' . PHP_EOL . print_r($remaining, true));
\Illuminate\Support\Facades\Log::channel('custom_channel')->info('$interval ' . PHP_EOL . print_r($interval, true));
\Illuminate\Support\Facades\Log::channel('custom_channel')->info('$body ' . PHP_EOL . print_r($body, true));
return $response;
}
/**
* @throws BadRequest
* @throws HubspotException
*/
public function createMeeting(array $payload): Response
{
$endpoint = '/crm/v3/objects/meetings';
return $this->makeRequest($endpoint, 'POST', $payload);
}
/**
* @throws BadRequest
* @throws HubspotException
*/
public function updateMeeting(string $meetingId, array $payload): Response
{
$endpoint = '/crm/v3/objects/meetings/' . $meetingId;
return $this->makeRequest($endpoint, 'PATCH', $payload);
}
/**
* @throws \Exception
*/
public function createNote(
string $body,
string $ownerId,
int $timestamp,
string $objectId,
NoteObject $noteObject
): ?string {
try {
$noteInput = new SimplePublicObjectInput([
'properties' => [
'hs_note_body' => $body,
'hubspot_owner_id' => $ownerId,
'hs_timestamp' => $timestamp,
],
]);
// Create note
$note = $this->getNewInstance()->crm()->objects()->basicApi()->create('note', $noteInput);
$this->getNewInstance()->crm()->objects()->associationsApi()->create(
'note',
$note->getId(),
$this->getNoteObject($noteObject),
$objectId,
$this->getNoteAssociationType($noteObject),
);
return $note->getId();
} catch (\Exception $e) {
$this->log->error('[Hubspot] Failed to create note', [
'objectId' => $objectId,
'noteObject' => $noteObject->getObjectType(),
'reason' => $e->getMessage(),
]);
\Sentry::captureException($e);
}
return null;
}
public function updateEngagement(string $objectId, array $engagement, array $metadata): void
{
$this->getInstance()->engagements()->update($objectId, $engagement, $metadata);
}
public function getEngagementData(string $engagementId): array
{
$engagement = $this->getInstance()->engagements()->get($engagementId);
return $engagement->toArray();
}
public function createEngagement(array $engagement, array $associations, array $metadata): Response
{
return $this->getInstance()
->engagements()
->create($engagement, $associations, $metadata);
}
public function isUnauthorizedException(\Exception $e): bool
{
// Check for specific HubSpot API exception types first
if ($e instanceof BadRequest) {
// BadRequest can contain 401 status codes
return $e->getCode() === 401;
}
// Check for HTTP client exceptions with status codes
if ($e instanceof \GuzzleHttp\Exception\RequestException && $e->hasResponse()) {
$response = $e->getResponse();
if ($response !== null) {
return $response->getStatusCode() === 401;
}
}
// Check for Guzzle HTTP exceptions
if ($e instanceof \GuzzleHttp\Exception\ClientException) {
return $e->getCode() === 401;
}
// Fallback to string matching as last resort, but be more specific
$message = strtolower($e->getMessage());
return str_contains($message, '401 unauthorized') ||
str_contains($message, 'http 401') ||
str_contains($message, 'status code 401') ||
(preg_match('/\b401\b/', $message) && str_contains($message, 'unauthorized'));
}
/**
* Validates and refreshes the access token if needed before API requests.
* This ensures long-running processes don't fail due to token expiration.
*
* @throws SocialAccountTokenInvalidException
*/
public function ensureValidToken(): void
{
if ($this->oauthAccount === null) {
return;
}
$newToken = $this->tokenManager->ensureValidToken($this->oauthAccount);
if ($newToken !== null) {
$this->accessToken = $newToken;
}
}
public function getConfig()
{
return $this->config;
}
// returns only active (archived=false)
public function getOwners(): array
{
return $this->getNewInstance()->crm()->owners()->getAll();
}
/**
* @param bool $archived
*
* @return array<Owner>|[]
*/
public function getOwnersArchived(bool $archived = true): array
{
$endpoint = '/crm/v3/owners';
$queryParams = [
'archived' => $archived ? 'true' : 'false',
];
$queryString = http_build_query($queryParams);
$owners = [];
try {
$response = $this->makeRequest(endpoint: $endpoint, queryString: $queryString);
$responseData = $response?->toArray();
foreach ($responseData['results'] as $result) {
try {
$owners[] = Owner::create($result);
} catch (Throwable $e) {
$this->log->error('[HubSpot] Failed to process owner data', [
'result' => $result,
'error' => $e->getMessage(),
]);
continue;
}
}
} catch (Throwable $e) {
$this->log->error('HubSpot] Failed to fetch owners', [
'archived' => $archived,
'error' => $e->getMessage(),
]);
return [];
}
return $owners;
}
public function getMeeting(string $engagementId): ObjectWithAssociations
{
return $this->getNewInstance()->crm()->objects()->basicApi()
->getById('meeting', $engagementId, null, 'contact,company,deal');
}
public function deleteEngagement(string $engagementId): void
{
$this->getInstance()->engagements()->delete((int) $engagementId);
}
public function getAssociationsData(array $ids, string $fromObject, string $toObject): array
{
$associationData = [];
$idChunks = array_chunk($ids, self::ASSOCIATIONS_BATCH_SIZE_LIMIT);
foreach ($idChunks as $idChunk) {
try {
$batchInput = new \HubSpot\Client\Crm\Associations\Model\BatchInputPublicObjectId();
$batchInput->setInputs(array_map(function ($id) {
$publicObjectId = new \HubSpot\Client\Crm\Associations\Model\PublicObjectId();
$publicObjectId->setId($id);
return $publicObjectId;
}, $idChunk));
$associatedObjectsData = $this
->getNewInstance()
->crm()
->associations()
->batchApi()
->read($fromObject, $toObject, $batchInput);
if ($associatedObjectsData instanceof \HubSpot\Client\Crm\Associations\Model\BatchResponsePublicAssociationMulti) {
foreach ($associatedObjectsData->getResults() as $association) {
$from = $association->getFrom()->getId();
$toAssociations = $association->getTo();
if (! empty($toAssociations)) {
$associationData[$from] = array_map(function ($item) {
return $item->getId();
}, $toAssociations);
}
}
}
} catch (\Exception $e) {
$this->log->error('[Hubspot] Failed to fetch associations', [
'from_object' => $fromObject,
'to_object' => $toObject,
'reason' => $e->getMessage(),
]);
}
}
return $associationData;
}
/**
* @throws \Exception
*/
private function getNoteAssociationType(NoteObject $noteObject): string
{
return match($noteObject) {
NoteObject::Opportunity => 'note_to_deal',
NoteObject::Lead, NoteObject::Contact => 'note_to_contact', // or 'note_to_lead' if your portal supports it
NoteObject::Account => 'note_to_company',
NoteObject::Call, NoteObject::Event => throw new \Exception('Not supported'),
};
}
/**
* @throws \Exception
*/
private function getNoteObject(NoteObject $noteObject): string
{
return match($noteObject) {
NoteObject::Opportunity => 'deal',
NoteObject::Lead, NoteObject::Contact => 'contact',
NoteObject::Account => 'company',
NoteObject::Call, NoteObject::Event => throw new \Exception('Not supported'),
};
}
public function addAssociations(string $objectType, string $associationType, array $payload): Response
{
$endpoint = "/crm/v4/associations/$objectType/$associationType/batch/create";
return $this->makeRequest($endpoint, 'POST', $payload);
}
public function removeAssociations(string $objectType, string $associationType, array $payload): Response
{
$endpoint = "/crm/v4/associations/$objectType/$associationType/batch/archive";
return $this->makeRequest($endpoint, 'POST', $payload);
}
}
Project
Project
New File or Directory…
Expand Selected
Collapse All
Options
Hide...
|
[{"role":"AXButton","text" [{"role":"AXButton","text":"Project: faVsco.js, menu","depth":5,"bounds":{"left":0.025930852,"top":0.019952115,"width":0.03856383,"height":0.025538707},"on_screen":true,"help_text":"~/jiminny/app","role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"master, menu","depth":5,"bounds":{"left":0.064494684,"top":0.019952115,"width":0.034242023,"height":0.025538707},"on_screen":true,"help_text":"Git Branch: master","role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Start Listening for PHP Debug Connections","depth":5,"bounds":{"left":0.8081782,"top":0.019952115,"width":0.011303191,"height":0.025538707},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"AskJiminnyReportActivityServiceTest","depth":6,"bounds":{"left":0.8234708,"top":0.019952115,"width":0.09208777,"height":0.025538707},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Run 'AskJiminnyReportActivityServiceTest'","depth":6,"bounds":{"left":0.9155585,"top":0.019952115,"width":0.011303191,"height":0.025538707},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Debug 'AskJiminnyReportActivityServiceTest'","depth":6,"bounds":{"left":0.9268617,"top":0.019952115,"width":0.011303191,"height":0.025538707},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"More Actions","depth":6,"bounds":{"left":0.9381649,"top":0.019952115,"width":0.011303191,"height":0.025538707},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"JetBrains AI","depth":5,"bounds":{"left":0.96609044,"top":0.019952115,"width":0.011303191,"height":0.025538707},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Search Everywhere","depth":5,"bounds":{"left":0.9773936,"top":0.019952115,"width":0.011303191,"height":0.025538707},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"IDE and Project Settings","depth":5,"bounds":{"left":0.9886968,"top":0.019952115,"width":0.011303186,"height":0.025538707},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Sync Changes","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.008643617,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Hide This Notification","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.008643617,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Code changed:","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.042220745,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Hide","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.008643617,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXTextArea","text":"Editor for custom.log","depth":4,"bounds":{"left":0.5475399,"top":0.0726257,"width":0.44082448,"height":0.9066241},"on_screen":true,"role_description":"text entry area","is_enabled":true,"is_focused":true,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Sync Changes","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.008643617,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Hide This Notification","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.008643617,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Code changed:","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.042220745,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Hide","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.008643617,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"2","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.007978723,"height":0.0},"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"60","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.010305851,"height":0.0},"on_screen":false,"role_description":"text"},{"role":"AXButton","text":"Previous Highlighted Error","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.00731383,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Next Highlighted Error","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.006981383,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXTextArea","text":"<?php\n\ndeclare(strict_types=1);\n\nnamespace Jiminny\\Services\\Crm\\Hubspot;\n\nuse HubSpot\\Client\\Crm\\Deals\\ApiException as DealApiException;\nuse HubSpot\\Client\\Crm\\Contacts\\ApiException as ContactApiException;\nuse HubSpot\\Client\\Crm\\Companies\\ApiException as CompanyApiException;\nuse HubSpot\\Client\\Crm\\Contacts\\Model\\SimplePublicObjectWithAssociations as ContactsWithAssociations;\nuse HubSpot\\Client\\Crm\\Companies\\Model\\SimplePublicObjectWithAssociations as CompaniesWithAssociations;\nuse HubSpot\\Client\\Crm\\Deals\\Model\\SimplePublicObjectWithAssociations as DealWithAssociations;\nuse HubSpot\\Client\\Crm\\Objects\\Model\\SimplePublicObjectInput;\nuse HubSpot\\Client\\Crm\\Objects\\Model\\SimplePublicObjectWithAssociations as ObjectWithAssociations;\nuse HubSpot\\Client\\Crm\\Pipelines\\Model\\Error;\nuse HubSpot\\Client\\Crm\\Pipelines\\Model\\PipelineStage;\nuse HubSpot\\Client\\Crm\\Properties\\Model\\Property;\nuse HubSpot\\Discovery\\Discovery;\nuse Jiminny\\Component\\Utility\\Service\\ProviderRateLimiter;\nuse Jiminny\\Exceptions\\CrmException;\nuse Jiminny\\Exceptions\\RateLimitException;\nuse Jiminny\\Exceptions\\SocialAccountTokenInvalidException;\nuse Jiminny\\Jobs\\Crm\\NoteObject;\nuse Jiminny\\Models\\Crm\\Field;\nuse Jiminny\\Services\\Crm\\BaseClient;\nuse Jiminny\\Services\\Crm\\Hubspot\\DTO\\Response\\Owner;\nuse Jiminny\\Services\\SocialAccountService;\nuse SevenShores\\Hubspot\\Exceptions\\BadRequest;\nuse SevenShores\\Hubspot\\Exceptions\\HubspotException;\nuse SevenShores\\Hubspot\\Factory;\nuse SevenShores\\Hubspot\\Http\\Response;\nuse Jiminny\\Services\\Crm\\Hubspot\\Pagination\\HubspotPaginationService;\nuse Throwable;\n\n/**\n * @phpstan-type CrmFieldOption array{id:string, label:string, value?:string}\n */\nclass Client extends BaseClient implements HubspotClientInterface\n{\n public const string MIN_API_VERSION = '2';\n\n public const string BASE_URL = 'https://api.hubapi.com';\n\n public const int ASSOCIATIONS_BATCH_SIZE_LIMIT = 1000;\n\n private HubspotPaginationService $paginationService;\n private HubspotTokenManager $tokenManager;\n private ProviderRateLimiter $rateLimiter;\n\n public function __construct(\n SocialAccountService $socialAccountService,\n HubspotPaginationService $paginationService,\n HubspotTokenManager $tokenManager,\n ProviderRateLimiter $rateLimiter,\n ) {\n parent::__construct($socialAccountService);\n $this->paginationService = $paginationService;\n $this->tokenManager = $tokenManager;\n $this->rateLimiter = $rateLimiter;\n\n $this->setBaseUrl(self::BASE_URL);\n $this->setVersion(self::MIN_API_VERSION);\n }\n\n /**\n * Single entry point for every HubSpot API call. Enforces the per-portal\n * rate limit configured in the rate_limits table (morphed to the current\n * Configuration) and reacts to a real 429 from HubSpot by translating it\n * into a RateLimitException carrying Retry-After.\n *\n * Wrap any outbound HubSpot call (SDK or raw HTTP) like:\n *\n * $this->executeRequest(fn () => $this->getNewInstance()->crm()->...);\n *\n * @template T\n * @param callable(): T $apiCall\n * @return T\n *\n * @throws RateLimitException\n */\n private function executeRequest(callable $apiCall)\n {\n if (! $this->rateLimiter->canMakeRequest($this->config)) {\n $retryAfter = $this->rateLimiter->requestAvailableIn($this->config);\n\n $this->log->warning('[Hubspot] Rate limit exceeded, deferring request', [\n 'team_id' => $this->config->team_id,\n 'config_id' => $this->config->getId(),\n 'retry_after' => $retryAfter,\n ]);\n\n throw new RateLimitException(\n 'Hubspot rate limit reached for configuration ' . $this->config->getId(),\n $retryAfter,\n );\n }\n\n $this->rateLimiter->incrementRequestCount($this->config);\n\n try {\n return $apiCall();\n } catch (Throwable $e) {\n if ($this->isHubspotRateLimit($e)) {\n $retryAfter = $this->parseRetryAfter($e);\n\n $this->log->warning('[Hubspot] Received 429 from API', [\n 'team_id' => $this->config->team_id,\n 'config_id' => $this->config->getId(),\n 'retry_after' => $retryAfter,\n 'reason' => $e->getMessage(),\n ]);\n\n throw new RateLimitException('Hubspot returned 429', $retryAfter, $e);\n }\n\n throw $e;\n }\n }\n\n private function isHubspotRateLimit(Throwable $e): bool\n {\n return method_exists($e, 'getCode') && (int) $e->getCode() === 429;\n }\n\n private function parseRetryAfter(Throwable $e): int\n {\n if (method_exists($e, 'getResponseHeaders')) {\n $headers = $e->getResponseHeaders() ?: [];\n $value = $headers['Retry-After'] ?? $headers['retry-after'] ?? null;\n if (is_array($value)) {\n $value = $value[0] ?? null;\n }\n if (is_numeric($value)) {\n return (int) $value;\n }\n }\n\n return 10;\n }\n\n public function getMinimumApiVersion(): string\n {\n return self::MIN_API_VERSION;\n }\n\n public function getInstance(): Factory\n {\n return new Factory([\n 'key' => $this->accessToken,\n 'oauth2' => true,\n 'base_url' => $this->baseUrl,\n ]);\n }\n\n public function getNewInstance(): Discovery\n {\n return \\HubSpot\\Factory::createWithAccessToken($this->accessToken);\n }\n\n /**\n * Secondly and daily limits for Hubspot API\n *\n * Product Tier: Free & Starter | Professional & Enterprise | API add-on (any tier)\n * Burst: 100/10 seconds | 150/10 seconds | 200/10 seconds\n * Daily: 250,000 | 500,000 | 1,000,000\n *\n * Official documentation states: The search endpoints are rate limited to five requests per second.\n * Since with 5 RPS were still hitting secondly rate limits we lowered it to 4\n */\n public function getPaginatedData(array $payload, string $type, int $offset = 0): array\n {\n $total = 0;\n $lastId = null;\n $rows = [];\n foreach ($this->getPaginatedDataGenerator($payload, $type, $offset, $total, $lastId) as $row) {\n $rows[] = $row;\n }\n\n return ['results' => $rows, 'total' => $total, 'last_record' => $lastId];\n }\n\n /**\n * @throws HubspotException\n * @throws SocialAccountTokenInvalidException\n * @throws BadRequest\n */\n public function getPaginatedDataGenerator(\n array $payload,\n string $type,\n int $offset = 0,\n int &$total = 0,\n ?string &$lastRecordId = null\n ): \\Generator {\n return $this->paginationService->getPaginatedDataGenerator(\n $this,\n $payload,\n $type,\n $offset,\n $total,\n $lastRecordId\n );\n }\n\n /**\n * @throws DealApiException\n * @throws CrmException\n */\n public function getOpportunityById(string $crmId, array $fields): array\n {\n try {\n $deal = $this->executeRequest(fn () => $this->getNewInstance()->crm()->deals()->basicApi()->getById(\n $crmId,\n implode(',', $fields),\n 'companies,contacts'\n ));\n } catch (DealApiException $e) {\n $this->log->info('[Hubspot] Failed to fetch opportunity', [\n 'crm_id' => $crmId,\n 'reason' => $e->getMessage(),\n ]);\n\n throw $e;\n }\n\n if (! $deal instanceof DealWithAssociations) {\n throw new CrmException('Deal not found');\n }\n\n return [\n 'id' => $deal->getId(),\n 'properties' => $deal->getProperties(),\n 'associations' => $deal->getAssociations(),\n ];\n }\n\n /**\n * Generic batch read method for HubSpot objects\n *\n * @param string $objectType The object type ('deals', 'companies', 'contacts')\n * @param array<string> $crmIds Array of HubSpot object IDs (max 100)\n * @param array<string> $fields Array of property names to fetch\n *\n * @return array<string, array> Array keyed by CRM ID with object data\n */\n private function batchReadObjects(string $objectType, array $crmIds, array $fields): array\n {\n if (empty($crmIds)) {\n return [];\n }\n\n $this->validateBatchSize($objectType, $crmIds);\n $this->ensureValidToken();\n\n try {\n $batchConfig = $this->createBatchConfiguration($objectType);\n $batchReadRequest = $this->prepareBatchRequest($batchConfig, $crmIds, $fields);\n $response = $batchConfig['api']->read($batchReadRequest);\n\n $this->validateApiResponse($response, $objectType);\n\n $results = $this->processApiResults($response);\n $this->logBatchResults($objectType, $crmIds, $results);\n\n return $results;\n } catch (\\Throwable $e) {\n $this->handleBatchError($e, $objectType, $crmIds);\n }\n }\n\n private function validateBatchSize(string $objectType, array $crmIds): void\n {\n if (count($crmIds) > 100) {\n throw new \\InvalidArgumentException(\"Batch size cannot exceed 100 {$objectType}\");\n }\n }\n\n private function createBatchConfiguration(string $objectType): array\n {\n $configurations = [\n 'deals' => [\n 'batchReadRequest' => new \\HubSpot\\Client\\Crm\\Deals\\Model\\BatchReadInputSimplePublicObjectId(),\n 'inputClass' => \\HubSpot\\Client\\Crm\\Deals\\Model\\SimplePublicObjectId::class,\n 'api' => $this->getNewInstance()->crm()->deals()->batchApi(),\n ],\n 'companies' => [\n 'batchReadRequest' => new \\HubSpot\\Client\\Crm\\Companies\\Model\\BatchReadInputSimplePublicObjectId(),\n 'inputClass' => \\HubSpot\\Client\\Crm\\Companies\\Model\\SimplePublicObjectId::class,\n 'api' => $this->getNewInstance()->crm()->companies()->batchApi(),\n ],\n 'contacts' => [\n 'batchReadRequest' => new \\HubSpot\\Client\\Crm\\Contacts\\Model\\BatchReadInputSimplePublicObjectId(),\n 'inputClass' => \\HubSpot\\Client\\Crm\\Contacts\\Model\\SimplePublicObjectId::class,\n 'api' => $this->getNewInstance()->crm()->contacts()->batchApi(),\n ],\n ];\n\n if (! isset($configurations[$objectType])) {\n throw new \\InvalidArgumentException(\"Unsupported object type: {$objectType}\");\n }\n\n return $configurations[$objectType];\n }\n\n private function prepareBatchRequest(array $batchConfig, array $crmIds, array $fields): object\n {\n $batchReadRequest = $batchConfig['batchReadRequest'];\n $inputClass = $batchConfig['inputClass'];\n\n $inputs = array_map(function ($crmId) use ($inputClass) {\n $input = new $inputClass();\n $input->setId($crmId);\n\n return $input;\n }, $crmIds);\n\n $batchReadRequest->setInputs($inputs);\n $batchReadRequest->setProperties($fields);\n\n return $batchReadRequest;\n }\n\n private function validateApiResponse($response, string $objectType): void\n {\n if (! $response) {\n throw new CrmException(\"HubSpot API returned null response for {$objectType} batch read\");\n }\n }\n\n private function processApiResults($response): array\n {\n $results = [];\n $responseResults = $response->getResults();\n\n if ($responseResults) {\n foreach ($responseResults as $object) {\n if ($object && $object->getId()) {\n $results[$object->getId()] = [\n 'id' => $object->getId(),\n 'properties' => $object->getProperties() ?: [],\n ];\n }\n }\n }\n\n return $results;\n }\n\n private function logBatchResults(string $objectType, array $crmIds, array $results): void\n {\n $this->log->info(\"[HubSpot] Batch fetched {$objectType}\", [\n 'requested_count' => count($crmIds),\n 'returned_count' => count($results),\n 'crm_ids' => $crmIds,\n ]);\n }\n\n private function handleBatchError(\\Throwable $e, string $objectType, array $crmIds): void\n {\n $errorMessage = $e->getMessage() ?: 'Unknown error';\n $errorTrace = $e->getTraceAsString() ?: 'No trace available';\n\n $this->log->error(\"[HubSpot] Failed to batch fetch {$objectType}\", [\n 'crm_ids' => $crmIds,\n 'error' => $errorMessage,\n 'trace' => $errorTrace,\n ]);\n\n throw new CrmException(\"Failed to batch fetch {$objectType}: \" . $errorMessage);\n }\n\n /**\n * Batch read multiple opportunities by their CRM IDs\n *\n * @param array<string> $crmIds Array of HubSpot deal IDs (max 100)\n * @param array<string> $fields Array of property names to fetch\n *\n * @return array<string, array> Array keyed by CRM ID with opportunity data\n */\n public function getOpportunitiesByIds(array $crmIds, array $fields): array\n {\n return $this->batchReadObjects('deals', $crmIds, $fields);\n }\n\n /**\n * Batch read multiple companies by their CRM IDs\n *\n * @param array<string> $crmIds Array of HubSpot company IDs (max 100)\n * @param array<string> $fields Array of property names to fetch\n *\n * @return array<string, array> Array keyed by CRM ID with company data\n */\n public function getCompaniesByIds(array $crmIds, array $fields): array\n {\n return $this->batchReadObjects('companies', $crmIds, $fields);\n }\n\n /**\n * Batch read multiple contacts by their CRM IDs\n *\n * @param array<string> $crmIds Array of HubSpot contact IDs (max 100)\n * @param array<string> $fields Array of property names to fetch\n *\n * @return array<string, array> Array keyed by CRM ID with contact data\n */\n public function getContactsByIds(array $crmIds, array $fields): array\n {\n return $this->batchReadObjects('contacts', $crmIds, $fields);\n }\n\n /**\n * @throws CompanyApiException\n * @throws CrmException\n */\n public function getAccountById(string $crmId, array $fields): array\n {\n try {\n $company = $this->getNewInstance()->crm()->companies()->basicApi()->getById(\n $crmId,\n implode(',', $fields),\n );\n } catch (CompanyApiException $e) {\n $this->log->info('[Hubspot] Failed to fetch account', [\n 'crm_id' => $crmId,\n 'reason' => $e->getMessage(),\n ]);\n\n throw $e;\n }\n\n if (! $company instanceof CompaniesWithAssociations) {\n throw new CrmException('Account not found');\n }\n\n return [\n 'id' => $company->getId(),\n 'properties' => $company->getProperties(),\n ];\n }\n\n /**\n * @throws ContactApiException\n * @throws CrmException\n */\n public function getContactById(string $crmId, array $fields): array\n {\n try {\n $contact = $this->getNewInstance()->crm()->contacts()->basicApi()->getById(\n $crmId,\n implode(',', $fields)\n );\n } catch (ContactApiException $e) {\n $this->log->info('[Hubspot] Failed to fetch contact', [\n 'crm_id' => $crmId,\n 'reason' => $e->getMessage(),\n ]);\n\n throw $e;\n }\n\n if (! $contact instanceof ContactsWithAssociations) {\n throw new CrmException('Contact not found');\n }\n\n return [\n 'id' => $contact->getId(),\n 'properties' => $contact->getProperties(),\n ];\n }\n\n /**\n * This is email search request that Hubspot offers as GET (more generous quota)\n */\n public function getContactByEmail(string $email, array $fields = []): array\n {\n try {\n $contact = $this->getNewInstance()->crm()->contacts()->basicApi()->getById(\n $email,\n implode(',', $fields),\n null,\n false,\n 'email'\n );\n\n return [\n 'id' => $contact->getId(),\n 'properties' => $contact->getProperties(),\n ];\n } catch (ContactApiException $e) {\n $this->log->info('[Hubspot] Failed to fetch contact', [\n 'email' => $email,\n 'reason' => $e->getMessage(),\n ]);\n\n return [];\n }\n }\n\n /**\n * @throws CrmException\n */\n public function fetchProperty(string $objectType, string $propertyId): Property\n {\n $result = $this->getNewInstance()->crm()->properties()->coreApi()->getByName($objectType, $propertyId);\n\n if (! $result instanceof Property) {\n $this->log->error('[Hubspot] Failed to fetch property', [\n 'object_type' => $objectType,\n 'property_id' => $propertyId,\n 'reason' => $result->getMessage(),\n ]);\n\n throw new CrmException('Failed to fetch property');\n }\n\n return $result;\n }\n\n /**\n * @return array<CrmFieldOption>\n */\n public function fetchPropertyOptions(string $objectType, string $propertyId): array\n {\n /** @var array<CrmFieldOption> */\n return $this->fetchProperty($objectType, $propertyId)->getOptions();\n }\n\n /**\n * @return array<array{id:string, label:string, deleted:bool}>\n */\n public function fetchCallDispositions(): array\n {\n /** @var Response $response */\n $response = $this->getInstance()->engagements()->getCallDispositions();\n\n /**\n * @var array<array{\n * id:string,\n * label:string,\n * deleted: bool\n * }>\n */\n return $response->toArray();\n }\n\n /**\n * @return array<CrmFieldOption>\n */\n public function fetchOpportunityPipelineStages(): array\n {\n $stages = [];\n $apiResponse = $this->getNewInstance()->crm()->pipelines()->pipelinesApi()->getAll('deals');\n\n if ($apiResponse instanceof Error) {\n $this->log->error('[Hubspot] Failed to fetch opportunity pipelines', [\n 'reason' => $apiResponse->getMessage(),\n ]);\n\n return [];\n }\n\n foreach ($apiResponse->getResults() as $pipeline) {\n $pipelineStages = array_map(\n static function (PipelineStage $stage) {\n return [\n 'id' => $stage->getId(),\n 'label' => $stage->getLabel(),\n ];\n },\n $pipeline->getStages()\n );\n\n $stages = array_merge($stages, $pipelineStages);\n }\n\n return $stages;\n }\n\n public function fetchOpportunityPipelines(): array\n {\n $pipelines = [];\n\n try {\n $apiResponse = $this->makeRequest('/crm/v3/pipelines/deals');\n } catch (\\Exception $e) {\n $this->log->info('[Hubspot] Failed to fetch opportunity pipelines', [\n 'reason' => $e->getMessage(),\n ]);\n\n return [];\n }\n\n $response = $apiResponse->toArray();\n\n foreach ($response['results'] as $pipeline) {\n $pipelines[] = [\n 'id' => $pipeline['id'],\n 'label' => $pipeline['label'],\n ];\n }\n\n return $pipelines;\n }\n\n /**\n * @return array<CrmFieldOption>\n */\n public function fetchMeetingOutcomeFieldOptions(Field $field): array\n {\n return $field->getCrmProviderId() === 'meetingOutcome'\n ? $this->fetchMeetingOutcomeTypes()\n : $this->fetchCallActivityTypes();\n }\n\n public function fetchMeetingOutcomeTypes(): array\n {\n return $this->extractMeetingTypeOptions(\n 'https://api.hubapi.com/crm/v3/properties/meeting/hs_meeting_outcome'\n );\n }\n\n public function fetchCallActivityTypes(): array\n {\n return $this->extractMeetingTypeOptions(\n 'https://api.hubapi.com/crm/v3/properties/call/hs_activity_type'\n );\n }\n\n private function extractMeetingTypeOptions(string $endpoint): array\n {\n /** @var Response $response */\n $response = $this->getInstance()\n ->getClient()\n ->request('GET', $endpoint);\n\n /**\n * @var array<array{\n * value: string,\n * label: string,\n * displayOrder: int\n * }> $optionData\n */\n $optionData = $response->toArray()['options'] ?? [];\n\n $options = [];\n foreach ($optionData as $item) {\n $options[] = [\n 'id' => $item['value'],\n 'value' => $item['value'],\n 'label' => $item['label'],\n 'display_order' => $item['displayOrder'],\n ];\n }\n\n return $options;\n }\n\n /**\n * @return array<CrmFieldOption>\n */\n public function fetchDispositionFieldOptions(): array\n {\n $options = [];\n\n $dispositions = $this->fetchCallDispositions();\n\n foreach ($dispositions as $disposition) {\n if ($disposition['deleted'] !== false) {\n continue;\n }\n\n $option['value'] = $disposition['id'];\n $option['id'] = $disposition['id'];\n $option['label'] = $disposition['label'];\n\n $options[] = $option;\n }\n\n return $options;\n }\n\n /**\n * @return array<CrmFieldOption>\n */\n public function fetchOpportunityFieldOptions(Field $field): array\n {\n if ($field->isStageField()) {\n return $this->fetchOpportunityPipelineStages();\n }\n\n if ($field->isPipelineField()) {\n return $this->fetchOpportunityPipelines();\n }\n\n return $this->fetchPropertyOptions('deals', $field->getCrmProviderId());\n }\n\n /**\n * @throws BadRequest\n * @throws HubspotException\n */\n public function makeRequest(string $endpoint, $method = 'GET', $payload = [], ?string $queryString = null)\n {\n $endpoint = self::BASE_URL . $endpoint;\n\n if ($method === 'GET') {\n $response = $this->getInstance()->getClient()?->request(\n method: $method,\n endpoint: $endpoint,\n query_string: $queryString\n );\n } else {\n $response = $this->getInstance()->getClient()->request($method, $endpoint, [\n 'json' => ($payload),\n ]);\n }\n\n $max = $response->getHeaderLine('X-HubSpot-RateLimit-Max'); // \"110\"\n $remaining = $response->getHeaderLine('X-HubSpot-RateLimit-Remaining'); // \"109\"\n $interval = $response->getHeaderLine('X-HubSpot-RateLimit-Interval-Milliseconds'); // \"10000\"\n $body = json_decode((string) $response->getBody(), true);\n\n \\Illuminate\\Support\\Facades\\Log::channel('custom_channel')->info('$max ' . PHP_EOL . print_r($max, true));\n \\Illuminate\\Support\\Facades\\Log::channel('custom_channel')->info('$remaining ' . PHP_EOL . print_r($remaining, true));\n \\Illuminate\\Support\\Facades\\Log::channel('custom_channel')->info('$interval ' . PHP_EOL . print_r($interval, true));\n \\Illuminate\\Support\\Facades\\Log::channel('custom_channel')->info('$body ' . PHP_EOL . print_r($body, true));\n\n return $response;\n }\n\n /**\n * @throws BadRequest\n * @throws HubspotException\n */\n public function createMeeting(array $payload): Response\n {\n $endpoint = '/crm/v3/objects/meetings';\n\n return $this->makeRequest($endpoint, 'POST', $payload);\n }\n\n /**\n * @throws BadRequest\n * @throws HubspotException\n */\n public function updateMeeting(string $meetingId, array $payload): Response\n {\n $endpoint = '/crm/v3/objects/meetings/' . $meetingId;\n\n return $this->makeRequest($endpoint, 'PATCH', $payload);\n }\n\n /**\n * @throws \\Exception\n */\n public function createNote(\n string $body,\n string $ownerId,\n int $timestamp,\n string $objectId,\n NoteObject $noteObject\n ): ?string {\n try {\n $noteInput = new SimplePublicObjectInput([\n 'properties' => [\n 'hs_note_body' => $body,\n 'hubspot_owner_id' => $ownerId,\n 'hs_timestamp' => $timestamp,\n ],\n ]);\n\n // Create note\n $note = $this->getNewInstance()->crm()->objects()->basicApi()->create('note', $noteInput);\n\n $this->getNewInstance()->crm()->objects()->associationsApi()->create(\n 'note',\n $note->getId(),\n $this->getNoteObject($noteObject),\n $objectId,\n $this->getNoteAssociationType($noteObject),\n );\n\n return $note->getId();\n } catch (\\Exception $e) {\n $this->log->error('[Hubspot] Failed to create note', [\n 'objectId' => $objectId,\n 'noteObject' => $noteObject->getObjectType(),\n 'reason' => $e->getMessage(),\n ]);\n\n \\Sentry::captureException($e);\n }\n\n return null;\n }\n\n public function updateEngagement(string $objectId, array $engagement, array $metadata): void\n {\n $this->getInstance()->engagements()->update($objectId, $engagement, $metadata);\n }\n\n public function getEngagementData(string $engagementId): array\n {\n $engagement = $this->getInstance()->engagements()->get($engagementId);\n\n return $engagement->toArray();\n }\n\n public function createEngagement(array $engagement, array $associations, array $metadata): Response\n {\n return $this->getInstance()\n ->engagements()\n ->create($engagement, $associations, $metadata);\n }\n\n public function isUnauthorizedException(\\Exception $e): bool\n {\n // Check for specific HubSpot API exception types first\n if ($e instanceof BadRequest) {\n // BadRequest can contain 401 status codes\n return $e->getCode() === 401;\n }\n\n // Check for HTTP client exceptions with status codes\n if ($e instanceof \\GuzzleHttp\\Exception\\RequestException && $e->hasResponse()) {\n $response = $e->getResponse();\n if ($response !== null) {\n return $response->getStatusCode() === 401;\n }\n }\n\n // Check for Guzzle HTTP exceptions\n if ($e instanceof \\GuzzleHttp\\Exception\\ClientException) {\n return $e->getCode() === 401;\n }\n\n // Fallback to string matching as last resort, but be more specific\n $message = strtolower($e->getMessage());\n\n return str_contains($message, '401 unauthorized') ||\n str_contains($message, 'http 401') ||\n str_contains($message, 'status code 401') ||\n (preg_match('/\\b401\\b/', $message) && str_contains($message, 'unauthorized'));\n }\n\n /**\n * Validates and refreshes the access token if needed before API requests.\n * This ensures long-running processes don't fail due to token expiration.\n *\n * @throws SocialAccountTokenInvalidException\n */\n public function ensureValidToken(): void\n {\n if ($this->oauthAccount === null) {\n return;\n }\n\n $newToken = $this->tokenManager->ensureValidToken($this->oauthAccount);\n if ($newToken !== null) {\n $this->accessToken = $newToken;\n }\n }\n\n public function getConfig()\n {\n return $this->config;\n }\n\n // returns only active (archived=false)\n public function getOwners(): array\n {\n return $this->getNewInstance()->crm()->owners()->getAll();\n }\n\n /**\n * @param bool $archived\n *\n * @return array<Owner>|[]\n */\n public function getOwnersArchived(bool $archived = true): array\n {\n $endpoint = '/crm/v3/owners';\n $queryParams = [\n 'archived' => $archived ? 'true' : 'false',\n ];\n $queryString = http_build_query($queryParams);\n\n $owners = [];\n\n try {\n $response = $this->makeRequest(endpoint: $endpoint, queryString: $queryString);\n $responseData = $response?->toArray();\n\n foreach ($responseData['results'] as $result) {\n try {\n $owners[] = Owner::create($result);\n } catch (Throwable $e) {\n $this->log->error('[HubSpot] Failed to process owner data', [\n 'result' => $result,\n 'error' => $e->getMessage(),\n ]);\n\n continue;\n }\n }\n } catch (Throwable $e) {\n $this->log->error('HubSpot] Failed to fetch owners', [\n 'archived' => $archived,\n 'error' => $e->getMessage(),\n ]);\n\n return [];\n }\n\n return $owners;\n }\n\n public function getMeeting(string $engagementId): ObjectWithAssociations\n {\n return $this->getNewInstance()->crm()->objects()->basicApi()\n ->getById('meeting', $engagementId, null, 'contact,company,deal');\n }\n\n public function deleteEngagement(string $engagementId): void\n {\n $this->getInstance()->engagements()->delete((int) $engagementId);\n }\n\n public function getAssociationsData(array $ids, string $fromObject, string $toObject): array\n {\n $associationData = [];\n $idChunks = array_chunk($ids, self::ASSOCIATIONS_BATCH_SIZE_LIMIT);\n\n foreach ($idChunks as $idChunk) {\n try {\n $batchInput = new \\HubSpot\\Client\\Crm\\Associations\\Model\\BatchInputPublicObjectId();\n $batchInput->setInputs(array_map(function ($id) {\n $publicObjectId = new \\HubSpot\\Client\\Crm\\Associations\\Model\\PublicObjectId();\n $publicObjectId->setId($id);\n\n return $publicObjectId;\n }, $idChunk));\n\n $associatedObjectsData = $this\n ->getNewInstance()\n ->crm()\n ->associations()\n ->batchApi()\n ->read($fromObject, $toObject, $batchInput);\n\n if ($associatedObjectsData instanceof \\HubSpot\\Client\\Crm\\Associations\\Model\\BatchResponsePublicAssociationMulti) {\n foreach ($associatedObjectsData->getResults() as $association) {\n $from = $association->getFrom()->getId();\n $toAssociations = $association->getTo();\n\n if (! empty($toAssociations)) {\n $associationData[$from] = array_map(function ($item) {\n return $item->getId();\n }, $toAssociations);\n }\n }\n }\n } catch (\\Exception $e) {\n $this->log->error('[Hubspot] Failed to fetch associations', [\n 'from_object' => $fromObject,\n 'to_object' => $toObject,\n 'reason' => $e->getMessage(),\n ]);\n }\n }\n\n return $associationData;\n }\n\n /**\n * @throws \\Exception\n */\n private function getNoteAssociationType(NoteObject $noteObject): string\n {\n return match($noteObject) {\n NoteObject::Opportunity => 'note_to_deal',\n NoteObject::Lead, NoteObject::Contact => 'note_to_contact', // or 'note_to_lead' if your portal supports it\n NoteObject::Account => 'note_to_company',\n NoteObject::Call, NoteObject::Event => throw new \\Exception('Not supported'),\n };\n }\n\n /**\n * @throws \\Exception\n */\n private function getNoteObject(NoteObject $noteObject): string\n {\n return match($noteObject) {\n NoteObject::Opportunity => 'deal',\n NoteObject::Lead, NoteObject::Contact => 'contact',\n NoteObject::Account => 'company',\n NoteObject::Call, NoteObject::Event => throw new \\Exception('Not supported'),\n };\n }\n\n public function addAssociations(string $objectType, string $associationType, array $payload): Response\n {\n $endpoint = \"/crm/v4/associations/$objectType/$associationType/batch/create\";\n\n return $this->makeRequest($endpoint, 'POST', $payload);\n }\n\n public function removeAssociations(string $objectType, string $associationType, array $payload): Response\n {\n $endpoint = \"/crm/v4/associations/$objectType/$associationType/batch/archive\";\n\n return $this->makeRequest($endpoint, 'POST', $payload);\n }\n}","depth":4,"on_screen":true,"value":"<?php\n\ndeclare(strict_types=1);\n\nnamespace Jiminny\\Services\\Crm\\Hubspot;\n\nuse HubSpot\\Client\\Crm\\Deals\\ApiException as DealApiException;\nuse HubSpot\\Client\\Crm\\Contacts\\ApiException as ContactApiException;\nuse HubSpot\\Client\\Crm\\Companies\\ApiException as CompanyApiException;\nuse HubSpot\\Client\\Crm\\Contacts\\Model\\SimplePublicObjectWithAssociations as ContactsWithAssociations;\nuse HubSpot\\Client\\Crm\\Companies\\Model\\SimplePublicObjectWithAssociations as CompaniesWithAssociations;\nuse HubSpot\\Client\\Crm\\Deals\\Model\\SimplePublicObjectWithAssociations as DealWithAssociations;\nuse HubSpot\\Client\\Crm\\Objects\\Model\\SimplePublicObjectInput;\nuse HubSpot\\Client\\Crm\\Objects\\Model\\SimplePublicObjectWithAssociations as ObjectWithAssociations;\nuse HubSpot\\Client\\Crm\\Pipelines\\Model\\Error;\nuse HubSpot\\Client\\Crm\\Pipelines\\Model\\PipelineStage;\nuse HubSpot\\Client\\Crm\\Properties\\Model\\Property;\nuse HubSpot\\Discovery\\Discovery;\nuse Jiminny\\Component\\Utility\\Service\\ProviderRateLimiter;\nuse Jiminny\\Exceptions\\CrmException;\nuse Jiminny\\Exceptions\\RateLimitException;\nuse Jiminny\\Exceptions\\SocialAccountTokenInvalidException;\nuse Jiminny\\Jobs\\Crm\\NoteObject;\nuse Jiminny\\Models\\Crm\\Field;\nuse Jiminny\\Services\\Crm\\BaseClient;\nuse Jiminny\\Services\\Crm\\Hubspot\\DTO\\Response\\Owner;\nuse Jiminny\\Services\\SocialAccountService;\nuse SevenShores\\Hubspot\\Exceptions\\BadRequest;\nuse SevenShores\\Hubspot\\Exceptions\\HubspotException;\nuse SevenShores\\Hubspot\\Factory;\nuse SevenShores\\Hubspot\\Http\\Response;\nuse Jiminny\\Services\\Crm\\Hubspot\\Pagination\\HubspotPaginationService;\nuse Throwable;\n\n/**\n * @phpstan-type CrmFieldOption array{id:string, label:string, value?:string}\n */\nclass Client extends BaseClient implements HubspotClientInterface\n{\n public const string MIN_API_VERSION = '2';\n\n public const string BASE_URL = 'https://api.hubapi.com';\n\n public const int ASSOCIATIONS_BATCH_SIZE_LIMIT = 1000;\n\n private HubspotPaginationService $paginationService;\n private HubspotTokenManager $tokenManager;\n private ProviderRateLimiter $rateLimiter;\n\n public function __construct(\n SocialAccountService $socialAccountService,\n HubspotPaginationService $paginationService,\n HubspotTokenManager $tokenManager,\n ProviderRateLimiter $rateLimiter,\n ) {\n parent::__construct($socialAccountService);\n $this->paginationService = $paginationService;\n $this->tokenManager = $tokenManager;\n $this->rateLimiter = $rateLimiter;\n\n $this->setBaseUrl(self::BASE_URL);\n $this->setVersion(self::MIN_API_VERSION);\n }\n\n /**\n * Single entry point for every HubSpot API call. Enforces the per-portal\n * rate limit configured in the rate_limits table (morphed to the current\n * Configuration) and reacts to a real 429 from HubSpot by translating it\n * into a RateLimitException carrying Retry-After.\n *\n * Wrap any outbound HubSpot call (SDK or raw HTTP) like:\n *\n * $this->executeRequest(fn () => $this->getNewInstance()->crm()->...);\n *\n * @template T\n * @param callable(): T $apiCall\n * @return T\n *\n * @throws RateLimitException\n */\n private function executeRequest(callable $apiCall)\n {\n if (! $this->rateLimiter->canMakeRequest($this->config)) {\n $retryAfter = $this->rateLimiter->requestAvailableIn($this->config);\n\n $this->log->warning('[Hubspot] Rate limit exceeded, deferring request', [\n 'team_id' => $this->config->team_id,\n 'config_id' => $this->config->getId(),\n 'retry_after' => $retryAfter,\n ]);\n\n throw new RateLimitException(\n 'Hubspot rate limit reached for configuration ' . $this->config->getId(),\n $retryAfter,\n );\n }\n\n $this->rateLimiter->incrementRequestCount($this->config);\n\n try {\n return $apiCall();\n } catch (Throwable $e) {\n if ($this->isHubspotRateLimit($e)) {\n $retryAfter = $this->parseRetryAfter($e);\n\n $this->log->warning('[Hubspot] Received 429 from API', [\n 'team_id' => $this->config->team_id,\n 'config_id' => $this->config->getId(),\n 'retry_after' => $retryAfter,\n 'reason' => $e->getMessage(),\n ]);\n\n throw new RateLimitException('Hubspot returned 429', $retryAfter, $e);\n }\n\n throw $e;\n }\n }\n\n private function isHubspotRateLimit(Throwable $e): bool\n {\n return method_exists($e, 'getCode') && (int) $e->getCode() === 429;\n }\n\n private function parseRetryAfter(Throwable $e): int\n {\n if (method_exists($e, 'getResponseHeaders')) {\n $headers = $e->getResponseHeaders() ?: [];\n $value = $headers['Retry-After'] ?? $headers['retry-after'] ?? null;\n if (is_array($value)) {\n $value = $value[0] ?? null;\n }\n if (is_numeric($value)) {\n return (int) $value;\n }\n }\n\n return 10;\n }\n\n public function getMinimumApiVersion(): string\n {\n return self::MIN_API_VERSION;\n }\n\n public function getInstance(): Factory\n {\n return new Factory([\n 'key' => $this->accessToken,\n 'oauth2' => true,\n 'base_url' => $this->baseUrl,\n ]);\n }\n\n public function getNewInstance(): Discovery\n {\n return \\HubSpot\\Factory::createWithAccessToken($this->accessToken);\n }\n\n /**\n * Secondly and daily limits for Hubspot API\n *\n * Product Tier: Free & Starter | Professional & Enterprise | API add-on (any tier)\n * Burst: 100/10 seconds | 150/10 seconds | 200/10 seconds\n * Daily: 250,000 | 500,000 | 1,000,000\n *\n * Official documentation states: The search endpoints are rate limited to five requests per second.\n * Since with 5 RPS were still hitting secondly rate limits we lowered it to 4\n */\n public function getPaginatedData(array $payload, string $type, int $offset = 0): array\n {\n $total = 0;\n $lastId = null;\n $rows = [];\n foreach ($this->getPaginatedDataGenerator($payload, $type, $offset, $total, $lastId) as $row) {\n $rows[] = $row;\n }\n\n return ['results' => $rows, 'total' => $total, 'last_record' => $lastId];\n }\n\n /**\n * @throws HubspotException\n * @throws SocialAccountTokenInvalidException\n * @throws BadRequest\n */\n public function getPaginatedDataGenerator(\n array $payload,\n string $type,\n int $offset = 0,\n int &$total = 0,\n ?string &$lastRecordId = null\n ): \\Generator {\n return $this->paginationService->getPaginatedDataGenerator(\n $this,\n $payload,\n $type,\n $offset,\n $total,\n $lastRecordId\n );\n }\n\n /**\n * @throws DealApiException\n * @throws CrmException\n */\n public function getOpportunityById(string $crmId, array $fields): array\n {\n try {\n $deal = $this->executeRequest(fn () => $this->getNewInstance()->crm()->deals()->basicApi()->getById(\n $crmId,\n implode(',', $fields),\n 'companies,contacts'\n ));\n } catch (DealApiException $e) {\n $this->log->info('[Hubspot] Failed to fetch opportunity', [\n 'crm_id' => $crmId,\n 'reason' => $e->getMessage(),\n ]);\n\n throw $e;\n }\n\n if (! $deal instanceof DealWithAssociations) {\n throw new CrmException('Deal not found');\n }\n\n return [\n 'id' => $deal->getId(),\n 'properties' => $deal->getProperties(),\n 'associations' => $deal->getAssociations(),\n ];\n }\n\n /**\n * Generic batch read method for HubSpot objects\n *\n * @param string $objectType The object type ('deals', 'companies', 'contacts')\n * @param array<string> $crmIds Array of HubSpot object IDs (max 100)\n * @param array<string> $fields Array of property names to fetch\n *\n * @return array<string, array> Array keyed by CRM ID with object data\n */\n private function batchReadObjects(string $objectType, array $crmIds, array $fields): array\n {\n if (empty($crmIds)) {\n return [];\n }\n\n $this->validateBatchSize($objectType, $crmIds);\n $this->ensureValidToken();\n\n try {\n $batchConfig = $this->createBatchConfiguration($objectType);\n $batchReadRequest = $this->prepareBatchRequest($batchConfig, $crmIds, $fields);\n $response = $batchConfig['api']->read($batchReadRequest);\n\n $this->validateApiResponse($response, $objectType);\n\n $results = $this->processApiResults($response);\n $this->logBatchResults($objectType, $crmIds, $results);\n\n return $results;\n } catch (\\Throwable $e) {\n $this->handleBatchError($e, $objectType, $crmIds);\n }\n }\n\n private function validateBatchSize(string $objectType, array $crmIds): void\n {\n if (count($crmIds) > 100) {\n throw new \\InvalidArgumentException(\"Batch size cannot exceed 100 {$objectType}\");\n }\n }\n\n private function createBatchConfiguration(string $objectType): array\n {\n $configurations = [\n 'deals' => [\n 'batchReadRequest' => new \\HubSpot\\Client\\Crm\\Deals\\Model\\BatchReadInputSimplePublicObjectId(),\n 'inputClass' => \\HubSpot\\Client\\Crm\\Deals\\Model\\SimplePublicObjectId::class,\n 'api' => $this->getNewInstance()->crm()->deals()->batchApi(),\n ],\n 'companies' => [\n 'batchReadRequest' => new \\HubSpot\\Client\\Crm\\Companies\\Model\\BatchReadInputSimplePublicObjectId(),\n 'inputClass' => \\HubSpot\\Client\\Crm\\Companies\\Model\\SimplePublicObjectId::class,\n 'api' => $this->getNewInstance()->crm()->companies()->batchApi(),\n ],\n 'contacts' => [\n 'batchReadRequest' => new \\HubSpot\\Client\\Crm\\Contacts\\Model\\BatchReadInputSimplePublicObjectId(),\n 'inputClass' => \\HubSpot\\Client\\Crm\\Contacts\\Model\\SimplePublicObjectId::class,\n 'api' => $this->getNewInstance()->crm()->contacts()->batchApi(),\n ],\n ];\n\n if (! isset($configurations[$objectType])) {\n throw new \\InvalidArgumentException(\"Unsupported object type: {$objectType}\");\n }\n\n return $configurations[$objectType];\n }\n\n private function prepareBatchRequest(array $batchConfig, array $crmIds, array $fields): object\n {\n $batchReadRequest = $batchConfig['batchReadRequest'];\n $inputClass = $batchConfig['inputClass'];\n\n $inputs = array_map(function ($crmId) use ($inputClass) {\n $input = new $inputClass();\n $input->setId($crmId);\n\n return $input;\n }, $crmIds);\n\n $batchReadRequest->setInputs($inputs);\n $batchReadRequest->setProperties($fields);\n\n return $batchReadRequest;\n }\n\n private function validateApiResponse($response, string $objectType): void\n {\n if (! $response) {\n throw new CrmException(\"HubSpot API returned null response for {$objectType} batch read\");\n }\n }\n\n private function processApiResults($response): array\n {\n $results = [];\n $responseResults = $response->getResults();\n\n if ($responseResults) {\n foreach ($responseResults as $object) {\n if ($object && $object->getId()) {\n $results[$object->getId()] = [\n 'id' => $object->getId(),\n 'properties' => $object->getProperties() ?: [],\n ];\n }\n }\n }\n\n return $results;\n }\n\n private function logBatchResults(string $objectType, array $crmIds, array $results): void\n {\n $this->log->info(\"[HubSpot] Batch fetched {$objectType}\", [\n 'requested_count' => count($crmIds),\n 'returned_count' => count($results),\n 'crm_ids' => $crmIds,\n ]);\n }\n\n private function handleBatchError(\\Throwable $e, string $objectType, array $crmIds): void\n {\n $errorMessage = $e->getMessage() ?: 'Unknown error';\n $errorTrace = $e->getTraceAsString() ?: 'No trace available';\n\n $this->log->error(\"[HubSpot] Failed to batch fetch {$objectType}\", [\n 'crm_ids' => $crmIds,\n 'error' => $errorMessage,\n 'trace' => $errorTrace,\n ]);\n\n throw new CrmException(\"Failed to batch fetch {$objectType}: \" . $errorMessage);\n }\n\n /**\n * Batch read multiple opportunities by their CRM IDs\n *\n * @param array<string> $crmIds Array of HubSpot deal IDs (max 100)\n * @param array<string> $fields Array of property names to fetch\n *\n * @return array<string, array> Array keyed by CRM ID with opportunity data\n */\n public function getOpportunitiesByIds(array $crmIds, array $fields): array\n {\n return $this->batchReadObjects('deals', $crmIds, $fields);\n }\n\n /**\n * Batch read multiple companies by their CRM IDs\n *\n * @param array<string> $crmIds Array of HubSpot company IDs (max 100)\n * @param array<string> $fields Array of property names to fetch\n *\n * @return array<string, array> Array keyed by CRM ID with company data\n */\n public function getCompaniesByIds(array $crmIds, array $fields): array\n {\n return $this->batchReadObjects('companies', $crmIds, $fields);\n }\n\n /**\n * Batch read multiple contacts by their CRM IDs\n *\n * @param array<string> $crmIds Array of HubSpot contact IDs (max 100)\n * @param array<string> $fields Array of property names to fetch\n *\n * @return array<string, array> Array keyed by CRM ID with contact data\n */\n public function getContactsByIds(array $crmIds, array $fields): array\n {\n return $this->batchReadObjects('contacts', $crmIds, $fields);\n }\n\n /**\n * @throws CompanyApiException\n * @throws CrmException\n */\n public function getAccountById(string $crmId, array $fields): array\n {\n try {\n $company = $this->getNewInstance()->crm()->companies()->basicApi()->getById(\n $crmId,\n implode(',', $fields),\n );\n } catch (CompanyApiException $e) {\n $this->log->info('[Hubspot] Failed to fetch account', [\n 'crm_id' => $crmId,\n 'reason' => $e->getMessage(),\n ]);\n\n throw $e;\n }\n\n if (! $company instanceof CompaniesWithAssociations) {\n throw new CrmException('Account not found');\n }\n\n return [\n 'id' => $company->getId(),\n 'properties' => $company->getProperties(),\n ];\n }\n\n /**\n * @throws ContactApiException\n * @throws CrmException\n */\n public function getContactById(string $crmId, array $fields): array\n {\n try {\n $contact = $this->getNewInstance()->crm()->contacts()->basicApi()->getById(\n $crmId,\n implode(',', $fields)\n );\n } catch (ContactApiException $e) {\n $this->log->info('[Hubspot] Failed to fetch contact', [\n 'crm_id' => $crmId,\n 'reason' => $e->getMessage(),\n ]);\n\n throw $e;\n }\n\n if (! $contact instanceof ContactsWithAssociations) {\n throw new CrmException('Contact not found');\n }\n\n return [\n 'id' => $contact->getId(),\n 'properties' => $contact->getProperties(),\n ];\n }\n\n /**\n * This is email search request that Hubspot offers as GET (more generous quota)\n */\n public function getContactByEmail(string $email, array $fields = []): array\n {\n try {\n $contact = $this->getNewInstance()->crm()->contacts()->basicApi()->getById(\n $email,\n implode(',', $fields),\n null,\n false,\n 'email'\n );\n\n return [\n 'id' => $contact->getId(),\n 'properties' => $contact->getProperties(),\n ];\n } catch (ContactApiException $e) {\n $this->log->info('[Hubspot] Failed to fetch contact', [\n 'email' => $email,\n 'reason' => $e->getMessage(),\n ]);\n\n return [];\n }\n }\n\n /**\n * @throws CrmException\n */\n public function fetchProperty(string $objectType, string $propertyId): Property\n {\n $result = $this->getNewInstance()->crm()->properties()->coreApi()->getByName($objectType, $propertyId);\n\n if (! $result instanceof Property) {\n $this->log->error('[Hubspot] Failed to fetch property', [\n 'object_type' => $objectType,\n 'property_id' => $propertyId,\n 'reason' => $result->getMessage(),\n ]);\n\n throw new CrmException('Failed to fetch property');\n }\n\n return $result;\n }\n\n /**\n * @return array<CrmFieldOption>\n */\n public function fetchPropertyOptions(string $objectType, string $propertyId): array\n {\n /** @var array<CrmFieldOption> */\n return $this->fetchProperty($objectType, $propertyId)->getOptions();\n }\n\n /**\n * @return array<array{id:string, label:string, deleted:bool}>\n */\n public function fetchCallDispositions(): array\n {\n /** @var Response $response */\n $response = $this->getInstance()->engagements()->getCallDispositions();\n\n /**\n * @var array<array{\n * id:string,\n * label:string,\n * deleted: bool\n * }>\n */\n return $response->toArray();\n }\n\n /**\n * @return array<CrmFieldOption>\n */\n public function fetchOpportunityPipelineStages(): array\n {\n $stages = [];\n $apiResponse = $this->getNewInstance()->crm()->pipelines()->pipelinesApi()->getAll('deals');\n\n if ($apiResponse instanceof Error) {\n $this->log->error('[Hubspot] Failed to fetch opportunity pipelines', [\n 'reason' => $apiResponse->getMessage(),\n ]);\n\n return [];\n }\n\n foreach ($apiResponse->getResults() as $pipeline) {\n $pipelineStages = array_map(\n static function (PipelineStage $stage) {\n return [\n 'id' => $stage->getId(),\n 'label' => $stage->getLabel(),\n ];\n },\n $pipeline->getStages()\n );\n\n $stages = array_merge($stages, $pipelineStages);\n }\n\n return $stages;\n }\n\n public function fetchOpportunityPipelines(): array\n {\n $pipelines = [];\n\n try {\n $apiResponse = $this->makeRequest('/crm/v3/pipelines/deals');\n } catch (\\Exception $e) {\n $this->log->info('[Hubspot] Failed to fetch opportunity pipelines', [\n 'reason' => $e->getMessage(),\n ]);\n\n return [];\n }\n\n $response = $apiResponse->toArray();\n\n foreach ($response['results'] as $pipeline) {\n $pipelines[] = [\n 'id' => $pipeline['id'],\n 'label' => $pipeline['label'],\n ];\n }\n\n return $pipelines;\n }\n\n /**\n * @return array<CrmFieldOption>\n */\n public function fetchMeetingOutcomeFieldOptions(Field $field): array\n {\n return $field->getCrmProviderId() === 'meetingOutcome'\n ? $this->fetchMeetingOutcomeTypes()\n : $this->fetchCallActivityTypes();\n }\n\n public function fetchMeetingOutcomeTypes(): array\n {\n return $this->extractMeetingTypeOptions(\n 'https://api.hubapi.com/crm/v3/properties/meeting/hs_meeting_outcome'\n );\n }\n\n public function fetchCallActivityTypes(): array\n {\n return $this->extractMeetingTypeOptions(\n 'https://api.hubapi.com/crm/v3/properties/call/hs_activity_type'\n );\n }\n\n private function extractMeetingTypeOptions(string $endpoint): array\n {\n /** @var Response $response */\n $response = $this->getInstance()\n ->getClient()\n ->request('GET', $endpoint);\n\n /**\n * @var array<array{\n * value: string,\n * label: string,\n * displayOrder: int\n * }> $optionData\n */\n $optionData = $response->toArray()['options'] ?? [];\n\n $options = [];\n foreach ($optionData as $item) {\n $options[] = [\n 'id' => $item['value'],\n 'value' => $item['value'],\n 'label' => $item['label'],\n 'display_order' => $item['displayOrder'],\n ];\n }\n\n return $options;\n }\n\n /**\n * @return array<CrmFieldOption>\n */\n public function fetchDispositionFieldOptions(): array\n {\n $options = [];\n\n $dispositions = $this->fetchCallDispositions();\n\n foreach ($dispositions as $disposition) {\n if ($disposition['deleted'] !== false) {\n continue;\n }\n\n $option['value'] = $disposition['id'];\n $option['id'] = $disposition['id'];\n $option['label'] = $disposition['label'];\n\n $options[] = $option;\n }\n\n return $options;\n }\n\n /**\n * @return array<CrmFieldOption>\n */\n public function fetchOpportunityFieldOptions(Field $field): array\n {\n if ($field->isStageField()) {\n return $this->fetchOpportunityPipelineStages();\n }\n\n if ($field->isPipelineField()) {\n return $this->fetchOpportunityPipelines();\n }\n\n return $this->fetchPropertyOptions('deals', $field->getCrmProviderId());\n }\n\n /**\n * @throws BadRequest\n * @throws HubspotException\n */\n public function makeRequest(string $endpoint, $method = 'GET', $payload = [], ?string $queryString = null)\n {\n $endpoint = self::BASE_URL . $endpoint;\n\n if ($method === 'GET') {\n $response = $this->getInstance()->getClient()?->request(\n method: $method,\n endpoint: $endpoint,\n query_string: $queryString\n );\n } else {\n $response = $this->getInstance()->getClient()->request($method, $endpoint, [\n 'json' => ($payload),\n ]);\n }\n\n $max = $response->getHeaderLine('X-HubSpot-RateLimit-Max'); // \"110\"\n $remaining = $response->getHeaderLine('X-HubSpot-RateLimit-Remaining'); // \"109\"\n $interval = $response->getHeaderLine('X-HubSpot-RateLimit-Interval-Milliseconds'); // \"10000\"\n $body = json_decode((string) $response->getBody(), true);\n\n \\Illuminate\\Support\\Facades\\Log::channel('custom_channel')->info('$max ' . PHP_EOL . print_r($max, true));\n \\Illuminate\\Support\\Facades\\Log::channel('custom_channel')->info('$remaining ' . PHP_EOL . print_r($remaining, true));\n \\Illuminate\\Support\\Facades\\Log::channel('custom_channel')->info('$interval ' . PHP_EOL . print_r($interval, true));\n \\Illuminate\\Support\\Facades\\Log::channel('custom_channel')->info('$body ' . PHP_EOL . print_r($body, true));\n\n return $response;\n }\n\n /**\n * @throws BadRequest\n * @throws HubspotException\n */\n public function createMeeting(array $payload): Response\n {\n $endpoint = '/crm/v3/objects/meetings';\n\n return $this->makeRequest($endpoint, 'POST', $payload);\n }\n\n /**\n * @throws BadRequest\n * @throws HubspotException\n */\n public function updateMeeting(string $meetingId, array $payload): Response\n {\n $endpoint = '/crm/v3/objects/meetings/' . $meetingId;\n\n return $this->makeRequest($endpoint, 'PATCH', $payload);\n }\n\n /**\n * @throws \\Exception\n */\n public function createNote(\n string $body,\n string $ownerId,\n int $timestamp,\n string $objectId,\n NoteObject $noteObject\n ): ?string {\n try {\n $noteInput = new SimplePublicObjectInput([\n 'properties' => [\n 'hs_note_body' => $body,\n 'hubspot_owner_id' => $ownerId,\n 'hs_timestamp' => $timestamp,\n ],\n ]);\n\n // Create note\n $note = $this->getNewInstance()->crm()->objects()->basicApi()->create('note', $noteInput);\n\n $this->getNewInstance()->crm()->objects()->associationsApi()->create(\n 'note',\n $note->getId(),\n $this->getNoteObject($noteObject),\n $objectId,\n $this->getNoteAssociationType($noteObject),\n );\n\n return $note->getId();\n } catch (\\Exception $e) {\n $this->log->error('[Hubspot] Failed to create note', [\n 'objectId' => $objectId,\n 'noteObject' => $noteObject->getObjectType(),\n 'reason' => $e->getMessage(),\n ]);\n\n \\Sentry::captureException($e);\n }\n\n return null;\n }\n\n public function updateEngagement(string $objectId, array $engagement, array $metadata): void\n {\n $this->getInstance()->engagements()->update($objectId, $engagement, $metadata);\n }\n\n public function getEngagementData(string $engagementId): array\n {\n $engagement = $this->getInstance()->engagements()->get($engagementId);\n\n return $engagement->toArray();\n }\n\n public function createEngagement(array $engagement, array $associations, array $metadata): Response\n {\n return $this->getInstance()\n ->engagements()\n ->create($engagement, $associations, $metadata);\n }\n\n public function isUnauthorizedException(\\Exception $e): bool\n {\n // Check for specific HubSpot API exception types first\n if ($e instanceof BadRequest) {\n // BadRequest can contain 401 status codes\n return $e->getCode() === 401;\n }\n\n // Check for HTTP client exceptions with status codes\n if ($e instanceof \\GuzzleHttp\\Exception\\RequestException && $e->hasResponse()) {\n $response = $e->getResponse();\n if ($response !== null) {\n return $response->getStatusCode() === 401;\n }\n }\n\n // Check for Guzzle HTTP exceptions\n if ($e instanceof \\GuzzleHttp\\Exception\\ClientException) {\n return $e->getCode() === 401;\n }\n\n // Fallback to string matching as last resort, but be more specific\n $message = strtolower($e->getMessage());\n\n return str_contains($message, '401 unauthorized') ||\n str_contains($message, 'http 401') ||\n str_contains($message, 'status code 401') ||\n (preg_match('/\\b401\\b/', $message) && str_contains($message, 'unauthorized'));\n }\n\n /**\n * Validates and refreshes the access token if needed before API requests.\n * This ensures long-running processes don't fail due to token expiration.\n *\n * @throws SocialAccountTokenInvalidException\n */\n public function ensureValidToken(): void\n {\n if ($this->oauthAccount === null) {\n return;\n }\n\n $newToken = $this->tokenManager->ensureValidToken($this->oauthAccount);\n if ($newToken !== null) {\n $this->accessToken = $newToken;\n }\n }\n\n public function getConfig()\n {\n return $this->config;\n }\n\n // returns only active (archived=false)\n public function getOwners(): array\n {\n return $this->getNewInstance()->crm()->owners()->getAll();\n }\n\n /**\n * @param bool $archived\n *\n * @return array<Owner>|[]\n */\n public function getOwnersArchived(bool $archived = true): array\n {\n $endpoint = '/crm/v3/owners';\n $queryParams = [\n 'archived' => $archived ? 'true' : 'false',\n ];\n $queryString = http_build_query($queryParams);\n\n $owners = [];\n\n try {\n $response = $this->makeRequest(endpoint: $endpoint, queryString: $queryString);\n $responseData = $response?->toArray();\n\n foreach ($responseData['results'] as $result) {\n try {\n $owners[] = Owner::create($result);\n } catch (Throwable $e) {\n $this->log->error('[HubSpot] Failed to process owner data', [\n 'result' => $result,\n 'error' => $e->getMessage(),\n ]);\n\n continue;\n }\n }\n } catch (Throwable $e) {\n $this->log->error('HubSpot] Failed to fetch owners', [\n 'archived' => $archived,\n 'error' => $e->getMessage(),\n ]);\n\n return [];\n }\n\n return $owners;\n }\n\n public function getMeeting(string $engagementId): ObjectWithAssociations\n {\n return $this->getNewInstance()->crm()->objects()->basicApi()\n ->getById('meeting', $engagementId, null, 'contact,company,deal');\n }\n\n public function deleteEngagement(string $engagementId): void\n {\n $this->getInstance()->engagements()->delete((int) $engagementId);\n }\n\n public function getAssociationsData(array $ids, string $fromObject, string $toObject): array\n {\n $associationData = [];\n $idChunks = array_chunk($ids, self::ASSOCIATIONS_BATCH_SIZE_LIMIT);\n\n foreach ($idChunks as $idChunk) {\n try {\n $batchInput = new \\HubSpot\\Client\\Crm\\Associations\\Model\\BatchInputPublicObjectId();\n $batchInput->setInputs(array_map(function ($id) {\n $publicObjectId = new \\HubSpot\\Client\\Crm\\Associations\\Model\\PublicObjectId();\n $publicObjectId->setId($id);\n\n return $publicObjectId;\n }, $idChunk));\n\n $associatedObjectsData = $this\n ->getNewInstance()\n ->crm()\n ->associations()\n ->batchApi()\n ->read($fromObject, $toObject, $batchInput);\n\n if ($associatedObjectsData instanceof \\HubSpot\\Client\\Crm\\Associations\\Model\\BatchResponsePublicAssociationMulti) {\n foreach ($associatedObjectsData->getResults() as $association) {\n $from = $association->getFrom()->getId();\n $toAssociations = $association->getTo();\n\n if (! empty($toAssociations)) {\n $associationData[$from] = array_map(function ($item) {\n return $item->getId();\n }, $toAssociations);\n }\n }\n }\n } catch (\\Exception $e) {\n $this->log->error('[Hubspot] Failed to fetch associations', [\n 'from_object' => $fromObject,\n 'to_object' => $toObject,\n 'reason' => $e->getMessage(),\n ]);\n }\n }\n\n return $associationData;\n }\n\n /**\n * @throws \\Exception\n */\n private function getNoteAssociationType(NoteObject $noteObject): string\n {\n return match($noteObject) {\n NoteObject::Opportunity => 'note_to_deal',\n NoteObject::Lead, NoteObject::Contact => 'note_to_contact', // or 'note_to_lead' if your portal supports it\n NoteObject::Account => 'note_to_company',\n NoteObject::Call, NoteObject::Event => throw new \\Exception('Not supported'),\n };\n }\n\n /**\n * @throws \\Exception\n */\n private function getNoteObject(NoteObject $noteObject): string\n {\n return match($noteObject) {\n NoteObject::Opportunity => 'deal',\n NoteObject::Lead, NoteObject::Contact => 'contact',\n NoteObject::Account => 'company',\n NoteObject::Call, NoteObject::Event => throw new \\Exception('Not supported'),\n };\n }\n\n public function addAssociations(string $objectType, string $associationType, array $payload): Response\n {\n $endpoint = \"/crm/v4/associations/$objectType/$associationType/batch/create\";\n\n return $this->makeRequest($endpoint, 'POST', $payload);\n }\n\n public function removeAssociations(string $objectType, string $associationType, array $payload): Response\n {\n $endpoint = \"/crm/v4/associations/$objectType/$associationType/batch/archive\";\n\n return $this->makeRequest($endpoint, 'POST', $payload);\n }\n}","role_description":"text entry area","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"Project","depth":3,"on_screen":false,"role_description":"text"},{"role":"AXButton","text":"Project","depth":3,"bounds":{"left":0.011968086,"top":0.047885075,"width":0.024268618,"height":0.024740623},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"New File or Directory…","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.008643617,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Expand Selected","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.008643617,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Collapse All","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.008643617,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Options","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.008643617,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Hide","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.008643617,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false}]...
|
-188086479456983350
|
6378757184196315236
|
click
|
accessibility
|
NULL
|
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
Editor for custom.log
Sync Changes
Hide This Notification
Code changed:
Hide
2
60
Previous Highlighted Error
Next Highlighted Error
<?php
declare(strict_types=1);
namespace Jiminny\Services\Crm\Hubspot;
use HubSpot\Client\Crm\Deals\ApiException as DealApiException;
use HubSpot\Client\Crm\Contacts\ApiException as ContactApiException;
use HubSpot\Client\Crm\Companies\ApiException as CompanyApiException;
use HubSpot\Client\Crm\Contacts\Model\SimplePublicObjectWithAssociations as ContactsWithAssociations;
use HubSpot\Client\Crm\Companies\Model\SimplePublicObjectWithAssociations as CompaniesWithAssociations;
use HubSpot\Client\Crm\Deals\Model\SimplePublicObjectWithAssociations as DealWithAssociations;
use HubSpot\Client\Crm\Objects\Model\SimplePublicObjectInput;
use HubSpot\Client\Crm\Objects\Model\SimplePublicObjectWithAssociations as ObjectWithAssociations;
use HubSpot\Client\Crm\Pipelines\Model\Error;
use HubSpot\Client\Crm\Pipelines\Model\PipelineStage;
use HubSpot\Client\Crm\Properties\Model\Property;
use HubSpot\Discovery\Discovery;
use Jiminny\Component\Utility\Service\ProviderRateLimiter;
use Jiminny\Exceptions\CrmException;
use Jiminny\Exceptions\RateLimitException;
use Jiminny\Exceptions\SocialAccountTokenInvalidException;
use Jiminny\Jobs\Crm\NoteObject;
use Jiminny\Models\Crm\Field;
use Jiminny\Services\Crm\BaseClient;
use Jiminny\Services\Crm\Hubspot\DTO\Response\Owner;
use Jiminny\Services\SocialAccountService;
use SevenShores\Hubspot\Exceptions\BadRequest;
use SevenShores\Hubspot\Exceptions\HubspotException;
use SevenShores\Hubspot\Factory;
use SevenShores\Hubspot\Http\Response;
use Jiminny\Services\Crm\Hubspot\Pagination\HubspotPaginationService;
use Throwable;
/**
* @phpstan-type CrmFieldOption array{id:string, label:string, value?:string}
*/
class Client extends BaseClient implements HubspotClientInterface
{
public const string MIN_API_VERSION = '2';
public const string BASE_URL = '[URL_WITH_CREDENTIALS] T
* @param callable(): T $apiCall
* @return T
*
* @throws RateLimitException
*/
private function executeRequest(callable $apiCall)
{
if (! $this->rateLimiter->canMakeRequest($this->config)) {
$retryAfter = $this->rateLimiter->requestAvailableIn($this->config);
$this->log->warning('[Hubspot] Rate limit exceeded, deferring request', [
'team_id' => $this->config->team_id,
'config_id' => $this->config->getId(),
'retry_after' => $retryAfter,
]);
throw new RateLimitException(
'Hubspot rate limit reached for configuration ' . $this->config->getId(),
$retryAfter,
);
}
$this->rateLimiter->incrementRequestCount($this->config);
try {
return $apiCall();
} catch (Throwable $e) {
if ($this->isHubspotRateLimit($e)) {
$retryAfter = $this->parseRetryAfter($e);
$this->log->warning('[Hubspot] Received 429 from API', [
'team_id' => $this->config->team_id,
'config_id' => $this->config->getId(),
'retry_after' => $retryAfter,
'reason' => $e->getMessage(),
]);
throw new RateLimitException('Hubspot returned 429', $retryAfter, $e);
}
throw $e;
}
}
private function isHubspotRateLimit(Throwable $e): bool
{
return method_exists($e, 'getCode') && (int) $e->getCode() === 429;
}
private function parseRetryAfter(Throwable $e): int
{
if (method_exists($e, 'getResponseHeaders')) {
$headers = $e->getResponseHeaders() ?: [];
$value = $headers['Retry-After'] ?? $headers['retry-after'] ?? null;
if (is_array($value)) {
$value = $value[0] ?? null;
}
if (is_numeric($value)) {
return (int) $value;
}
}
return 10;
}
public function getMinimumApiVersion(): string
{
return self::MIN_API_VERSION;
}
public function getInstance(): Factory
{
return new Factory([
'key' => $this->accessToken,
'oauth2' => true,
'base_url' => $this->baseUrl,
]);
}
public function getNewInstance(): Discovery
{
return \HubSpot\Factory::createWithAccessToken($this->accessToken);
}
/**
* Secondly and daily limits for Hubspot API
*
* Product Tier: Free & Starter | Professional & Enterprise | API add-on (any tier)
* Burst: 100/10 seconds | 150/10 seconds | 200/10 seconds
* Daily: 250,000 | 500,000 | 1,000,000
*
* Official documentation states: The search endpoints are rate limited to five requests per second.
* Since with 5 RPS were still hitting secondly rate limits we lowered it to 4
*/
public function getPaginatedData(array $payload, string $type, int $offset = 0): array
{
$total = 0;
$lastId = null;
$rows = [];
foreach ($this->getPaginatedDataGenerator($payload, $type, $offset, $total, $lastId) as $row) {
$rows[] = $row;
}
return ['results' => $rows, 'total' => $total, 'last_record' => $lastId];
}
/**
* @throws HubspotException
* @throws SocialAccountTokenInvalidException
* @throws BadRequest
*/
public function getPaginatedDataGenerator(
array $payload,
string $type,
int $offset = 0,
int &$total = 0,
?string &$lastRecordId = null
): \Generator {
return $this->paginationService->getPaginatedDataGenerator(
$this,
$payload,
$type,
$offset,
$total,
$lastRecordId
);
}
/**
* @throws DealApiException
* @throws CrmException
*/
public function getOpportunityById(string $crmId, array $fields): array
{
try {
$deal = $this->executeRequest(fn () => $this->getNewInstance()->crm()->deals()->basicApi()->getById(
$crmId,
implode(',', $fields),
'companies,contacts'
));
} catch (DealApiException $e) {
$this->log->info('[Hubspot] Failed to fetch opportunity', [
'crm_id' => $crmId,
'reason' => $e->getMessage(),
]);
throw $e;
}
if (! $deal instanceof DealWithAssociations) {
throw new CrmException('Deal not found');
}
return [
'id' => $deal->getId(),
'properties' => $deal->getProperties(),
'associations' => $deal->getAssociations(),
];
}
/**
* Generic batch read method for HubSpot objects
*
* @param string $objectType The object type ('deals', 'companies', 'contacts')
* @param array<string> $crmIds Array of HubSpot object IDs (max 100)
* @param array<string> $fields Array of property names to fetch
*
* @return array<string, array> Array keyed by CRM ID with object data
*/
private function batchReadObjects(string $objectType, array $crmIds, array $fields): array
{
if (empty($crmIds)) {
return [];
}
$this->validateBatchSize($objectType, $crmIds);
$this->ensureValidToken();
try {
$batchConfig = $this->createBatchConfiguration($objectType);
$batchReadRequest = $this->prepareBatchRequest($batchConfig, $crmIds, $fields);
$response = $batchConfig['api']->read($batchReadRequest);
$this->validateApiResponse($response, $objectType);
$results = $this->processApiResults($response);
$this->logBatchResults($objectType, $crmIds, $results);
return $results;
} catch (\Throwable $e) {
$this->handleBatchError($e, $objectType, $crmIds);
}
}
private function validateBatchSize(string $objectType, array $crmIds): void
{
if (count($crmIds) > 100) {
throw new \InvalidArgumentException("Batch size cannot exceed 100 {$objectType}");
}
}
private function createBatchConfiguration(string $objectType): array
{
$configurations = [
'deals' => [
'batchReadRequest' => new \HubSpot\Client\Crm\Deals\Model\BatchReadInputSimplePublicObjectId(),
'inputClass' => \HubSpot\Client\Crm\Deals\Model\SimplePublicObjectId::class,
'api' => $this->getNewInstance()->crm()->deals()->batchApi(),
],
'companies' => [
'batchReadRequest' => new \HubSpot\Client\Crm\Companies\Model\BatchReadInputSimplePublicObjectId(),
'inputClass' => \HubSpot\Client\Crm\Companies\Model\SimplePublicObjectId::class,
'api' => $this->getNewInstance()->crm()->companies()->batchApi(),
],
'contacts' => [
'batchReadRequest' => new \HubSpot\Client\Crm\Contacts\Model\BatchReadInputSimplePublicObjectId(),
'inputClass' => \HubSpot\Client\Crm\Contacts\Model\SimplePublicObjectId::class,
'api' => $this->getNewInstance()->crm()->contacts()->batchApi(),
],
];
if (! isset($configurations[$objectType])) {
throw new \InvalidArgumentException("Unsupported object type: {$objectType}");
}
return $configurations[$objectType];
}
private function prepareBatchRequest(array $batchConfig, array $crmIds, array $fields): object
{
$batchReadRequest = $batchConfig['batchReadRequest'];
$inputClass = $batchConfig['inputClass'];
$inputs = array_map(function ($crmId) use ($inputClass) {
$input = new $inputClass();
$input->setId($crmId);
return $input;
}, $crmIds);
$batchReadRequest->setInputs($inputs);
$batchReadRequest->setProperties($fields);
return $batchReadRequest;
}
private function validateApiResponse($response, string $objectType): void
{
if (! $response) {
throw new CrmException("HubSpot API returned null response for {$objectType} batch read");
}
}
private function processApiResults($response): array
{
$results = [];
$responseResults = $response->getResults();
if ($responseResults) {
foreach ($responseResults as $object) {
if ($object && $object->getId()) {
$results[$object->getId()] = [
'id' => $object->getId(),
'properties' => $object->getProperties() ?: [],
];
}
}
}
return $results;
}
private function logBatchResults(string $objectType, array $crmIds, array $results): void
{
$this->log->info("[HubSpot] Batch fetched {$objectType}", [
'requested_count' => count($crmIds),
'returned_count' => count($results),
'crm_ids' => $crmIds,
]);
}
private function handleBatchError(\Throwable $e, string $objectType, array $crmIds): void
{
$errorMessage = $e->getMessage() ?: 'Unknown error';
$errorTrace = $e->getTraceAsString() ?: 'No trace available';
$this->log->error("[HubSpot] Failed to batch fetch {$objectType}", [
'crm_ids' => $crmIds,
'error' => $errorMessage,
'trace' => $errorTrace,
]);
throw new CrmException("Failed to batch fetch {$objectType}: " . $errorMessage);
}
/**
* Batch read multiple opportunities by their CRM IDs
*
* @param array<string> $crmIds Array of HubSpot deal IDs (max 100)
* @param array<string> $fields Array of property names to fetch
*
* @return array<string, array> Array keyed by CRM ID with opportunity data
*/
public function getOpportunitiesByIds(array $crmIds, array $fields): array
{
return $this->batchReadObjects('deals', $crmIds, $fields);
}
/**
* Batch read multiple companies by their CRM IDs
*
* @param array<string> $crmIds Array of HubSpot company IDs (max 100)
* @param array<string> $fields Array of property names to fetch
*
* @return array<string, array> Array keyed by CRM ID with company data
*/
public function getCompaniesByIds(array $crmIds, array $fields): array
{
return $this->batchReadObjects('companies', $crmIds, $fields);
}
/**
* Batch read multiple contacts by their CRM IDs
*
* @param array<string> $crmIds Array of HubSpot contact IDs (max 100)
* @param array<string> $fields Array of property names to fetch
*
* @return array<string, array> Array keyed by CRM ID with contact data
*/
public function getContactsByIds(array $crmIds, array $fields): array
{
return $this->batchReadObjects('contacts', $crmIds, $fields);
}
/**
* @throws CompanyApiException
* @throws CrmException
*/
public function getAccountById(string $crmId, array $fields): array
{
try {
$company = $this->getNewInstance()->crm()->companies()->basicApi()->getById(
$crmId,
implode(',', $fields),
);
} catch (CompanyApiException $e) {
$this->log->info('[Hubspot] Failed to fetch account', [
'crm_id' => $crmId,
'reason' => $e->getMessage(),
]);
throw $e;
}
if (! $company instanceof CompaniesWithAssociations) {
throw new CrmException('Account not found');
}
return [
'id' => $company->getId(),
'properties' => $company->getProperties(),
];
}
/**
* @throws ContactApiException
* @throws CrmException
*/
public function getContactById(string $crmId, array $fields): array
{
try {
$contact = $this->getNewInstance()->crm()->contacts()->basicApi()->getById(
$crmId,
implode(',', $fields)
);
} catch (ContactApiException $e) {
$this->log->info('[Hubspot] Failed to fetch contact', [
'crm_id' => $crmId,
'reason' => $e->getMessage(),
]);
throw $e;
}
if (! $contact instanceof ContactsWithAssociations) {
throw new CrmException('Contact not found');
}
return [
'id' => $contact->getId(),
'properties' => $contact->getProperties(),
];
}
/**
* This is email search request that Hubspot offers as GET (more generous quota)
*/
public function getContactByEmail(string $email, array $fields = []): array
{
try {
$contact = $this->getNewInstance()->crm()->contacts()->basicApi()->getById(
$email,
implode(',', $fields),
null,
false,
'email'
);
return [
'id' => $contact->getId(),
'properties' => $contact->getProperties(),
];
} catch (ContactApiException $e) {
$this->log->info('[Hubspot] Failed to fetch contact', [
'email' => $email,
'reason' => $e->getMessage(),
]);
return [];
}
}
/**
* @throws CrmException
*/
public function fetchProperty(string $objectType, string $propertyId): Property
{
$result = $this->getNewInstance()->crm()->properties()->coreApi()->getByName($objectType, $propertyId);
if (! $result instanceof Property) {
$this->log->error('[Hubspot] Failed to fetch property', [
'object_type' => $objectType,
'property_id' => $propertyId,
'reason' => $result->getMessage(),
]);
throw new CrmException('Failed to fetch property');
}
return $result;
}
/**
* @return array<CrmFieldOption>
*/
public function fetchPropertyOptions(string $objectType, string $propertyId): array
{
/** @var array<CrmFieldOption> */
return $this->fetchProperty($objectType, $propertyId)->getOptions();
}
/**
* @return array<array{id:string, label:string, deleted:bool}>
*/
public function fetchCallDispositions(): array
{
/** @var Response $response */
$response = $this->getInstance()->engagements()->getCallDispositions();
/**
* @var array<array{
* id:string,
* label:string,
* deleted: bool
* }>
*/
return $response->toArray();
}
/**
* @return array<CrmFieldOption>
*/
public function fetchOpportunityPipelineStages(): array
{
$stages = [];
$apiResponse = $this->getNewInstance()->crm()->pipelines()->pipelinesApi()->getAll('deals');
if ($apiResponse instanceof Error) {
$this->log->error('[Hubspot] Failed to fetch opportunity pipelines', [
'reason' => $apiResponse->getMessage(),
]);
return [];
}
foreach ($apiResponse->getResults() as $pipeline) {
$pipelineStages = array_map(
static function (PipelineStage $stage) {
return [
'id' => $stage->getId(),
'label' => $stage->getLabel(),
];
},
$pipeline->getStages()
);
$stages = array_merge($stages, $pipelineStages);
}
return $stages;
}
public function fetchOpportunityPipelines(): array
{
$pipelines = [];
try {
$apiResponse = $this->makeRequest('/crm/v3/pipelines/deals');
} catch (\Exception $e) {
$this->log->info('[Hubspot] Failed to fetch opportunity pipelines', [
'reason' => $e->getMessage(),
]);
return [];
}
$response = $apiResponse->toArray();
foreach ($response['results'] as $pipeline) {
$pipelines[] = [
'id' => $pipeline['id'],
'label' => $pipeline['label'],
];
}
return $pipelines;
}
/**
* @return array<CrmFieldOption>
*/
public function fetchMeetingOutcomeFieldOptions(Field $field): array
{
return $field->getCrmProviderId() === 'meetingOutcome'
? $this->fetchMeetingOutcomeTypes()
: $this->fetchCallActivityTypes();
}
public function fetchMeetingOutcomeTypes(): array
{
return $this->extractMeetingTypeOptions(
'[URL_WITH_CREDENTIALS] Response $response */
$response = $this->getInstance()
->getClient()
->request('GET', $endpoint);
/**
* @var array<array{
* value: string,
* label: string,
* displayOrder: int
* }> $optionData
*/
$optionData = $response->toArray()['options'] ?? [];
$options = [];
foreach ($optionData as $item) {
$options[] = [
'id' => $item['value'],
'value' => $item['value'],
'label' => $item['label'],
'display_order' => $item['displayOrder'],
];
}
return $options;
}
/**
* @return array<CrmFieldOption>
*/
public function fetchDispositionFieldOptions(): array
{
$options = [];
$dispositions = $this->fetchCallDispositions();
foreach ($dispositions as $disposition) {
if ($disposition['deleted'] !== false) {
continue;
}
$option['value'] = $disposition['id'];
$option['id'] = $disposition['id'];
$option['label'] = $disposition['label'];
$options[] = $option;
}
return $options;
}
/**
* @return array<CrmFieldOption>
*/
public function fetchOpportunityFieldOptions(Field $field): array
{
if ($field->isStageField()) {
return $this->fetchOpportunityPipelineStages();
}
if ($field->isPipelineField()) {
return $this->fetchOpportunityPipelines();
}
return $this->fetchPropertyOptions('deals', $field->getCrmProviderId());
}
/**
* @throws BadRequest
* @throws HubspotException
*/
public function makeRequest(string $endpoint, $method = 'GET', $payload = [], ?string $queryString = null)
{
$endpoint = self::BASE_URL . $endpoint;
if ($method === 'GET') {
$response = $this->getInstance()->getClient()?->request(
method: $method,
endpoint: $endpoint,
query_string: $queryString
);
} else {
$response = $this->getInstance()->getClient()->request($method, $endpoint, [
'json' => ($payload),
]);
}
$max = $response->getHeaderLine('X-HubSpot-RateLimit-Max'); // "110"
$remaining = $response->getHeaderLine('X-HubSpot-RateLimit-Remaining'); // "109"
$interval = $response->getHeaderLine('X-HubSpot-RateLimit-Interval-Milliseconds'); // "10000"
$body = json_decode((string) $response->getBody(), true);
\Illuminate\Support\Facades\Log::channel('custom_channel')->info('$max ' . PHP_EOL . print_r($max, true));
\Illuminate\Support\Facades\Log::channel('custom_channel')->info('$remaining ' . PHP_EOL . print_r($remaining, true));
\Illuminate\Support\Facades\Log::channel('custom_channel')->info('$interval ' . PHP_EOL . print_r($interval, true));
\Illuminate\Support\Facades\Log::channel('custom_channel')->info('$body ' . PHP_EOL . print_r($body, true));
return $response;
}
/**
* @throws BadRequest
* @throws HubspotException
*/
public function createMeeting(array $payload): Response
{
$endpoint = '/crm/v3/objects/meetings';
return $this->makeRequest($endpoint, 'POST', $payload);
}
/**
* @throws BadRequest
* @throws HubspotException
*/
public function updateMeeting(string $meetingId, array $payload): Response
{
$endpoint = '/crm/v3/objects/meetings/' . $meetingId;
return $this->makeRequest($endpoint, 'PATCH', $payload);
}
/**
* @throws \Exception
*/
public function createNote(
string $body,
string $ownerId,
int $timestamp,
string $objectId,
NoteObject $noteObject
): ?string {
try {
$noteInput = new SimplePublicObjectInput([
'properties' => [
'hs_note_body' => $body,
'hubspot_owner_id' => $ownerId,
'hs_timestamp' => $timestamp,
],
]);
// Create note
$note = $this->getNewInstance()->crm()->objects()->basicApi()->create('note', $noteInput);
$this->getNewInstance()->crm()->objects()->associationsApi()->create(
'note',
$note->getId(),
$this->getNoteObject($noteObject),
$objectId,
$this->getNoteAssociationType($noteObject),
);
return $note->getId();
} catch (\Exception $e) {
$this->log->error('[Hubspot] Failed to create note', [
'objectId' => $objectId,
'noteObject' => $noteObject->getObjectType(),
'reason' => $e->getMessage(),
]);
\Sentry::captureException($e);
}
return null;
}
public function updateEngagement(string $objectId, array $engagement, array $metadata): void
{
$this->getInstance()->engagements()->update($objectId, $engagement, $metadata);
}
public function getEngagementData(string $engagementId): array
{
$engagement = $this->getInstance()->engagements()->get($engagementId);
return $engagement->toArray();
}
public function createEngagement(array $engagement, array $associations, array $metadata): Response
{
return $this->getInstance()
->engagements()
->create($engagement, $associations, $metadata);
}
public function isUnauthorizedException(\Exception $e): bool
{
// Check for specific HubSpot API exception types first
if ($e instanceof BadRequest) {
// BadRequest can contain 401 status codes
return $e->getCode() === 401;
}
// Check for HTTP client exceptions with status codes
if ($e instanceof \GuzzleHttp\Exception\RequestException && $e->hasResponse()) {
$response = $e->getResponse();
if ($response !== null) {
return $response->getStatusCode() === 401;
}
}
// Check for Guzzle HTTP exceptions
if ($e instanceof \GuzzleHttp\Exception\ClientException) {
return $e->getCode() === 401;
}
// Fallback to string matching as last resort, but be more specific
$message = strtolower($e->getMessage());
return str_contains($message, '401 unauthorized') ||
str_contains($message, 'http 401') ||
str_contains($message, 'status code 401') ||
(preg_match('/\b401\b/', $message) && str_contains($message, 'unauthorized'));
}
/**
* Validates and refreshes the access token if needed before API requests.
* This ensures long-running processes don't fail due to token expiration.
*
* @throws SocialAccountTokenInvalidException
*/
public function ensureValidToken(): void
{
if ($this->oauthAccount === null) {
return;
}
$newToken = $this->tokenManager->ensureValidToken($this->oauthAccount);
if ($newToken !== null) {
$this->accessToken = $newToken;
}
}
public function getConfig()
{
return $this->config;
}
// returns only active (archived=false)
public function getOwners(): array
{
return $this->getNewInstance()->crm()->owners()->getAll();
}
/**
* @param bool $archived
*
* @return array<Owner>|[]
*/
public function getOwnersArchived(bool $archived = true): array
{
$endpoint = '/crm/v3/owners';
$queryParams = [
'archived' => $archived ? 'true' : 'false',
];
$queryString = http_build_query($queryParams);
$owners = [];
try {
$response = $this->makeRequest(endpoint: $endpoint, queryString: $queryString);
$responseData = $response?->toArray();
foreach ($responseData['results'] as $result) {
try {
$owners[] = Owner::create($result);
} catch (Throwable $e) {
$this->log->error('[HubSpot] Failed to process owner data', [
'result' => $result,
'error' => $e->getMessage(),
]);
continue;
}
}
} catch (Throwable $e) {
$this->log->error('HubSpot] Failed to fetch owners', [
'archived' => $archived,
'error' => $e->getMessage(),
]);
return [];
}
return $owners;
}
public function getMeeting(string $engagementId): ObjectWithAssociations
{
return $this->getNewInstance()->crm()->objects()->basicApi()
->getById('meeting', $engagementId, null, 'contact,company,deal');
}
public function deleteEngagement(string $engagementId): void
{
$this->getInstance()->engagements()->delete((int) $engagementId);
}
public function getAssociationsData(array $ids, string $fromObject, string $toObject): array
{
$associationData = [];
$idChunks = array_chunk($ids, self::ASSOCIATIONS_BATCH_SIZE_LIMIT);
foreach ($idChunks as $idChunk) {
try {
$batchInput = new \HubSpot\Client\Crm\Associations\Model\BatchInputPublicObjectId();
$batchInput->setInputs(array_map(function ($id) {
$publicObjectId = new \HubSpot\Client\Crm\Associations\Model\PublicObjectId();
$publicObjectId->setId($id);
return $publicObjectId;
}, $idChunk));
$associatedObjectsData = $this
->getNewInstance()
->crm()
->associations()
->batchApi()
->read($fromObject, $toObject, $batchInput);
if ($associatedObjectsData instanceof \HubSpot\Client\Crm\Associations\Model\BatchResponsePublicAssociationMulti) {
foreach ($associatedObjectsData->getResults() as $association) {
$from = $association->getFrom()->getId();
$toAssociations = $association->getTo();
if (! empty($toAssociations)) {
$associationData[$from] = array_map(function ($item) {
return $item->getId();
}, $toAssociations);
}
}
}
} catch (\Exception $e) {
$this->log->error('[Hubspot] Failed to fetch associations', [
'from_object' => $fromObject,
'to_object' => $toObject,
'reason' => $e->getMessage(),
]);
}
}
return $associationData;
}
/**
* @throws \Exception
*/
private function getNoteAssociationType(NoteObject $noteObject): string
{
return match($noteObject) {
NoteObject::Opportunity => 'note_to_deal',
NoteObject::Lead, NoteObject::Contact => 'note_to_contact', // or 'note_to_lead' if your portal supports it
NoteObject::Account => 'note_to_company',
NoteObject::Call, NoteObject::Event => throw new \Exception('Not supported'),
};
}
/**
* @throws \Exception
*/
private function getNoteObject(NoteObject $noteObject): string
{
return match($noteObject) {
NoteObject::Opportunity => 'deal',
NoteObject::Lead, NoteObject::Contact => 'contact',
NoteObject::Account => 'company',
NoteObject::Call, NoteObject::Event => throw new \Exception('Not supported'),
};
}
public function addAssociations(string $objectType, string $associationType, array $payload): Response
{
$endpoint = "/crm/v4/associations/$objectType/$associationType/batch/create";
return $this->makeRequest($endpoint, 'POST', $payload);
}
public function removeAssociations(string $objectType, string $associationType, array $payload): Response
{
$endpoint = "/crm/v4/associations/$objectType/$associationType/batch/archive";
return $this->makeRequest($endpoint, 'POST', $payload);
}
}
Project
Project
New File or Directory…
Expand Selected
Collapse All
Options
Hide...
|
2739
|
NULL
|
NULL
|
NULL
|
|
2742
|
111
|
42
|
2026-05-07T11:38:08.024805+00:00
|
/Users/lukas/.screenpipe/data/data/2026-05-07/1778 /Users/lukas/.screenpipe/data/data/2026-05-07/1778153888024_m1.jpg...
|
PhpStorm
|
faVsco.js – laravel.log
|
True
|
NULL
|
monitor_1
|
NULL
|
NULL
|
NULL
|
NULL
|
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
[2026-05-07 11:36:44] local.INFO: Jiminny\Console\Commands\Command::run Memory usage before starting command {"command":"crm:sync-opportunity","memoryBeforeCommandInMb":60.0,"memoryPeakBeforeCommandInMb":99.727} {"correlation_id":"462baf35-d312-4641-9896-e8b15e8fdb82","trace_id":"aeb070fe-166a-48bf-99c0-acfcbfb40c1b"}
[2026-05-07 11:36:44] local.INFO: [SocialAccountService] Fetching token {"socialAccountId":1499,"provider":"hubspot"} {"correlation_id":"462baf35-d312-4641-9896-e8b15e8fdb82","trace_id":"aeb070fe-166a-48bf-99c0-acfcbfb40c1b"}
[2026-05-07 11:36:44] local.INFO: [SocialAccountService] Token retrieved {"socialAccountId":1499,"provider":"hubspot"} {"correlation_id":"462baf35-d312-4641-9896-e8b15e8fdb82","trace_id":"aeb070fe-166a-48bf-99c0-acfcbfb40c1b"}
[2026-05-07 11:36:44] local.INFO: [EncryptedTokenManager] Generating access token. {"mode":"legacy"} {"correlation_id":"462baf35-d312-4641-9896-e8b15e8fdb82","trace_id":"aeb070fe-166a-48bf-99c0-acfcbfb40c1b"}
[2026-05-07 11:36:44] local.INFO: [HubSpot] Syncing opportunities using strategy: lastModified {"team":2} {"correlation_id":"462baf35-d312-4641-9896-e8b15e8fdb82","trace_id":"aeb070fe-166a-48bf-99c0-acfcbfb40c1b"}
[2026-05-07 11:36:45] local.INFO: [Hubspot] Pagination completed {"team_id":2,"endpoint":"[URL_WITH_CREDENTIALS] CrmFieldOption array{id:string, label:string, value?:string}
*/
class Client extends BaseClient implements HubspotClientInterface
{
public const string MIN_API_VERSION = '2';
public const string BASE_URL = '[URL_WITH_CREDENTIALS] T
* @param callable(): T $apiCall
* @return T
*
* @throws RateLimitException
*/
private function executeRequest(callable $apiCall)
{
if (! $this->rateLimiter->canMakeRequest($this->config)) {
$retryAfter = $this->rateLimiter->requestAvailableIn($this->config);
$this->log->warning('[Hubspot] Rate limit exceeded, deferring request', [
'team_id' => $this->config->team_id,
'config_id' => $this->config->getId(),
'retry_after' => $retryAfter,
]);
throw new RateLimitException(
'Hubspot rate limit reached for configuration ' . $this->config->getId(),
$retryAfter,
);
}
$this->rateLimiter->incrementRequestCount($this->config);
try {
return $apiCall();
} catch (Throwable $e) {
if ($this->isHubspotRateLimit($e)) {
$retryAfter = $this->parseRetryAfter($e);
$this->log->warning('[Hubspot] Received 429 from API', [
'team_id' => $this->config->team_id,
'config_id' => $this->config->getId(),
'retry_after' => $retryAfter,
'reason' => $e->getMessage(),
]);
throw new RateLimitException('Hubspot returned 429', $retryAfter, $e);
}
throw $e;
}
}
private function isHubspotRateLimit(Throwable $e): bool
{
return method_exists($e, 'getCode') && (int) $e->getCode() === 429;
}
private function parseRetryAfter(Throwable $e): int
{
if (method_exists($e, 'getResponseHeaders')) {
$headers = $e->getResponseHeaders() ?: [];
$value = $headers['Retry-After'] ?? $headers['retry-after'] ?? null;
if (is_array($value)) {
$value = $value[0] ?? null;
}
if (is_numeric($value)) {
return (int) $value;
}
}
return 10;
}
public function getMinimumApiVersion(): string
{
return self::MIN_API_VERSION;
}
public function getInstance(): Factory
{
return new Factory([
'key' => $this->accessToken,
'oauth2' => true,
'base_url' => $this->baseUrl,
]);
}
public function getNewInstance(): Discovery
{
return \HubSpot\Factory::createWithAccessToken($this->accessToken);
}
/**
* Secondly and daily limits for Hubspot API
*
* Product Tier: Free & Starter | Professional & Enterprise | API add-on (any tier)
* Burst: 100/10 seconds | 150/10 seconds | 200/10 seconds
* Daily: 250,000 | 500,000 | 1,000,000
*
* Official documentation states: The search endpoints are rate limited to five requests per second.
* Since with 5 RPS were still hitting secondly rate limits we lowered it to 4
*/
public function getPaginatedData(array $payload, string $type, int $offset = 0): array
{
$total = 0;
$lastId = null;
$rows = [];
foreach ($this->getPaginatedDataGenerator($payload, $type, $offset, $total, $lastId) as $row) {
$rows[] = $row;
}
return ['results' => $rows, 'total' => $total, 'last_record' => $lastId];
}
/**
* @throws HubspotException
* @throws SocialAccountTokenInvalidException
* @throws BadRequest
*/
public function getPaginatedDataGenerator(
array $payload,
string $type,
int $offset = 0,
int &$total = 0,
?string &$lastRecordId = null
): \Generator {
return $this->paginationService->getPaginatedDataGenerator(
$this,
$payload,
$type,
$offset,
$total,
$lastRecordId
);
}
/**
* @throws DealApiException
* @throws CrmException
*/
public function getOpportunityById(string $crmId, array $fields): array
{
try {
$deal = $this->executeRequest(fn () => $this->getNewInstance()->crm()->deals()->basicApi()->getById(
$crmId,
implode(',', $fields),
'companies,contacts'
));
} catch (DealApiException $e) {
$this->log->info('[Hubspot] Failed to fetch opportunity', [
'crm_id' => $crmId,
'reason' => $e->getMessage(),
]);
throw $e;
}
if (! $deal instanceof DealWithAssociations) {
throw new CrmException('Deal not found');
}
return [
'id' => $deal->getId(),
'properties' => $deal->getProperties(),
'associations' => $deal->getAssociations(),
];
}
/**
* Generic batch read method for HubSpot objects
*
* @param string $objectType The object type ('deals', 'companies', 'contacts')
* @param array<string> $crmIds Array of HubSpot object IDs (max 100)
* @param array<string> $fields Array of property names to fetch
*
* @return array<string, array> Array keyed by CRM ID with object data
*/
private function batchReadObjects(string $objectType, array $crmIds, array $fields): array
{
if (empty($crmIds)) {
return [];
}
$this->validateBatchSize($objectType, $crmIds);
$this->ensureValidToken();
try {
$batchConfig = $this->createBatchConfiguration($objectType);
$batchReadRequest = $this->prepareBatchRequest($batchConfig, $crmIds, $fields);
$response = $batchConfig['api']->read($batchReadRequest);
$this->validateApiResponse($response, $objectType);
$results = $this->processApiResults($response);
$this->logBatchResults($objectType, $crmIds, $results);
return $results;
} catch (\Throwable $e) {
$this->handleBatchError($e, $objectType, $crmIds);
}
}
private function validateBatchSize(string $objectType, array $crmIds): void
{
if (count($crmIds) > 100) {
throw new \InvalidArgumentException("Batch size cannot exceed 100 {$objectType}");
}
}
private function createBatchConfiguration(string $objectType): array
{
$configurations = [
'deals' => [
'batchReadRequest' => new \HubSpot\Client\Crm\Deals\Model\BatchReadInputSimplePublicObjectId(),
'inputClass' => \HubSpot\Client\Crm\Deals\Model\SimplePublicObjectId::class,
'api' => $this->getNewInstance()->crm()->deals()->batchApi(),
],
'companies' => [
'batchReadRequest' => new \HubSpot\Client\Crm\Companies\Model\BatchReadInputSimplePublicObjectId(),
'inputClass' => \HubSpot\Client\Crm\Companies\Model\SimplePublicObjectId::class,
'api' => $this->getNewInstance()->crm()->companies()->batchApi(),
],
'contacts' => [
'batchReadRequest' => new \HubSpot\Client\Crm\Contacts\Model\BatchReadInputSimplePublicObjectId(),
'inputClass' => \HubSpot\Client\Crm\Contacts\Model\SimplePublicObjectId::class,
'api' => $this->getNewInstance()->crm()->contacts()->batchApi(),
],
];
if (! isset($configurations[$objectType])) {
throw new \InvalidArgumentException("Unsupported object type: {$objectType}");
}
return $configurations[$objectType];
}
private function prepareBatchRequest(array $batchConfig, array $crmIds, array $fields): object
{
$batchReadRequest = $batchConfig['batchReadRequest'];
$inputClass = $batchConfig['inputClass'];
$inputs = array_map(function ($crmId) use ($inputClass) {
$input = new $inputClass();
$input->setId($crmId);
return $input;
}, $crmIds);
$batchReadRequest->setInputs($inputs);
$batchReadRequest->setProperties($fields);
return $batchReadRequest;
}
private function validateApiResponse($response, string $objectType): void
{
if (! $response) {
throw new CrmException("HubSpot API returned null response for {$objectType} batch read");
}
}
private function processApiResults($response): array
{
$results = [];
$responseResults = $response->getResults();
if ($responseResults) {
foreach ($responseResults as $object) {
if ($object && $object->getId()) {
$results[$object->getId()] = [
'id' => $object->getId(),
'properties' => $object->getProperties() ?: [],
];
}
}
}
return $results;
}
private function logBatchResults(string $objectType, array $crmIds, array $results): void
{
$this->log->info("[HubSpot] Batch fetched {$objectType}", [
'requested_count' => count($crmIds),
'returned_count' => count($results),
'crm_ids' => $crmIds,
]);
}
private function handleBatchError(\Throwable $e, string $objectType, array $crmIds): void
{
$errorMessage = $e->getMessage() ?: 'Unknown error';
$errorTrace = $e->getTraceAsString() ?: 'No trace available';
$this->log->error("[HubSpot] Failed to batch fetch {$objectType}", [
'crm_ids' => $crmIds,
'error' => $errorMessage,
'trace' => $errorTrace,
]);
throw new CrmException("Failed to batch fetch {$objectType}: " . $errorMessage);
}
/**
* Batch read multiple opportunities by their CRM IDs
*
* @param array<string> $crmIds Array of HubSpot deal IDs (max 100)
* @param array<string> $fields Array of property names to fetch
*
* @return array<string, array> Array keyed by CRM ID with opportunity data
*/
public function getOpportunitiesByIds(array $crmIds, array $fields): array
{
return $this->batchReadObjects('deals', $crmIds, $fields);
}
/**
* Batch read multiple companies by their CRM IDs
*
* @param array<string> $crmIds Array of HubSpot company IDs (max 100)
* @param array<string> $fields Array of property names to fetch
*
* @return array<string, array> Array keyed by CRM ID with company data
*/
public function getCompaniesByIds(array $crmIds, array $fields): array
{
return $this->batchReadObjects('companies', $crmIds, $fields);
}
/**
* Batch read multiple contacts by their CRM IDs
*
* @param array<string> $crmIds Array of HubSpot contact IDs (max 100)
* @param array<string> $fields Array of property names to fetch
*
* @return array<string, array> Array keyed by CRM ID with contact data
*/
public function getContactsByIds(array $crmIds, array $fields): array
{
return $this->batchReadObjects('contacts', $crmIds, $fields);
}
/**
* @throws CompanyApiException
* @throws CrmException
*/
public function getAccountById(string $crmId, array $fields): array
{
try {
$company = $this->getNewInstance()->crm()->companies()->basicApi()->getById(
$crmId,
implode(',', $fields),
);
} catch (CompanyApiException $e) {
$this->log->info('[Hubspot] Failed to fetch account', [
'crm_id' => $crmId,
'reason' => $e->getMessage(),
]);
throw $e;
}
if (! $company instanceof CompaniesWithAssociations) {
throw new CrmException('Account not found');
}
return [
'id' => $company->getId(),
'properties' => $company->getProperties(),
];
}
/**
* @throws ContactApiException
* @throws CrmException
*/
public function getContactById(string $crmId, array $fields): array
{
try {
$contact = $this->getNewInstance()->crm()->contacts()->basicApi()->getById(
$crmId,
implode(',', $fields)
);
} catch (ContactApiException $e) {
$this->log->info('[Hubspot] Failed to fetch contact', [
'crm_id' => $crmId,
'reason' => $e->getMessage(),
]);
throw $e;
}
if (! $contact instanceof ContactsWithAssociations) {
throw new CrmException('Contact not found');
}
return [
'id' => $contact->getId(),
'properties' => $contact->getProperties(),
];
}
/**
* This is email search request that Hubspot offers as GET (more generous quota)
*/
public function getContactByEmail(string $email, array $fields = []): array
{
try {
$contact = $this->getNewInstance()->crm()->contacts()->basicApi()->getById(
$email,
implode(',', $fields),
null,
false,
'email'
);
return [
'id' => $contact->getId(),
'properties' => $contact->getProperties(),
];
} catch (ContactApiException $e) {
$this->log->info('[Hubspot] Failed to fetch contact', [
'email' => $email,
'reason' => $e->getMessage(),
]);
return [];
}
}
/**
* @throws CrmException
*/
public function fetchProperty(string $objectType, string $propertyId): Property
{
$result = $this->getNewInstance()->crm()->properties()->coreApi()->getByName($objectType, $propertyId);
if (! $result instanceof Property) {
$this->log->error('[Hubspot] Failed to fetch property', [
'object_type' => $objectType,
'property_id' => $propertyId,
'reason' => $result->getMessage(),
]);
throw new CrmException('Failed to fetch property');
}
return $result;
}
/**
* @return array<CrmFieldOption>
*/
public function fetchPropertyOptions(string $objectType, string $propertyId): array
{
/** @var array<CrmFieldOption> */
return $this->fetchProperty($objectType, $propertyId)->getOptions();
}
/**
* @return array<array{id:string, label:string, deleted:bool}>
*/
public function fetchCallDispositions(): array
{
/** @var Response $response */
$response = $this->getInstance()->engagements()->getCallDispositions();
/**
* @var array<array{
* id:string,
* label:string,
* deleted: bool
* }>
*/
return $response->toArray();
}
/**
* @return array<CrmFieldOption>
*/
public function fetchOpportunityPipelineStages(): array
{
$stages = [];
$apiResponse = $this->getNewInstance()->crm()->pipelines()->pipelinesApi()->getAll('deals');
if ($apiResponse instanceof Error) {
$this->log->error('[Hubspot] Failed to fetch opportunity pipelines', [
'reason' => $apiResponse->getMessage(),
]);
return [];
}
foreach ($apiResponse->getResults() as $pipeline) {
$pipelineStages = array_map(
static function (PipelineStage $stage) {
return [
'id' => $stage->getId(),
'label' => $stage->getLabel(),
];
},
$pipeline->getStages()
);
$stages = array_merge($stages, $pipelineStages);
}
return $stages;
}
public function fetchOpportunityPipelines(): array
{
$pipelines = [];
try {
$apiResponse = $this->makeRequest('/crm/v3/pipelines/deals');
} catch (\Exception $e) {
$this->log->info('[Hubspot] Failed to fetch opportunity pipelines', [
'reason' => $e->getMessage(),
]);
return [];
}
$response = $apiResponse->toArray();
foreach ($response['results'] as $pipeline) {
$pipelines[] = [
'id' => $pipeline['id'],
'label' => $pipeline['label'],
];
}
return $pipelines;
}
/**
* @return array<CrmFieldOption>
*/
public function fetchMeetingOutcomeFieldOptions(Field $field): array
{
return $field->getCrmProviderId() === 'meetingOutcome'
? $this->fetchMeetingOutcomeTypes()
: $this->fetchCallActivityTypes();
}
public function fetchMeetingOutcomeTypes(): array
{
return $this->extractMeetingTypeOptions(
'[URL_WITH_CREDENTIALS] Response $response */
$response = $this->getInstance()
->getClient()
->request('GET', $endpoint);
/**
* @var array<array{
* value: string,
* label: string,
* displayOrder: int
* }> $optionData
*/
$optionData = $response->toArray()['options'] ?? [];
$options = [];
foreach ($optionData as $item) {
$options[] = [
'id' => $item['value'],
'value' => $item['value'],
'label' => $item['label'],
'display_order' => $item['displayOrder'],
];
}
return $options;
}
/**
* @return array<CrmFieldOption>
*/
public function fetchDispositionFieldOptions(): array
{
$options = [];
$dispositions = $this->fetchCallDispositions();
foreach ($dispositions as $disposition) {
if ($disposition['deleted'] !== false) {
continue;
}
$option['value'] = $disposition['id'];
$option['id'] = $disposition['id'];
$option['label'] = $disposition['label'];
$options[] = $option;
}
return $options;
}
/**
* @return array<CrmFieldOption>
*/
public function fetchOpportunityFieldOptions(Field $field): array
{
if ($field->isStageField()) {
return $this->fetchOpportunityPipelineStages();
}
if ($field->isPipelineField()) {
return $this->fetchOpportunityPipelines();
}
return $this->fetchPropertyOptions('deals', $field->getCrmProviderId());
}
/**
* @throws BadRequest
* @throws HubspotException
*/
public function makeRequest(string $endpoint, $method = 'GET', $payload = [], ?string $queryString = null)
{
$endpoint = self::BASE_URL . $endpoint;
if ($method === 'GET') {
$response = $this->getInstance()->getClient()?->request(
method: $method,
endpoint: $endpoint,
query_string: $queryString
);
} else {
$response = $this->getInstance()->getClient()->request($method, $endpoint, [
'json' => ($payload),
]);
}
$max = $response->getHeaderLine('X-HubSpot-RateLimit-Max'); // "110"
$remaining = $response->getHeaderLine('X-HubSpot-RateLimit-Remaining'); // "109"
$interval = $response->getHeaderLine('X-HubSpot-RateLimit-Interval-Milliseconds'); // "10000"
$body = json_decode((string) $response->getBody(), true);
\Illuminate\Support\Facades\Log::channel('custom_channel')->info('$max ' . PHP_EOL . print_r($max, true));
\Illuminate\Support\Facades\Log::channel('custom_channel')->info('$remaining ' . PHP_EOL . print_r($remaining, true));
\Illuminate\Support\Facades\Log::channel('custom_channel')->info('$interval ' . PHP_EOL . print_r($interval, true));
\Illuminate\Support\Facades\Log::channel('custom_channel')->info('$body ' . PHP_EOL . print_r($body, true));
return $response;
}
/**
* @throws BadRequest
* @throws HubspotException
*/
public function createMeeting(array $payload): Response
{
$endpoint = '/crm/v3/objects/meetings';
return $this->makeRequest($endpoint, 'POST', $payload);
}
/**
* @throws BadRequest
* @throws HubspotException
*/
public function updateMeeting(string $meetingId, array $payload): Response
{
$endpoint = '/crm/v3/objects/meetings/' . $meetingId;
return $this->makeRequest($endpoint, 'PATCH', $payload);
}
/**
* @throws \Exception
*/
public function createNote(
string $body,
string $ownerId,
int $timestamp,
string $objectId,
NoteObject $noteObject
): ?string {
try {
$noteInput = new SimplePublicObjectInput([
'properties' => [
'hs_note_body' => $body,
'hubspot_owner_id' => $ownerId,
'hs_timestamp' => $timestamp,
],
]);
// Create note
$note = $this->getNewInstance()->crm()->objects()->basicApi()->create('note', $noteInput);
$this->getNewInstance()->crm()->objects()->associationsApi()->create(
'note',
$note->getId(),
$this->getNoteObject($noteObject),
$objectId,
$this->getNoteAssociationType($noteObject),
);
return $note->getId();
} catch (\Exception $e) {
$this->log->error('[Hubspot] Failed to create note', [
'objectId' => $objectId,
'noteObject' => $noteObject->getObjectType(),
'reason' => $e->getMessage(),
]);
\Sentry::captureException($e);
}
return null;
}
public function updateEngagement(string $objectId, array $engagement, array $metadata): void
{
$this->getInstance()->engagements()->update($objectId, $engagement, $metadata);
}
public function getEngagementData(string $engagementId): array
{
$engagement = $this->getInstance()->engagements()->get($engagementId);
return $engagement->toArray();
}
public function createEngagement(array $engagement, array $associations, array $metadata): Response
{
return $this->getInstance()
->engagements()
->create($engagement, $associations, $metadata);
}
public function isUnauthorizedException(\Exception $e): bool
{
// Check for specific HubSpot API exception types first
if ($e instanceof BadRequest) {
// BadRequest can contain 401 status codes
return $e->getCode() === 401;
}
// Check for HTTP client exceptions with status codes
if ($e instanceof \GuzzleHttp\Exception\RequestException && $e->hasResponse()) {
$response = $e->getResponse();
if ($response !== null) {
return $response->getStatusCode() === 401;
}
}
// Check for Guzzle HTTP exceptions
if ($e instanceof \GuzzleHttp\Exception\ClientException) {
return $e->getCode() === 401;
}
// Fallback to string matching as last resort, but be more specific
$message = strtolower($e->getMessage());
return str_contains($message, '401 unauthorized') ||
str_contains($message, 'http 401') ||
str_contains($message, 'status code 401') ||
(preg_match('/\b401\b/', $message) && str_contains($message, 'unauthorized'));
}
/**
* Validates and refreshes the access token if needed before API requests.
* This ensures long-running processes don't fail due to token expiration.
*
* @throws SocialAccountTokenInvalidException
*/
public function ensureValidToken(): void
{
if ($this->oauthAccount === null) {
return;
}
$newToken = $this->tokenManager->ensureValidToken($this->oauthAccount);
if ($newToken !== null) {
$this->accessToken = $newToken;
}
}
public function getConfig()
{
return $this->config;
}
// returns only active (archived=false)
public function getOwners(): array
{
return $this->getNewInstance()->crm()->owners()->getAll();
}
/**
* @param bool $archived
*
* @return array<Owner>|[]
*/
public function getOwnersArchived(bool $archived = true): array
{
$endpoint = '/crm/v3/owners';
$queryParams = [
'archived' => $archived ? 'true' : 'false',
];
$queryString = http_build_query($queryParams);
$owners = [];
try {
$response = $this->makeRequest(endpoint: $endpoint, queryString: $queryString);
$responseData = $response?->toArray();
foreach ($responseData['results'] as $result) {
try {
$owners[] = Owner::create($result);
} catch (Throwable $e) {
$this->log->error('[HubSpot] Failed to process owner data', [
'result' => $result,
'error' => $e->getMessage(),
]);
continue;
}
}
} catch (Throwable $e) {
$this->log->error('HubSpot] Failed to fetch owners', [
'archived' => $archived,
'error' => $e->getMessage(),
]);
return [];
}
return $owners;
}
public function getMeeting(string $engagementId): ObjectWithAssociations
{
return $this->getNewInstance()->crm()->objects()->basicApi()
->getById('meeting', $engagementId, null, 'contact,company,deal');
}
public function deleteEngagement(string $engagementId): void
{
$this->getInstance()->engagements()->delete((int) $engagementId);
}
public function getAssociationsData(array $ids, string $fromObject, string $toObject): array
{
$associationData = [];
$idChunks = array_chunk($ids, self::ASSOCIATIONS_BATCH_SIZE_LIMIT);
foreach ($idChunks as $idChunk) {
try {
$batchInput = new \HubSpot\Client\Crm\Associations\Model\BatchInputPublicObjectId();
$batchInput->setInputs(array_map(function ($id) {
$publicObjectId = new \HubSpot\Client\Crm\Associations\Model\PublicObjectId();
$publicObjectId->setId($id);
return $publicObjectId;
}, $idChunk));
$associatedObjectsData = $this
->getNewInstance()
->crm()
->associations()
->batchApi()
->read($fromObject, $toObject, $batchInput);
if ($associatedObjectsData instanceof \HubSpot\Client\Crm\Associations\Model\BatchResponsePublicAssociationMulti) {
foreach ($associatedObjectsData->getResults() as $association) {
$from = $association->getFrom()->getId();
$toAssociations = $association->getTo();
if (! empty($toAssociations)) {
$associationData[$from] = array_map(function ($item) {
return $item->getId();
}, $toAssociations);
}
}
}
} catch (\Exception $e) {
$this->log->error('[Hubspot] Failed to fetch associations', [
'from_object' => $fromObject,
'to_object' => $toObject,
'reason' => $e->getMessage(),
]);
}
}
return $associationData;
}
/**
* @throws \Exception
*/
private function getNoteAssociationType(NoteObject $noteObject): string
{
return match($noteObject) {
NoteObject::Opportunity => 'note_to_deal',
NoteObject::Lead, NoteObject::Contact => 'note_to_contact', // or 'note_to_lead' if your portal supports it
NoteObject::Account => 'note_to_company',
NoteObject::Call, NoteObject::Event => throw new \Exception('Not supported'),
};
}
/**
* @throws \Exception
*/
private function getNoteObject(NoteObject $noteObject): string
{
return match($noteObject) {
NoteObject::Opportunity => 'deal',
NoteObject::Lead, NoteObject::Contact => 'contact',
NoteObject::Account => 'company',
NoteObject::Call, NoteObject::Event => throw new \Exception('Not supported'),
};
}
public function addAssociations(string $objectType, string $associationType, array $payload): Response
{
$endpoint = "/crm/v4/associations/$objectType/$associationType/batch/create";
return $this->makeRequest($endpoint, 'POST', $payload);
}
public function removeAssociations(string $objectType, string $associationType, array $payload): Response
{
$endpoint = "/crm/v4/associations/$objectType/$associationType/batch/archive";
return $this->makeRequest($endpoint, 'POST', $payload);
}
}
Project
Project
New File or Directory…
Expand Selected
Collapse All
Options
Hide...
|
[{"role":"AXButton","text" [{"role":"AXButton","text":"Project: faVsco.js, menu","depth":5,"on_screen":true,"help_text":"~/jiminny/app","role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"master, menu","depth":5,"on_screen":true,"help_text":"Git Branch: master","role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Start Listening for PHP Debug Connections","depth":5,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"AskJiminnyReportActivityServiceTest","depth":6,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Run 'AskJiminnyReportActivityServiceTest'","depth":6,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Debug 'AskJiminnyReportActivityServiceTest'","depth":6,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"More Actions","depth":6,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"JetBrains AI","depth":5,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Search Everywhere","depth":5,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"IDE and Project Settings","depth":5,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXTextArea","text":"[2026-05-07 11:36:44] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage before starting command {\"command\":\"crm:sync-opportunity\",\"memoryBeforeCommandInMb\":60.0,\"memoryPeakBeforeCommandInMb\":99.727} {\"correlation_id\":\"462baf35-d312-4641-9896-e8b15e8fdb82\",\"trace_id\":\"aeb070fe-166a-48bf-99c0-acfcbfb40c1b\"}\n[2026-05-07 11:36:44] local.INFO: [SocialAccountService] Fetching token {\"socialAccountId\":1499,\"provider\":\"hubspot\"} {\"correlation_id\":\"462baf35-d312-4641-9896-e8b15e8fdb82\",\"trace_id\":\"aeb070fe-166a-48bf-99c0-acfcbfb40c1b\"}\n[2026-05-07 11:36:44] local.INFO: [SocialAccountService] Token retrieved {\"socialAccountId\":1499,\"provider\":\"hubspot\"} {\"correlation_id\":\"462baf35-d312-4641-9896-e8b15e8fdb82\",\"trace_id\":\"aeb070fe-166a-48bf-99c0-acfcbfb40c1b\"}\n[2026-05-07 11:36:44] local.INFO: [EncryptedTokenManager] Generating access token. {\"mode\":\"legacy\"} {\"correlation_id\":\"462baf35-d312-4641-9896-e8b15e8fdb82\",\"trace_id\":\"aeb070fe-166a-48bf-99c0-acfcbfb40c1b\"}\n[2026-05-07 11:36:44] local.INFO: [HubSpot] Syncing opportunities using strategy: lastModified {\"team\":2} {\"correlation_id\":\"462baf35-d312-4641-9896-e8b15e8fdb82\",\"trace_id\":\"aeb070fe-166a-48bf-99c0-acfcbfb40c1b\"}\n[2026-05-07 11:36:45] local.INFO: [Hubspot] Pagination completed {\"team_id\":2,\"endpoint\":\"https://api.hubapi.com/crm/v3/objects/deals/search\",\"total_requests\":1,\"total_records_fetched\":6,\"total_elapsed_seconds\":0.48,\"average_seconds_per_request\":0.48} {\"correlation_id\":\"462baf35-d312-4641-9896-e8b15e8fdb82\",\"trace_id\":\"aeb070fe-166a-48bf-99c0-acfcbfb40c1b\"}\n[2026-05-07 11:36:46] local.INFO: [HubSpot] importOpportunityBatch timing {\"teamId\":2,\"deal_count\":6,\"total_ms\":659,\"company_assoc_api_ms\":276,\"contact_assoc_api_ms\":203,\"prepare_entities_ms\":19,\"prepare_accounts_ms\":3,\"prepare_contacts_ms\":17,\"total_companies\":5,\"missing_companies\":0,\"total_contacts\":23,\"missing_contacts\":0,\"deals_loop_ms\":152,\"avg_deal_ms\":25,\"slow_deals_count\":0,\"slow_deals\":[]} {\"correlation_id\":\"462baf35-d312-4641-9896-e8b15e8fdb82\",\"trace_id\":\"aeb070fe-166a-48bf-99c0-acfcbfb40c1b\"}\n[2026-05-07 11:36:46] local.INFO: [HubSpot] Synced opportunities {\"team\":2,\"strategies\":\"lastModified\",\"sync_count\":5,\"total\":6,\"last_synced_id\":\"297846423\",\"duration_ms\":1161.39} {\"correlation_id\":\"462baf35-d312-4641-9896-e8b15e8fdb82\",\"trace_id\":\"aeb070fe-166a-48bf-99c0-acfcbfb40c1b\"}\n[2026-05-07 11:36:46] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage for command {\"command\":\"crm:sync-opportunity\",\"memoryBeforeCommandInMb\":60.0,\"memoryAfterCommandInMB\":62.0,\"memoryPeakBeforeCommandInMb\":99.727,\"memoryPeakAfterCommandInMB\":99.727} {\"correlation_id\":\"462baf35-d312-4641-9896-e8b15e8fdb82\",\"trace_id\":\"aeb070fe-166a-48bf-99c0-acfcbfb40c1b\"}\n[2026-05-07 11:36:52] local.INFO: [HubSpot Journal Polling] Getting offset from database {\"offset\":\"\",\"jiminny_team_id\":1} {\"correlation_id\":\"8568abe7-be33-417c-aa2f-b1bd53fb4adc\",\"trace_id\":\"582bff07-5ea4-4469-8ca8-efa41b04d46d\"}\n[2026-05-07 11:36:52] local.INFO: [HubSpot Journal API] Fetching latest journal entry {\"url\":\"https://api.hubapi.com/webhooks/v4/journal/latest\"} {\"correlation_id\":\"8568abe7-be33-417c-aa2f-b1bd53fb4adc\",\"trace_id\":\"582bff07-5ea4-4469-8ca8-efa41b04d46d\"}\n[2026-05-07 11:36:52] local.INFO: [HubSpot Journal Polling] No data {\"correlation_id\":\"8568abe7-be33-417c-aa2f-b1bd53fb4adc\",\"trace_id\":\"582bff07-5ea4-4469-8ca8-efa41b04d46d\"}\n[2026-05-07 11:36:52] local.WARNING: [HubSpot Journal Polling] Maximum empty results reached, stopping {\"empty_results\":5,\"max_empty_results\":5} {\"correlation_id\":\"8568abe7-be33-417c-aa2f-b1bd53fb4adc\",\"trace_id\":\"582bff07-5ea4-4469-8ca8-efa41b04d46d\"}\n[2026-05-07 11:36:52] local.WARNING: [HubSpot Journal Polling] Maximum empty results reached, stopping {\"empty_results\":5,\"max_empty_results\":5} {\"correlation_id\":\"8568abe7-be33-417c-aa2f-b1bd53fb4adc\",\"trace_id\":\"582bff07-5ea4-4469-8ca8-efa41b04d46d\"}\n[2026-05-07 11:36:52] local.INFO: [HubSpot Journal Polling] Service ending {\"runtime_seconds\":56,\"total_cycles\":5,\"files_downloaded\":0,\"empty_files\":0,\"other_portal_skipped\":0,\"total_events\":0,\"events_per_file\":0,\"avg_api_ms\":176.5,\"avg_download_ms\":0.0,\"avg_transform_ms\":0.0,\"avg_process_ms\":0.0,\"peak_memory_mb\":99.73} {\"correlation_id\":\"8568abe7-be33-417c-aa2f-b1bd53fb4adc\",\"trace_id\":\"582bff07-5ea4-4469-8ca8-efa41b04d46d\"}\n[2026-05-07 11:36:52] local.INFO: [HubSpot Journal Polling] Released polling lock {\"correlation_id\":\"8568abe7-be33-417c-aa2f-b1bd53fb4adc\",\"trace_id\":\"582bff07-5ea4-4469-8ca8-efa41b04d46d\"}\n[2026-05-07 11:37:05] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage before starting command {\"command\":\"meeting-bot:schedule-bot\",\"memoryBeforeCommandInMb\":60.0,\"memoryPeakBeforeCommandInMb\":99.727} {\"correlation_id\":\"34ae70b9-ee92-4e0c-bb97-3ecc204278be\",\"trace_id\":\"0d02e69d-5e33-4059-938c-d41350a39b72\"}\n[2026-05-07 11:37:05] local.INFO: [ScheduleBotCommand] Number of activities to be captured: 0 {\"correlation_id\":\"34ae70b9-ee92-4e0c-bb97-3ecc204278be\",\"trace_id\":\"0d02e69d-5e33-4059-938c-d41350a39b72\"}\n[2026-05-07 11:37:05] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage for command {\"command\":\"meeting-bot:schedule-bot\",\"memoryBeforeCommandInMb\":60.0,\"memoryAfterCommandInMB\":62.0,\"memoryPeakBeforeCommandInMb\":99.727,\"memoryPeakAfterCommandInMB\":99.727} {\"correlation_id\":\"34ae70b9-ee92-4e0c-bb97-3ecc204278be\",\"trace_id\":\"0d02e69d-5e33-4059-938c-d41350a39b72\"}\n[2026-05-07 11:37:06] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage before starting command {\"command\":\"dialers:monitor-activities\",\"memoryBeforeCommandInMb\":62.0,\"memoryPeakBeforeCommandInMb\":99.727} {\"correlation_id\":\"6052f7ad-dde1-41de-8051-5ba8d54babdd\",\"trace_id\":\"2e216848-ddf9-4f8c-8808-93a09cef8f5f\"}\n[2026-05-07 11:37:06] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage for command {\"command\":\"dialers:monitor-activities\",\"memoryBeforeCommandInMb\":62.0,\"memoryAfterCommandInMB\":62.0,\"memoryPeakBeforeCommandInMb\":99.727,\"memoryPeakAfterCommandInMB\":99.727} {\"correlation_id\":\"6052f7ad-dde1-41de-8051-5ba8d54babdd\",\"trace_id\":\"2e216848-ddf9-4f8c-8808-93a09cef8f5f\"}\n[2026-05-07 11:37:07] local.NOTICE: Monitoring start {\"correlation_id\":\"64decce4-a339-4c1b-8a73-4f612ef2eea2\",\"trace_id\":\"07e59e6c-d681-41f9-ba04-5cab00e9a754\"}\n[2026-05-07 11:37:07] local.NOTICE: Monitoring end {\"correlation_id\":\"64decce4-a339-4c1b-8a73-4f612ef2eea2\",\"trace_id\":\"07e59e6c-d681-41f9-ba04-5cab00e9a754\"}\n[2026-05-07 11:37:09] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage before starting command {\"command\":\"mailbox:skip-lists:refresh\",\"memoryBeforeCommandInMb\":62.0,\"memoryPeakBeforeCommandInMb\":99.727} {\"correlation_id\":\"0326b1c8-69ac-4d09-aae5-e808abc71f46\",\"trace_id\":\"2d16668b-26b0-44cf-a533-493500255667\"}\n[2026-05-07 11:37:09] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage for command {\"command\":\"mailbox:skip-lists:refresh\",\"memoryBeforeCommandInMb\":62.0,\"memoryAfterCommandInMB\":62.0,\"memoryPeakBeforeCommandInMb\":99.727,\"memoryPeakAfterCommandInMB\":99.727} {\"correlation_id\":\"0326b1c8-69ac-4d09-aae5-e808abc71f46\",\"trace_id\":\"2d16668b-26b0-44cf-a533-493500255667\"}\n[2026-05-07 11:37:10] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage before starting command {\"command\":\"mailbox:batch:process\",\"memoryBeforeCommandInMb\":62.0,\"memoryPeakBeforeCommandInMb\":99.727} {\"correlation_id\":\"aa512302-070c-4f99-b3ef-1a43c71b6297\",\"trace_id\":\"9e5c4036-6a42-4c64-a9b3-f11143254293\"}\n[2026-05-07 11:37:10] local.INFO: [EmailSchedule] STARTING batch process {\"host\":\"docker_lamp_1\"} {\"correlation_id\":\"aa512302-070c-4f99-b3ef-1a43c71b6297\",\"trace_id\":\"9e5c4036-6a42-4c64-a9b3-f11143254293\"}\n[2026-05-07 11:37:10] local.INFO: [EmailSchedule] FINISHED batch process {\"host\":\"docker_lamp_1\",\"processed\":0} {\"correlation_id\":\"aa512302-070c-4f99-b3ef-1a43c71b6297\",\"trace_id\":\"9e5c4036-6a42-4c64-a9b3-f11143254293\"}\n[2026-05-07 11:37:10] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage for command {\"command\":\"mailbox:batch:process\",\"memoryBeforeCommandInMb\":62.0,\"memoryAfterCommandInMB\":62.0,\"memoryPeakBeforeCommandInMb\":99.727,\"memoryPeakAfterCommandInMB\":99.727} {\"correlation_id\":\"aa512302-070c-4f99-b3ef-1a43c71b6297\",\"trace_id\":\"9e5c4036-6a42-4c64-a9b3-f11143254293\"}\n[2026-05-07 11:37:13] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage before starting command {\"command\":\"mailbox:batch:create\",\"memoryBeforeCommandInMb\":62.0,\"memoryPeakBeforeCommandInMb\":99.727} {\"correlation_id\":\"b3d4318e-3db2-413c-8fe1-8827ec834020\",\"trace_id\":\"870e454c-3a47-419e-88f1-2321d38b8937\"}\n[2026-05-07 11:37:13] local.INFO: [EmailSchedule] STARTING batch create {\"host\":\"docker_lamp_1\"} {\"correlation_id\":\"b3d4318e-3db2-413c-8fe1-8827ec834020\",\"trace_id\":\"870e454c-3a47-419e-88f1-2321d38b8937\"}\n[2026-05-07 11:37:13] local.INFO: [EmailSchedule] FINISHED batch create {\"host\":\"docker_lamp_1\"} {\"correlation_id\":\"b3d4318e-3db2-413c-8fe1-8827ec834020\",\"trace_id\":\"870e454c-3a47-419e-88f1-2321d38b8937\"}\n[2026-05-07 11:37:13] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage for command {\"command\":\"mailbox:batch:create\",\"memoryBeforeCommandInMb\":62.0,\"memoryAfterCommandInMB\":62.0,\"memoryPeakBeforeCommandInMb\":99.727,\"memoryPeakAfterCommandInMB\":99.727} {\"correlation_id\":\"b3d4318e-3db2-413c-8fe1-8827ec834020\",\"trace_id\":\"870e454c-3a47-419e-88f1-2321d38b8937\"}\n[2026-05-07 11:37:14] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage before starting command {\"command\":\"activity:sync\",\"memoryBeforeCommandInMb\":62.0,\"memoryPeakBeforeCommandInMb\":99.727} {\"correlation_id\":\"3e5f2a44-295d-4c28-b5e9-1a649c1f49ae\",\"trace_id\":\"f4d85ec2-b1d2-4418-bb65-814c209e628f\"}\n[2026-05-07 11:37:15] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage for command {\"command\":\"activity:sync\",\"memoryBeforeCommandInMb\":62.0,\"memoryAfterCommandInMB\":62.0,\"memoryPeakBeforeCommandInMb\":99.727,\"memoryPeakAfterCommandInMB\":99.727} {\"correlation_id\":\"3e5f2a44-295d-4c28-b5e9-1a649c1f49ae\",\"trace_id\":\"f4d85ec2-b1d2-4418-bb65-814c209e628f\"}\n[2026-05-07 11:37:21] local.INFO: [Commands/AsyncUpdateEsEntities] Starting ES update worker {\"pid\":20519,\"workerId\":\"\",\"target\":\"activities\"} {\"correlation_id\":\"1a81e470-75ac-4728-9d45-9f879fc96c5b\",\"trace_id\":\"7184ea88-9f01-42bc-9508-8e4ad0c1204b\"}\n[2026-05-07 11:37:22] local.INFO: [Jiminny\\Jobs\\Mailbox\\CreateBatches] processed 2 inboxes and created 0 batches {\"userId\":null,\"batchSize\":30,\"maxBatches\":1000} {\"correlation_id\":\"798a3e91-233c-4b61-aa8e-80a7b84ac45f\",\"trace_id\":\"870e454c-3a47-419e-88f1-2321d38b8937\"}\n[2026-05-07 11:37:53] local.INFO: [Commands/AsyncUpdateEsEntities] Starting ES update worker {\"pid\":20661,\"workerId\":\"\",\"target\":\"activities\"} {\"correlation_id\":\"293fa4a8-dc12-452e-88a3-f7408f8ed676\",\"trace_id\":\"e3d77ea8-2ff3-4b4e-a388-8f21192e51f3\"}\n[2026-05-07 11:38:03] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage before starting command {\"command\":\"crm:sync-opportunity\",\"memoryBeforeCommandInMb\":62.0,\"memoryPeakBeforeCommandInMb\":99.727} {\"correlation_id\":\"eb6c1736-f06b-4fbc-80f1-e1283f2021fb\",\"trace_id\":\"cef5d248-2fbf-443a-915a-81470cec6a21\"}\n[2026-05-07 11:38:04] local.INFO: [SocialAccountService] Fetching token {\"socialAccountId\":1499,\"provider\":\"hubspot\"} {\"correlation_id\":\"eb6c1736-f06b-4fbc-80f1-e1283f2021fb\",\"trace_id\":\"cef5d248-2fbf-443a-915a-81470cec6a21\"}\n[2026-05-07 11:38:04] local.INFO: [SocialAccountService] Token retrieved {\"socialAccountId\":1499,\"provider\":\"hubspot\"} {\"correlation_id\":\"eb6c1736-f06b-4fbc-80f1-e1283f2021fb\",\"trace_id\":\"cef5d248-2fbf-443a-915a-81470cec6a21\"}\n[2026-05-07 11:38:04] local.INFO: [EncryptedTokenManager] Generating access token. {\"mode\":\"legacy\"} {\"correlation_id\":\"eb6c1736-f06b-4fbc-80f1-e1283f2021fb\",\"trace_id\":\"cef5d248-2fbf-443a-915a-81470cec6a21\"}\n[2026-05-07 11:38:04] local.INFO: [HubSpot] Syncing opportunities using strategy: lastModified {\"team\":2} {\"correlation_id\":\"eb6c1736-f06b-4fbc-80f1-e1283f2021fb\",\"trace_id\":\"cef5d248-2fbf-443a-915a-81470cec6a21\"}\n[2026-05-07 11:38:04] local.INFO: [Hubspot] Pagination completed {\"team_id\":2,\"endpoint\":\"https://api.hubapi.com/crm/v3/objects/deals/search\",\"total_requests\":1,\"total_records_fetched\":6,\"total_elapsed_seconds\":0.56,\"average_seconds_per_request\":0.56} {\"correlation_id\":\"eb6c1736-f06b-4fbc-80f1-e1283f2021fb\",\"trace_id\":\"cef5d248-2fbf-443a-915a-81470cec6a21\"}","depth":4,"on_screen":true,"value":"[2026-05-07 11:36:44] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage before starting command {\"command\":\"crm:sync-opportunity\",\"memoryBeforeCommandInMb\":60.0,\"memoryPeakBeforeCommandInMb\":99.727} {\"correlation_id\":\"462baf35-d312-4641-9896-e8b15e8fdb82\",\"trace_id\":\"aeb070fe-166a-48bf-99c0-acfcbfb40c1b\"}\n[2026-05-07 11:36:44] local.INFO: [SocialAccountService] Fetching token {\"socialAccountId\":1499,\"provider\":\"hubspot\"} {\"correlation_id\":\"462baf35-d312-4641-9896-e8b15e8fdb82\",\"trace_id\":\"aeb070fe-166a-48bf-99c0-acfcbfb40c1b\"}\n[2026-05-07 11:36:44] local.INFO: [SocialAccountService] Token retrieved {\"socialAccountId\":1499,\"provider\":\"hubspot\"} {\"correlation_id\":\"462baf35-d312-4641-9896-e8b15e8fdb82\",\"trace_id\":\"aeb070fe-166a-48bf-99c0-acfcbfb40c1b\"}\n[2026-05-07 11:36:44] local.INFO: [EncryptedTokenManager] Generating access token. {\"mode\":\"legacy\"} {\"correlation_id\":\"462baf35-d312-4641-9896-e8b15e8fdb82\",\"trace_id\":\"aeb070fe-166a-48bf-99c0-acfcbfb40c1b\"}\n[2026-05-07 11:36:44] local.INFO: [HubSpot] Syncing opportunities using strategy: lastModified {\"team\":2} {\"correlation_id\":\"462baf35-d312-4641-9896-e8b15e8fdb82\",\"trace_id\":\"aeb070fe-166a-48bf-99c0-acfcbfb40c1b\"}\n[2026-05-07 11:36:45] local.INFO: [Hubspot] Pagination completed {\"team_id\":2,\"endpoint\":\"https://api.hubapi.com/crm/v3/objects/deals/search\",\"total_requests\":1,\"total_records_fetched\":6,\"total_elapsed_seconds\":0.48,\"average_seconds_per_request\":0.48} {\"correlation_id\":\"462baf35-d312-4641-9896-e8b15e8fdb82\",\"trace_id\":\"aeb070fe-166a-48bf-99c0-acfcbfb40c1b\"}\n[2026-05-07 11:36:46] local.INFO: [HubSpot] importOpportunityBatch timing {\"teamId\":2,\"deal_count\":6,\"total_ms\":659,\"company_assoc_api_ms\":276,\"contact_assoc_api_ms\":203,\"prepare_entities_ms\":19,\"prepare_accounts_ms\":3,\"prepare_contacts_ms\":17,\"total_companies\":5,\"missing_companies\":0,\"total_contacts\":23,\"missing_contacts\":0,\"deals_loop_ms\":152,\"avg_deal_ms\":25,\"slow_deals_count\":0,\"slow_deals\":[]} {\"correlation_id\":\"462baf35-d312-4641-9896-e8b15e8fdb82\",\"trace_id\":\"aeb070fe-166a-48bf-99c0-acfcbfb40c1b\"}\n[2026-05-07 11:36:46] local.INFO: [HubSpot] Synced opportunities {\"team\":2,\"strategies\":\"lastModified\",\"sync_count\":5,\"total\":6,\"last_synced_id\":\"297846423\",\"duration_ms\":1161.39} {\"correlation_id\":\"462baf35-d312-4641-9896-e8b15e8fdb82\",\"trace_id\":\"aeb070fe-166a-48bf-99c0-acfcbfb40c1b\"}\n[2026-05-07 11:36:46] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage for command {\"command\":\"crm:sync-opportunity\",\"memoryBeforeCommandInMb\":60.0,\"memoryAfterCommandInMB\":62.0,\"memoryPeakBeforeCommandInMb\":99.727,\"memoryPeakAfterCommandInMB\":99.727} {\"correlation_id\":\"462baf35-d312-4641-9896-e8b15e8fdb82\",\"trace_id\":\"aeb070fe-166a-48bf-99c0-acfcbfb40c1b\"}\n[2026-05-07 11:36:52] local.INFO: [HubSpot Journal Polling] Getting offset from database {\"offset\":\"\",\"jiminny_team_id\":1} {\"correlation_id\":\"8568abe7-be33-417c-aa2f-b1bd53fb4adc\",\"trace_id\":\"582bff07-5ea4-4469-8ca8-efa41b04d46d\"}\n[2026-05-07 11:36:52] local.INFO: [HubSpot Journal API] Fetching latest journal entry {\"url\":\"https://api.hubapi.com/webhooks/v4/journal/latest\"} {\"correlation_id\":\"8568abe7-be33-417c-aa2f-b1bd53fb4adc\",\"trace_id\":\"582bff07-5ea4-4469-8ca8-efa41b04d46d\"}\n[2026-05-07 11:36:52] local.INFO: [HubSpot Journal Polling] No data {\"correlation_id\":\"8568abe7-be33-417c-aa2f-b1bd53fb4adc\",\"trace_id\":\"582bff07-5ea4-4469-8ca8-efa41b04d46d\"}\n[2026-05-07 11:36:52] local.WARNING: [HubSpot Journal Polling] Maximum empty results reached, stopping {\"empty_results\":5,\"max_empty_results\":5} {\"correlation_id\":\"8568abe7-be33-417c-aa2f-b1bd53fb4adc\",\"trace_id\":\"582bff07-5ea4-4469-8ca8-efa41b04d46d\"}\n[2026-05-07 11:36:52] local.WARNING: [HubSpot Journal Polling] Maximum empty results reached, stopping {\"empty_results\":5,\"max_empty_results\":5} {\"correlation_id\":\"8568abe7-be33-417c-aa2f-b1bd53fb4adc\",\"trace_id\":\"582bff07-5ea4-4469-8ca8-efa41b04d46d\"}\n[2026-05-07 11:36:52] local.INFO: [HubSpot Journal Polling] Service ending {\"runtime_seconds\":56,\"total_cycles\":5,\"files_downloaded\":0,\"empty_files\":0,\"other_portal_skipped\":0,\"total_events\":0,\"events_per_file\":0,\"avg_api_ms\":176.5,\"avg_download_ms\":0.0,\"avg_transform_ms\":0.0,\"avg_process_ms\":0.0,\"peak_memory_mb\":99.73} {\"correlation_id\":\"8568abe7-be33-417c-aa2f-b1bd53fb4adc\",\"trace_id\":\"582bff07-5ea4-4469-8ca8-efa41b04d46d\"}\n[2026-05-07 11:36:52] local.INFO: [HubSpot Journal Polling] Released polling lock {\"correlation_id\":\"8568abe7-be33-417c-aa2f-b1bd53fb4adc\",\"trace_id\":\"582bff07-5ea4-4469-8ca8-efa41b04d46d\"}\n[2026-05-07 11:37:05] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage before starting command {\"command\":\"meeting-bot:schedule-bot\",\"memoryBeforeCommandInMb\":60.0,\"memoryPeakBeforeCommandInMb\":99.727} {\"correlation_id\":\"34ae70b9-ee92-4e0c-bb97-3ecc204278be\",\"trace_id\":\"0d02e69d-5e33-4059-938c-d41350a39b72\"}\n[2026-05-07 11:37:05] local.INFO: [ScheduleBotCommand] Number of activities to be captured: 0 {\"correlation_id\":\"34ae70b9-ee92-4e0c-bb97-3ecc204278be\",\"trace_id\":\"0d02e69d-5e33-4059-938c-d41350a39b72\"}\n[2026-05-07 11:37:05] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage for command {\"command\":\"meeting-bot:schedule-bot\",\"memoryBeforeCommandInMb\":60.0,\"memoryAfterCommandInMB\":62.0,\"memoryPeakBeforeCommandInMb\":99.727,\"memoryPeakAfterCommandInMB\":99.727} {\"correlation_id\":\"34ae70b9-ee92-4e0c-bb97-3ecc204278be\",\"trace_id\":\"0d02e69d-5e33-4059-938c-d41350a39b72\"}\n[2026-05-07 11:37:06] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage before starting command {\"command\":\"dialers:monitor-activities\",\"memoryBeforeCommandInMb\":62.0,\"memoryPeakBeforeCommandInMb\":99.727} {\"correlation_id\":\"6052f7ad-dde1-41de-8051-5ba8d54babdd\",\"trace_id\":\"2e216848-ddf9-4f8c-8808-93a09cef8f5f\"}\n[2026-05-07 11:37:06] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage for command {\"command\":\"dialers:monitor-activities\",\"memoryBeforeCommandInMb\":62.0,\"memoryAfterCommandInMB\":62.0,\"memoryPeakBeforeCommandInMb\":99.727,\"memoryPeakAfterCommandInMB\":99.727} {\"correlation_id\":\"6052f7ad-dde1-41de-8051-5ba8d54babdd\",\"trace_id\":\"2e216848-ddf9-4f8c-8808-93a09cef8f5f\"}\n[2026-05-07 11:37:07] local.NOTICE: Monitoring start {\"correlation_id\":\"64decce4-a339-4c1b-8a73-4f612ef2eea2\",\"trace_id\":\"07e59e6c-d681-41f9-ba04-5cab00e9a754\"}\n[2026-05-07 11:37:07] local.NOTICE: Monitoring end {\"correlation_id\":\"64decce4-a339-4c1b-8a73-4f612ef2eea2\",\"trace_id\":\"07e59e6c-d681-41f9-ba04-5cab00e9a754\"}\n[2026-05-07 11:37:09] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage before starting command {\"command\":\"mailbox:skip-lists:refresh\",\"memoryBeforeCommandInMb\":62.0,\"memoryPeakBeforeCommandInMb\":99.727} {\"correlation_id\":\"0326b1c8-69ac-4d09-aae5-e808abc71f46\",\"trace_id\":\"2d16668b-26b0-44cf-a533-493500255667\"}\n[2026-05-07 11:37:09] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage for command {\"command\":\"mailbox:skip-lists:refresh\",\"memoryBeforeCommandInMb\":62.0,\"memoryAfterCommandInMB\":62.0,\"memoryPeakBeforeCommandInMb\":99.727,\"memoryPeakAfterCommandInMB\":99.727} {\"correlation_id\":\"0326b1c8-69ac-4d09-aae5-e808abc71f46\",\"trace_id\":\"2d16668b-26b0-44cf-a533-493500255667\"}\n[2026-05-07 11:37:10] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage before starting command {\"command\":\"mailbox:batch:process\",\"memoryBeforeCommandInMb\":62.0,\"memoryPeakBeforeCommandInMb\":99.727} {\"correlation_id\":\"aa512302-070c-4f99-b3ef-1a43c71b6297\",\"trace_id\":\"9e5c4036-6a42-4c64-a9b3-f11143254293\"}\n[2026-05-07 11:37:10] local.INFO: [EmailSchedule] STARTING batch process {\"host\":\"docker_lamp_1\"} {\"correlation_id\":\"aa512302-070c-4f99-b3ef-1a43c71b6297\",\"trace_id\":\"9e5c4036-6a42-4c64-a9b3-f11143254293\"}\n[2026-05-07 11:37:10] local.INFO: [EmailSchedule] FINISHED batch process {\"host\":\"docker_lamp_1\",\"processed\":0} {\"correlation_id\":\"aa512302-070c-4f99-b3ef-1a43c71b6297\",\"trace_id\":\"9e5c4036-6a42-4c64-a9b3-f11143254293\"}\n[2026-05-07 11:37:10] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage for command {\"command\":\"mailbox:batch:process\",\"memoryBeforeCommandInMb\":62.0,\"memoryAfterCommandInMB\":62.0,\"memoryPeakBeforeCommandInMb\":99.727,\"memoryPeakAfterCommandInMB\":99.727} {\"correlation_id\":\"aa512302-070c-4f99-b3ef-1a43c71b6297\",\"trace_id\":\"9e5c4036-6a42-4c64-a9b3-f11143254293\"}\n[2026-05-07 11:37:13] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage before starting command {\"command\":\"mailbox:batch:create\",\"memoryBeforeCommandInMb\":62.0,\"memoryPeakBeforeCommandInMb\":99.727} {\"correlation_id\":\"b3d4318e-3db2-413c-8fe1-8827ec834020\",\"trace_id\":\"870e454c-3a47-419e-88f1-2321d38b8937\"}\n[2026-05-07 11:37:13] local.INFO: [EmailSchedule] STARTING batch create {\"host\":\"docker_lamp_1\"} {\"correlation_id\":\"b3d4318e-3db2-413c-8fe1-8827ec834020\",\"trace_id\":\"870e454c-3a47-419e-88f1-2321d38b8937\"}\n[2026-05-07 11:37:13] local.INFO: [EmailSchedule] FINISHED batch create {\"host\":\"docker_lamp_1\"} {\"correlation_id\":\"b3d4318e-3db2-413c-8fe1-8827ec834020\",\"trace_id\":\"870e454c-3a47-419e-88f1-2321d38b8937\"}\n[2026-05-07 11:37:13] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage for command {\"command\":\"mailbox:batch:create\",\"memoryBeforeCommandInMb\":62.0,\"memoryAfterCommandInMB\":62.0,\"memoryPeakBeforeCommandInMb\":99.727,\"memoryPeakAfterCommandInMB\":99.727} {\"correlation_id\":\"b3d4318e-3db2-413c-8fe1-8827ec834020\",\"trace_id\":\"870e454c-3a47-419e-88f1-2321d38b8937\"}\n[2026-05-07 11:37:14] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage before starting command {\"command\":\"activity:sync\",\"memoryBeforeCommandInMb\":62.0,\"memoryPeakBeforeCommandInMb\":99.727} {\"correlation_id\":\"3e5f2a44-295d-4c28-b5e9-1a649c1f49ae\",\"trace_id\":\"f4d85ec2-b1d2-4418-bb65-814c209e628f\"}\n[2026-05-07 11:37:15] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage for command {\"command\":\"activity:sync\",\"memoryBeforeCommandInMb\":62.0,\"memoryAfterCommandInMB\":62.0,\"memoryPeakBeforeCommandInMb\":99.727,\"memoryPeakAfterCommandInMB\":99.727} {\"correlation_id\":\"3e5f2a44-295d-4c28-b5e9-1a649c1f49ae\",\"trace_id\":\"f4d85ec2-b1d2-4418-bb65-814c209e628f\"}\n[2026-05-07 11:37:21] local.INFO: [Commands/AsyncUpdateEsEntities] Starting ES update worker {\"pid\":20519,\"workerId\":\"\",\"target\":\"activities\"} {\"correlation_id\":\"1a81e470-75ac-4728-9d45-9f879fc96c5b\",\"trace_id\":\"7184ea88-9f01-42bc-9508-8e4ad0c1204b\"}\n[2026-05-07 11:37:22] local.INFO: [Jiminny\\Jobs\\Mailbox\\CreateBatches] processed 2 inboxes and created 0 batches {\"userId\":null,\"batchSize\":30,\"maxBatches\":1000} {\"correlation_id\":\"798a3e91-233c-4b61-aa8e-80a7b84ac45f\",\"trace_id\":\"870e454c-3a47-419e-88f1-2321d38b8937\"}\n[2026-05-07 11:37:53] local.INFO: [Commands/AsyncUpdateEsEntities] Starting ES update worker {\"pid\":20661,\"workerId\":\"\",\"target\":\"activities\"} {\"correlation_id\":\"293fa4a8-dc12-452e-88a3-f7408f8ed676\",\"trace_id\":\"e3d77ea8-2ff3-4b4e-a388-8f21192e51f3\"}\n[2026-05-07 11:38:03] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage before starting command {\"command\":\"crm:sync-opportunity\",\"memoryBeforeCommandInMb\":62.0,\"memoryPeakBeforeCommandInMb\":99.727} {\"correlation_id\":\"eb6c1736-f06b-4fbc-80f1-e1283f2021fb\",\"trace_id\":\"cef5d248-2fbf-443a-915a-81470cec6a21\"}\n[2026-05-07 11:38:04] local.INFO: [SocialAccountService] Fetching token {\"socialAccountId\":1499,\"provider\":\"hubspot\"} {\"correlation_id\":\"eb6c1736-f06b-4fbc-80f1-e1283f2021fb\",\"trace_id\":\"cef5d248-2fbf-443a-915a-81470cec6a21\"}\n[2026-05-07 11:38:04] local.INFO: [SocialAccountService] Token retrieved {\"socialAccountId\":1499,\"provider\":\"hubspot\"} {\"correlation_id\":\"eb6c1736-f06b-4fbc-80f1-e1283f2021fb\",\"trace_id\":\"cef5d248-2fbf-443a-915a-81470cec6a21\"}\n[2026-05-07 11:38:04] local.INFO: [EncryptedTokenManager] Generating access token. {\"mode\":\"legacy\"} {\"correlation_id\":\"eb6c1736-f06b-4fbc-80f1-e1283f2021fb\",\"trace_id\":\"cef5d248-2fbf-443a-915a-81470cec6a21\"}\n[2026-05-07 11:38:04] local.INFO: [HubSpot] Syncing opportunities using strategy: lastModified {\"team\":2} {\"correlation_id\":\"eb6c1736-f06b-4fbc-80f1-e1283f2021fb\",\"trace_id\":\"cef5d248-2fbf-443a-915a-81470cec6a21\"}\n[2026-05-07 11:38:04] local.INFO: [Hubspot] Pagination completed {\"team_id\":2,\"endpoint\":\"https://api.hubapi.com/crm/v3/objects/deals/search\",\"total_requests\":1,\"total_records_fetched\":6,\"total_elapsed_seconds\":0.56,\"average_seconds_per_request\":0.56} {\"correlation_id\":\"eb6c1736-f06b-4fbc-80f1-e1283f2021fb\",\"trace_id\":\"cef5d248-2fbf-443a-915a-81470cec6a21\"}","role_description":"text entry area","is_enabled":true,"is_focused":true,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Sync Changes","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.018055556,"height":0.026666667},"on_screen":false,"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Hide This Notification","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.018055556,"height":0.026666667},"on_screen":false,"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Code changed:","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.088194445,"height":0.027777778},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Hide","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.018055556,"height":0.026666667},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"2","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.016666668,"height":0.02111111},"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"60","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.021527778,"height":0.02111111},"on_screen":false,"role_description":"text"},{"role":"AXButton","text":"Previous Highlighted Error","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.015277778,"height":0.025555555},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Next Highlighted Error","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.014583333,"height":0.025555555},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXTextArea","text":"<?php\n\ndeclare(strict_types=1);\n\nnamespace Jiminny\\Services\\Crm\\Hubspot;\n\nuse HubSpot\\Client\\Crm\\Deals\\ApiException as DealApiException;\nuse HubSpot\\Client\\Crm\\Contacts\\ApiException as ContactApiException;\nuse HubSpot\\Client\\Crm\\Companies\\ApiException as CompanyApiException;\nuse HubSpot\\Client\\Crm\\Contacts\\Model\\SimplePublicObjectWithAssociations as ContactsWithAssociations;\nuse HubSpot\\Client\\Crm\\Companies\\Model\\SimplePublicObjectWithAssociations as CompaniesWithAssociations;\nuse HubSpot\\Client\\Crm\\Deals\\Model\\SimplePublicObjectWithAssociations as DealWithAssociations;\nuse HubSpot\\Client\\Crm\\Objects\\Model\\SimplePublicObjectInput;\nuse HubSpot\\Client\\Crm\\Objects\\Model\\SimplePublicObjectWithAssociations as ObjectWithAssociations;\nuse HubSpot\\Client\\Crm\\Pipelines\\Model\\Error;\nuse HubSpot\\Client\\Crm\\Pipelines\\Model\\PipelineStage;\nuse HubSpot\\Client\\Crm\\Properties\\Model\\Property;\nuse HubSpot\\Discovery\\Discovery;\nuse Jiminny\\Component\\Utility\\Service\\ProviderRateLimiter;\nuse Jiminny\\Exceptions\\CrmException;\nuse Jiminny\\Exceptions\\RateLimitException;\nuse Jiminny\\Exceptions\\SocialAccountTokenInvalidException;\nuse Jiminny\\Jobs\\Crm\\NoteObject;\nuse Jiminny\\Models\\Crm\\Field;\nuse Jiminny\\Services\\Crm\\BaseClient;\nuse Jiminny\\Services\\Crm\\Hubspot\\DTO\\Response\\Owner;\nuse Jiminny\\Services\\SocialAccountService;\nuse SevenShores\\Hubspot\\Exceptions\\BadRequest;\nuse SevenShores\\Hubspot\\Exceptions\\HubspotException;\nuse SevenShores\\Hubspot\\Factory;\nuse SevenShores\\Hubspot\\Http\\Response;\nuse Jiminny\\Services\\Crm\\Hubspot\\Pagination\\HubspotPaginationService;\nuse Throwable;\n\n/**\n * @phpstan-type CrmFieldOption array{id:string, label:string, value?:string}\n */\nclass Client extends BaseClient implements HubspotClientInterface\n{\n public const string MIN_API_VERSION = '2';\n\n public const string BASE_URL = 'https://api.hubapi.com';\n\n public const int ASSOCIATIONS_BATCH_SIZE_LIMIT = 1000;\n\n private HubspotPaginationService $paginationService;\n private HubspotTokenManager $tokenManager;\n private ProviderRateLimiter $rateLimiter;\n\n public function __construct(\n SocialAccountService $socialAccountService,\n HubspotPaginationService $paginationService,\n HubspotTokenManager $tokenManager,\n ProviderRateLimiter $rateLimiter,\n ) {\n parent::__construct($socialAccountService);\n $this->paginationService = $paginationService;\n $this->tokenManager = $tokenManager;\n $this->rateLimiter = $rateLimiter;\n\n $this->setBaseUrl(self::BASE_URL);\n $this->setVersion(self::MIN_API_VERSION);\n }\n\n /**\n * Single entry point for every HubSpot API call. Enforces the per-portal\n * rate limit configured in the rate_limits table (morphed to the current\n * Configuration) and reacts to a real 429 from HubSpot by translating it\n * into a RateLimitException carrying Retry-After.\n *\n * Wrap any outbound HubSpot call (SDK or raw HTTP) like:\n *\n * $this->executeRequest(fn () => $this->getNewInstance()->crm()->...);\n *\n * @template T\n * @param callable(): T $apiCall\n * @return T\n *\n * @throws RateLimitException\n */\n private function executeRequest(callable $apiCall)\n {\n if (! $this->rateLimiter->canMakeRequest($this->config)) {\n $retryAfter = $this->rateLimiter->requestAvailableIn($this->config);\n\n $this->log->warning('[Hubspot] Rate limit exceeded, deferring request', [\n 'team_id' => $this->config->team_id,\n 'config_id' => $this->config->getId(),\n 'retry_after' => $retryAfter,\n ]);\n\n throw new RateLimitException(\n 'Hubspot rate limit reached for configuration ' . $this->config->getId(),\n $retryAfter,\n );\n }\n\n $this->rateLimiter->incrementRequestCount($this->config);\n\n try {\n return $apiCall();\n } catch (Throwable $e) {\n if ($this->isHubspotRateLimit($e)) {\n $retryAfter = $this->parseRetryAfter($e);\n\n $this->log->warning('[Hubspot] Received 429 from API', [\n 'team_id' => $this->config->team_id,\n 'config_id' => $this->config->getId(),\n 'retry_after' => $retryAfter,\n 'reason' => $e->getMessage(),\n ]);\n\n throw new RateLimitException('Hubspot returned 429', $retryAfter, $e);\n }\n\n throw $e;\n }\n }\n\n private function isHubspotRateLimit(Throwable $e): bool\n {\n return method_exists($e, 'getCode') && (int) $e->getCode() === 429;\n }\n\n private function parseRetryAfter(Throwable $e): int\n {\n if (method_exists($e, 'getResponseHeaders')) {\n $headers = $e->getResponseHeaders() ?: [];\n $value = $headers['Retry-After'] ?? $headers['retry-after'] ?? null;\n if (is_array($value)) {\n $value = $value[0] ?? null;\n }\n if (is_numeric($value)) {\n return (int) $value;\n }\n }\n\n return 10;\n }\n\n public function getMinimumApiVersion(): string\n {\n return self::MIN_API_VERSION;\n }\n\n public function getInstance(): Factory\n {\n return new Factory([\n 'key' => $this->accessToken,\n 'oauth2' => true,\n 'base_url' => $this->baseUrl,\n ]);\n }\n\n public function getNewInstance(): Discovery\n {\n return \\HubSpot\\Factory::createWithAccessToken($this->accessToken);\n }\n\n /**\n * Secondly and daily limits for Hubspot API\n *\n * Product Tier: Free & Starter | Professional & Enterprise | API add-on (any tier)\n * Burst: 100/10 seconds | 150/10 seconds | 200/10 seconds\n * Daily: 250,000 | 500,000 | 1,000,000\n *\n * Official documentation states: The search endpoints are rate limited to five requests per second.\n * Since with 5 RPS were still hitting secondly rate limits we lowered it to 4\n */\n public function getPaginatedData(array $payload, string $type, int $offset = 0): array\n {\n $total = 0;\n $lastId = null;\n $rows = [];\n foreach ($this->getPaginatedDataGenerator($payload, $type, $offset, $total, $lastId) as $row) {\n $rows[] = $row;\n }\n\n return ['results' => $rows, 'total' => $total, 'last_record' => $lastId];\n }\n\n /**\n * @throws HubspotException\n * @throws SocialAccountTokenInvalidException\n * @throws BadRequest\n */\n public function getPaginatedDataGenerator(\n array $payload,\n string $type,\n int $offset = 0,\n int &$total = 0,\n ?string &$lastRecordId = null\n ): \\Generator {\n return $this->paginationService->getPaginatedDataGenerator(\n $this,\n $payload,\n $type,\n $offset,\n $total,\n $lastRecordId\n );\n }\n\n /**\n * @throws DealApiException\n * @throws CrmException\n */\n public function getOpportunityById(string $crmId, array $fields): array\n {\n try {\n $deal = $this->executeRequest(fn () => $this->getNewInstance()->crm()->deals()->basicApi()->getById(\n $crmId,\n implode(',', $fields),\n 'companies,contacts'\n ));\n } catch (DealApiException $e) {\n $this->log->info('[Hubspot] Failed to fetch opportunity', [\n 'crm_id' => $crmId,\n 'reason' => $e->getMessage(),\n ]);\n\n throw $e;\n }\n\n if (! $deal instanceof DealWithAssociations) {\n throw new CrmException('Deal not found');\n }\n\n return [\n 'id' => $deal->getId(),\n 'properties' => $deal->getProperties(),\n 'associations' => $deal->getAssociations(),\n ];\n }\n\n /**\n * Generic batch read method for HubSpot objects\n *\n * @param string $objectType The object type ('deals', 'companies', 'contacts')\n * @param array<string> $crmIds Array of HubSpot object IDs (max 100)\n * @param array<string> $fields Array of property names to fetch\n *\n * @return array<string, array> Array keyed by CRM ID with object data\n */\n private function batchReadObjects(string $objectType, array $crmIds, array $fields): array\n {\n if (empty($crmIds)) {\n return [];\n }\n\n $this->validateBatchSize($objectType, $crmIds);\n $this->ensureValidToken();\n\n try {\n $batchConfig = $this->createBatchConfiguration($objectType);\n $batchReadRequest = $this->prepareBatchRequest($batchConfig, $crmIds, $fields);\n $response = $batchConfig['api']->read($batchReadRequest);\n\n $this->validateApiResponse($response, $objectType);\n\n $results = $this->processApiResults($response);\n $this->logBatchResults($objectType, $crmIds, $results);\n\n return $results;\n } catch (\\Throwable $e) {\n $this->handleBatchError($e, $objectType, $crmIds);\n }\n }\n\n private function validateBatchSize(string $objectType, array $crmIds): void\n {\n if (count($crmIds) > 100) {\n throw new \\InvalidArgumentException(\"Batch size cannot exceed 100 {$objectType}\");\n }\n }\n\n private function createBatchConfiguration(string $objectType): array\n {\n $configurations = [\n 'deals' => [\n 'batchReadRequest' => new \\HubSpot\\Client\\Crm\\Deals\\Model\\BatchReadInputSimplePublicObjectId(),\n 'inputClass' => \\HubSpot\\Client\\Crm\\Deals\\Model\\SimplePublicObjectId::class,\n 'api' => $this->getNewInstance()->crm()->deals()->batchApi(),\n ],\n 'companies' => [\n 'batchReadRequest' => new \\HubSpot\\Client\\Crm\\Companies\\Model\\BatchReadInputSimplePublicObjectId(),\n 'inputClass' => \\HubSpot\\Client\\Crm\\Companies\\Model\\SimplePublicObjectId::class,\n 'api' => $this->getNewInstance()->crm()->companies()->batchApi(),\n ],\n 'contacts' => [\n 'batchReadRequest' => new \\HubSpot\\Client\\Crm\\Contacts\\Model\\BatchReadInputSimplePublicObjectId(),\n 'inputClass' => \\HubSpot\\Client\\Crm\\Contacts\\Model\\SimplePublicObjectId::class,\n 'api' => $this->getNewInstance()->crm()->contacts()->batchApi(),\n ],\n ];\n\n if (! isset($configurations[$objectType])) {\n throw new \\InvalidArgumentException(\"Unsupported object type: {$objectType}\");\n }\n\n return $configurations[$objectType];\n }\n\n private function prepareBatchRequest(array $batchConfig, array $crmIds, array $fields): object\n {\n $batchReadRequest = $batchConfig['batchReadRequest'];\n $inputClass = $batchConfig['inputClass'];\n\n $inputs = array_map(function ($crmId) use ($inputClass) {\n $input = new $inputClass();\n $input->setId($crmId);\n\n return $input;\n }, $crmIds);\n\n $batchReadRequest->setInputs($inputs);\n $batchReadRequest->setProperties($fields);\n\n return $batchReadRequest;\n }\n\n private function validateApiResponse($response, string $objectType): void\n {\n if (! $response) {\n throw new CrmException(\"HubSpot API returned null response for {$objectType} batch read\");\n }\n }\n\n private function processApiResults($response): array\n {\n $results = [];\n $responseResults = $response->getResults();\n\n if ($responseResults) {\n foreach ($responseResults as $object) {\n if ($object && $object->getId()) {\n $results[$object->getId()] = [\n 'id' => $object->getId(),\n 'properties' => $object->getProperties() ?: [],\n ];\n }\n }\n }\n\n return $results;\n }\n\n private function logBatchResults(string $objectType, array $crmIds, array $results): void\n {\n $this->log->info(\"[HubSpot] Batch fetched {$objectType}\", [\n 'requested_count' => count($crmIds),\n 'returned_count' => count($results),\n 'crm_ids' => $crmIds,\n ]);\n }\n\n private function handleBatchError(\\Throwable $e, string $objectType, array $crmIds): void\n {\n $errorMessage = $e->getMessage() ?: 'Unknown error';\n $errorTrace = $e->getTraceAsString() ?: 'No trace available';\n\n $this->log->error(\"[HubSpot] Failed to batch fetch {$objectType}\", [\n 'crm_ids' => $crmIds,\n 'error' => $errorMessage,\n 'trace' => $errorTrace,\n ]);\n\n throw new CrmException(\"Failed to batch fetch {$objectType}: \" . $errorMessage);\n }\n\n /**\n * Batch read multiple opportunities by their CRM IDs\n *\n * @param array<string> $crmIds Array of HubSpot deal IDs (max 100)\n * @param array<string> $fields Array of property names to fetch\n *\n * @return array<string, array> Array keyed by CRM ID with opportunity data\n */\n public function getOpportunitiesByIds(array $crmIds, array $fields): array\n {\n return $this->batchReadObjects('deals', $crmIds, $fields);\n }\n\n /**\n * Batch read multiple companies by their CRM IDs\n *\n * @param array<string> $crmIds Array of HubSpot company IDs (max 100)\n * @param array<string> $fields Array of property names to fetch\n *\n * @return array<string, array> Array keyed by CRM ID with company data\n */\n public function getCompaniesByIds(array $crmIds, array $fields): array\n {\n return $this->batchReadObjects('companies', $crmIds, $fields);\n }\n\n /**\n * Batch read multiple contacts by their CRM IDs\n *\n * @param array<string> $crmIds Array of HubSpot contact IDs (max 100)\n * @param array<string> $fields Array of property names to fetch\n *\n * @return array<string, array> Array keyed by CRM ID with contact data\n */\n public function getContactsByIds(array $crmIds, array $fields): array\n {\n return $this->batchReadObjects('contacts', $crmIds, $fields);\n }\n\n /**\n * @throws CompanyApiException\n * @throws CrmException\n */\n public function getAccountById(string $crmId, array $fields): array\n {\n try {\n $company = $this->getNewInstance()->crm()->companies()->basicApi()->getById(\n $crmId,\n implode(',', $fields),\n );\n } catch (CompanyApiException $e) {\n $this->log->info('[Hubspot] Failed to fetch account', [\n 'crm_id' => $crmId,\n 'reason' => $e->getMessage(),\n ]);\n\n throw $e;\n }\n\n if (! $company instanceof CompaniesWithAssociations) {\n throw new CrmException('Account not found');\n }\n\n return [\n 'id' => $company->getId(),\n 'properties' => $company->getProperties(),\n ];\n }\n\n /**\n * @throws ContactApiException\n * @throws CrmException\n */\n public function getContactById(string $crmId, array $fields): array\n {\n try {\n $contact = $this->getNewInstance()->crm()->contacts()->basicApi()->getById(\n $crmId,\n implode(',', $fields)\n );\n } catch (ContactApiException $e) {\n $this->log->info('[Hubspot] Failed to fetch contact', [\n 'crm_id' => $crmId,\n 'reason' => $e->getMessage(),\n ]);\n\n throw $e;\n }\n\n if (! $contact instanceof ContactsWithAssociations) {\n throw new CrmException('Contact not found');\n }\n\n return [\n 'id' => $contact->getId(),\n 'properties' => $contact->getProperties(),\n ];\n }\n\n /**\n * This is email search request that Hubspot offers as GET (more generous quota)\n */\n public function getContactByEmail(string $email, array $fields = []): array\n {\n try {\n $contact = $this->getNewInstance()->crm()->contacts()->basicApi()->getById(\n $email,\n implode(',', $fields),\n null,\n false,\n 'email'\n );\n\n return [\n 'id' => $contact->getId(),\n 'properties' => $contact->getProperties(),\n ];\n } catch (ContactApiException $e) {\n $this->log->info('[Hubspot] Failed to fetch contact', [\n 'email' => $email,\n 'reason' => $e->getMessage(),\n ]);\n\n return [];\n }\n }\n\n /**\n * @throws CrmException\n */\n public function fetchProperty(string $objectType, string $propertyId): Property\n {\n $result = $this->getNewInstance()->crm()->properties()->coreApi()->getByName($objectType, $propertyId);\n\n if (! $result instanceof Property) {\n $this->log->error('[Hubspot] Failed to fetch property', [\n 'object_type' => $objectType,\n 'property_id' => $propertyId,\n 'reason' => $result->getMessage(),\n ]);\n\n throw new CrmException('Failed to fetch property');\n }\n\n return $result;\n }\n\n /**\n * @return array<CrmFieldOption>\n */\n public function fetchPropertyOptions(string $objectType, string $propertyId): array\n {\n /** @var array<CrmFieldOption> */\n return $this->fetchProperty($objectType, $propertyId)->getOptions();\n }\n\n /**\n * @return array<array{id:string, label:string, deleted:bool}>\n */\n public function fetchCallDispositions(): array\n {\n /** @var Response $response */\n $response = $this->getInstance()->engagements()->getCallDispositions();\n\n /**\n * @var array<array{\n * id:string,\n * label:string,\n * deleted: bool\n * }>\n */\n return $response->toArray();\n }\n\n /**\n * @return array<CrmFieldOption>\n */\n public function fetchOpportunityPipelineStages(): array\n {\n $stages = [];\n $apiResponse = $this->getNewInstance()->crm()->pipelines()->pipelinesApi()->getAll('deals');\n\n if ($apiResponse instanceof Error) {\n $this->log->error('[Hubspot] Failed to fetch opportunity pipelines', [\n 'reason' => $apiResponse->getMessage(),\n ]);\n\n return [];\n }\n\n foreach ($apiResponse->getResults() as $pipeline) {\n $pipelineStages = array_map(\n static function (PipelineStage $stage) {\n return [\n 'id' => $stage->getId(),\n 'label' => $stage->getLabel(),\n ];\n },\n $pipeline->getStages()\n );\n\n $stages = array_merge($stages, $pipelineStages);\n }\n\n return $stages;\n }\n\n public function fetchOpportunityPipelines(): array\n {\n $pipelines = [];\n\n try {\n $apiResponse = $this->makeRequest('/crm/v3/pipelines/deals');\n } catch (\\Exception $e) {\n $this->log->info('[Hubspot] Failed to fetch opportunity pipelines', [\n 'reason' => $e->getMessage(),\n ]);\n\n return [];\n }\n\n $response = $apiResponse->toArray();\n\n foreach ($response['results'] as $pipeline) {\n $pipelines[] = [\n 'id' => $pipeline['id'],\n 'label' => $pipeline['label'],\n ];\n }\n\n return $pipelines;\n }\n\n /**\n * @return array<CrmFieldOption>\n */\n public function fetchMeetingOutcomeFieldOptions(Field $field): array\n {\n return $field->getCrmProviderId() === 'meetingOutcome'\n ? $this->fetchMeetingOutcomeTypes()\n : $this->fetchCallActivityTypes();\n }\n\n public function fetchMeetingOutcomeTypes(): array\n {\n return $this->extractMeetingTypeOptions(\n 'https://api.hubapi.com/crm/v3/properties/meeting/hs_meeting_outcome'\n );\n }\n\n public function fetchCallActivityTypes(): array\n {\n return $this->extractMeetingTypeOptions(\n 'https://api.hubapi.com/crm/v3/properties/call/hs_activity_type'\n );\n }\n\n private function extractMeetingTypeOptions(string $endpoint): array\n {\n /** @var Response $response */\n $response = $this->getInstance()\n ->getClient()\n ->request('GET', $endpoint);\n\n /**\n * @var array<array{\n * value: string,\n * label: string,\n * displayOrder: int\n * }> $optionData\n */\n $optionData = $response->toArray()['options'] ?? [];\n\n $options = [];\n foreach ($optionData as $item) {\n $options[] = [\n 'id' => $item['value'],\n 'value' => $item['value'],\n 'label' => $item['label'],\n 'display_order' => $item['displayOrder'],\n ];\n }\n\n return $options;\n }\n\n /**\n * @return array<CrmFieldOption>\n */\n public function fetchDispositionFieldOptions(): array\n {\n $options = [];\n\n $dispositions = $this->fetchCallDispositions();\n\n foreach ($dispositions as $disposition) {\n if ($disposition['deleted'] !== false) {\n continue;\n }\n\n $option['value'] = $disposition['id'];\n $option['id'] = $disposition['id'];\n $option['label'] = $disposition['label'];\n\n $options[] = $option;\n }\n\n return $options;\n }\n\n /**\n * @return array<CrmFieldOption>\n */\n public function fetchOpportunityFieldOptions(Field $field): array\n {\n if ($field->isStageField()) {\n return $this->fetchOpportunityPipelineStages();\n }\n\n if ($field->isPipelineField()) {\n return $this->fetchOpportunityPipelines();\n }\n\n return $this->fetchPropertyOptions('deals', $field->getCrmProviderId());\n }\n\n /**\n * @throws BadRequest\n * @throws HubspotException\n */\n public function makeRequest(string $endpoint, $method = 'GET', $payload = [], ?string $queryString = null)\n {\n $endpoint = self::BASE_URL . $endpoint;\n\n if ($method === 'GET') {\n $response = $this->getInstance()->getClient()?->request(\n method: $method,\n endpoint: $endpoint,\n query_string: $queryString\n );\n } else {\n $response = $this->getInstance()->getClient()->request($method, $endpoint, [\n 'json' => ($payload),\n ]);\n }\n\n $max = $response->getHeaderLine('X-HubSpot-RateLimit-Max'); // \"110\"\n $remaining = $response->getHeaderLine('X-HubSpot-RateLimit-Remaining'); // \"109\"\n $interval = $response->getHeaderLine('X-HubSpot-RateLimit-Interval-Milliseconds'); // \"10000\"\n $body = json_decode((string) $response->getBody(), true);\n\n \\Illuminate\\Support\\Facades\\Log::channel('custom_channel')->info('$max ' . PHP_EOL . print_r($max, true));\n \\Illuminate\\Support\\Facades\\Log::channel('custom_channel')->info('$remaining ' . PHP_EOL . print_r($remaining, true));\n \\Illuminate\\Support\\Facades\\Log::channel('custom_channel')->info('$interval ' . PHP_EOL . print_r($interval, true));\n \\Illuminate\\Support\\Facades\\Log::channel('custom_channel')->info('$body ' . PHP_EOL . print_r($body, true));\n\n return $response;\n }\n\n /**\n * @throws BadRequest\n * @throws HubspotException\n */\n public function createMeeting(array $payload): Response\n {\n $endpoint = '/crm/v3/objects/meetings';\n\n return $this->makeRequest($endpoint, 'POST', $payload);\n }\n\n /**\n * @throws BadRequest\n * @throws HubspotException\n */\n public function updateMeeting(string $meetingId, array $payload): Response\n {\n $endpoint = '/crm/v3/objects/meetings/' . $meetingId;\n\n return $this->makeRequest($endpoint, 'PATCH', $payload);\n }\n\n /**\n * @throws \\Exception\n */\n public function createNote(\n string $body,\n string $ownerId,\n int $timestamp,\n string $objectId,\n NoteObject $noteObject\n ): ?string {\n try {\n $noteInput = new SimplePublicObjectInput([\n 'properties' => [\n 'hs_note_body' => $body,\n 'hubspot_owner_id' => $ownerId,\n 'hs_timestamp' => $timestamp,\n ],\n ]);\n\n // Create note\n $note = $this->getNewInstance()->crm()->objects()->basicApi()->create('note', $noteInput);\n\n $this->getNewInstance()->crm()->objects()->associationsApi()->create(\n 'note',\n $note->getId(),\n $this->getNoteObject($noteObject),\n $objectId,\n $this->getNoteAssociationType($noteObject),\n );\n\n return $note->getId();\n } catch (\\Exception $e) {\n $this->log->error('[Hubspot] Failed to create note', [\n 'objectId' => $objectId,\n 'noteObject' => $noteObject->getObjectType(),\n 'reason' => $e->getMessage(),\n ]);\n\n \\Sentry::captureException($e);\n }\n\n return null;\n }\n\n public function updateEngagement(string $objectId, array $engagement, array $metadata): void\n {\n $this->getInstance()->engagements()->update($objectId, $engagement, $metadata);\n }\n\n public function getEngagementData(string $engagementId): array\n {\n $engagement = $this->getInstance()->engagements()->get($engagementId);\n\n return $engagement->toArray();\n }\n\n public function createEngagement(array $engagement, array $associations, array $metadata): Response\n {\n return $this->getInstance()\n ->engagements()\n ->create($engagement, $associations, $metadata);\n }\n\n public function isUnauthorizedException(\\Exception $e): bool\n {\n // Check for specific HubSpot API exception types first\n if ($e instanceof BadRequest) {\n // BadRequest can contain 401 status codes\n return $e->getCode() === 401;\n }\n\n // Check for HTTP client exceptions with status codes\n if ($e instanceof \\GuzzleHttp\\Exception\\RequestException && $e->hasResponse()) {\n $response = $e->getResponse();\n if ($response !== null) {\n return $response->getStatusCode() === 401;\n }\n }\n\n // Check for Guzzle HTTP exceptions\n if ($e instanceof \\GuzzleHttp\\Exception\\ClientException) {\n return $e->getCode() === 401;\n }\n\n // Fallback to string matching as last resort, but be more specific\n $message = strtolower($e->getMessage());\n\n return str_contains($message, '401 unauthorized') ||\n str_contains($message, 'http 401') ||\n str_contains($message, 'status code 401') ||\n (preg_match('/\\b401\\b/', $message) && str_contains($message, 'unauthorized'));\n }\n\n /**\n * Validates and refreshes the access token if needed before API requests.\n * This ensures long-running processes don't fail due to token expiration.\n *\n * @throws SocialAccountTokenInvalidException\n */\n public function ensureValidToken(): void\n {\n if ($this->oauthAccount === null) {\n return;\n }\n\n $newToken = $this->tokenManager->ensureValidToken($this->oauthAccount);\n if ($newToken !== null) {\n $this->accessToken = $newToken;\n }\n }\n\n public function getConfig()\n {\n return $this->config;\n }\n\n // returns only active (archived=false)\n public function getOwners(): array\n {\n return $this->getNewInstance()->crm()->owners()->getAll();\n }\n\n /**\n * @param bool $archived\n *\n * @return array<Owner>|[]\n */\n public function getOwnersArchived(bool $archived = true): array\n {\n $endpoint = '/crm/v3/owners';\n $queryParams = [\n 'archived' => $archived ? 'true' : 'false',\n ];\n $queryString = http_build_query($queryParams);\n\n $owners = [];\n\n try {\n $response = $this->makeRequest(endpoint: $endpoint, queryString: $queryString);\n $responseData = $response?->toArray();\n\n foreach ($responseData['results'] as $result) {\n try {\n $owners[] = Owner::create($result);\n } catch (Throwable $e) {\n $this->log->error('[HubSpot] Failed to process owner data', [\n 'result' => $result,\n 'error' => $e->getMessage(),\n ]);\n\n continue;\n }\n }\n } catch (Throwable $e) {\n $this->log->error('HubSpot] Failed to fetch owners', [\n 'archived' => $archived,\n 'error' => $e->getMessage(),\n ]);\n\n return [];\n }\n\n return $owners;\n }\n\n public function getMeeting(string $engagementId): ObjectWithAssociations\n {\n return $this->getNewInstance()->crm()->objects()->basicApi()\n ->getById('meeting', $engagementId, null, 'contact,company,deal');\n }\n\n public function deleteEngagement(string $engagementId): void\n {\n $this->getInstance()->engagements()->delete((int) $engagementId);\n }\n\n public function getAssociationsData(array $ids, string $fromObject, string $toObject): array\n {\n $associationData = [];\n $idChunks = array_chunk($ids, self::ASSOCIATIONS_BATCH_SIZE_LIMIT);\n\n foreach ($idChunks as $idChunk) {\n try {\n $batchInput = new \\HubSpot\\Client\\Crm\\Associations\\Model\\BatchInputPublicObjectId();\n $batchInput->setInputs(array_map(function ($id) {\n $publicObjectId = new \\HubSpot\\Client\\Crm\\Associations\\Model\\PublicObjectId();\n $publicObjectId->setId($id);\n\n return $publicObjectId;\n }, $idChunk));\n\n $associatedObjectsData = $this\n ->getNewInstance()\n ->crm()\n ->associations()\n ->batchApi()\n ->read($fromObject, $toObject, $batchInput);\n\n if ($associatedObjectsData instanceof \\HubSpot\\Client\\Crm\\Associations\\Model\\BatchResponsePublicAssociationMulti) {\n foreach ($associatedObjectsData->getResults() as $association) {\n $from = $association->getFrom()->getId();\n $toAssociations = $association->getTo();\n\n if (! empty($toAssociations)) {\n $associationData[$from] = array_map(function ($item) {\n return $item->getId();\n }, $toAssociations);\n }\n }\n }\n } catch (\\Exception $e) {\n $this->log->error('[Hubspot] Failed to fetch associations', [\n 'from_object' => $fromObject,\n 'to_object' => $toObject,\n 'reason' => $e->getMessage(),\n ]);\n }\n }\n\n return $associationData;\n }\n\n /**\n * @throws \\Exception\n */\n private function getNoteAssociationType(NoteObject $noteObject): string\n {\n return match($noteObject) {\n NoteObject::Opportunity => 'note_to_deal',\n NoteObject::Lead, NoteObject::Contact => 'note_to_contact', // or 'note_to_lead' if your portal supports it\n NoteObject::Account => 'note_to_company',\n NoteObject::Call, NoteObject::Event => throw new \\Exception('Not supported'),\n };\n }\n\n /**\n * @throws \\Exception\n */\n private function getNoteObject(NoteObject $noteObject): string\n {\n return match($noteObject) {\n NoteObject::Opportunity => 'deal',\n NoteObject::Lead, NoteObject::Contact => 'contact',\n NoteObject::Account => 'company',\n NoteObject::Call, NoteObject::Event => throw new \\Exception('Not supported'),\n };\n }\n\n public function addAssociations(string $objectType, string $associationType, array $payload): Response\n {\n $endpoint = \"/crm/v4/associations/$objectType/$associationType/batch/create\";\n\n return $this->makeRequest($endpoint, 'POST', $payload);\n }\n\n public function removeAssociations(string $objectType, string $associationType, array $payload): Response\n {\n $endpoint = \"/crm/v4/associations/$objectType/$associationType/batch/archive\";\n\n return $this->makeRequest($endpoint, 'POST', $payload);\n }\n}","depth":4,"on_screen":true,"value":"<?php\n\ndeclare(strict_types=1);\n\nnamespace Jiminny\\Services\\Crm\\Hubspot;\n\nuse HubSpot\\Client\\Crm\\Deals\\ApiException as DealApiException;\nuse HubSpot\\Client\\Crm\\Contacts\\ApiException as ContactApiException;\nuse HubSpot\\Client\\Crm\\Companies\\ApiException as CompanyApiException;\nuse HubSpot\\Client\\Crm\\Contacts\\Model\\SimplePublicObjectWithAssociations as ContactsWithAssociations;\nuse HubSpot\\Client\\Crm\\Companies\\Model\\SimplePublicObjectWithAssociations as CompaniesWithAssociations;\nuse HubSpot\\Client\\Crm\\Deals\\Model\\SimplePublicObjectWithAssociations as DealWithAssociations;\nuse HubSpot\\Client\\Crm\\Objects\\Model\\SimplePublicObjectInput;\nuse HubSpot\\Client\\Crm\\Objects\\Model\\SimplePublicObjectWithAssociations as ObjectWithAssociations;\nuse HubSpot\\Client\\Crm\\Pipelines\\Model\\Error;\nuse HubSpot\\Client\\Crm\\Pipelines\\Model\\PipelineStage;\nuse HubSpot\\Client\\Crm\\Properties\\Model\\Property;\nuse HubSpot\\Discovery\\Discovery;\nuse Jiminny\\Component\\Utility\\Service\\ProviderRateLimiter;\nuse Jiminny\\Exceptions\\CrmException;\nuse Jiminny\\Exceptions\\RateLimitException;\nuse Jiminny\\Exceptions\\SocialAccountTokenInvalidException;\nuse Jiminny\\Jobs\\Crm\\NoteObject;\nuse Jiminny\\Models\\Crm\\Field;\nuse Jiminny\\Services\\Crm\\BaseClient;\nuse Jiminny\\Services\\Crm\\Hubspot\\DTO\\Response\\Owner;\nuse Jiminny\\Services\\SocialAccountService;\nuse SevenShores\\Hubspot\\Exceptions\\BadRequest;\nuse SevenShores\\Hubspot\\Exceptions\\HubspotException;\nuse SevenShores\\Hubspot\\Factory;\nuse SevenShores\\Hubspot\\Http\\Response;\nuse Jiminny\\Services\\Crm\\Hubspot\\Pagination\\HubspotPaginationService;\nuse Throwable;\n\n/**\n * @phpstan-type CrmFieldOption array{id:string, label:string, value?:string}\n */\nclass Client extends BaseClient implements HubspotClientInterface\n{\n public const string MIN_API_VERSION = '2';\n\n public const string BASE_URL = 'https://api.hubapi.com';\n\n public const int ASSOCIATIONS_BATCH_SIZE_LIMIT = 1000;\n\n private HubspotPaginationService $paginationService;\n private HubspotTokenManager $tokenManager;\n private ProviderRateLimiter $rateLimiter;\n\n public function __construct(\n SocialAccountService $socialAccountService,\n HubspotPaginationService $paginationService,\n HubspotTokenManager $tokenManager,\n ProviderRateLimiter $rateLimiter,\n ) {\n parent::__construct($socialAccountService);\n $this->paginationService = $paginationService;\n $this->tokenManager = $tokenManager;\n $this->rateLimiter = $rateLimiter;\n\n $this->setBaseUrl(self::BASE_URL);\n $this->setVersion(self::MIN_API_VERSION);\n }\n\n /**\n * Single entry point for every HubSpot API call. Enforces the per-portal\n * rate limit configured in the rate_limits table (morphed to the current\n * Configuration) and reacts to a real 429 from HubSpot by translating it\n * into a RateLimitException carrying Retry-After.\n *\n * Wrap any outbound HubSpot call (SDK or raw HTTP) like:\n *\n * $this->executeRequest(fn () => $this->getNewInstance()->crm()->...);\n *\n * @template T\n * @param callable(): T $apiCall\n * @return T\n *\n * @throws RateLimitException\n */\n private function executeRequest(callable $apiCall)\n {\n if (! $this->rateLimiter->canMakeRequest($this->config)) {\n $retryAfter = $this->rateLimiter->requestAvailableIn($this->config);\n\n $this->log->warning('[Hubspot] Rate limit exceeded, deferring request', [\n 'team_id' => $this->config->team_id,\n 'config_id' => $this->config->getId(),\n 'retry_after' => $retryAfter,\n ]);\n\n throw new RateLimitException(\n 'Hubspot rate limit reached for configuration ' . $this->config->getId(),\n $retryAfter,\n );\n }\n\n $this->rateLimiter->incrementRequestCount($this->config);\n\n try {\n return $apiCall();\n } catch (Throwable $e) {\n if ($this->isHubspotRateLimit($e)) {\n $retryAfter = $this->parseRetryAfter($e);\n\n $this->log->warning('[Hubspot] Received 429 from API', [\n 'team_id' => $this->config->team_id,\n 'config_id' => $this->config->getId(),\n 'retry_after' => $retryAfter,\n 'reason' => $e->getMessage(),\n ]);\n\n throw new RateLimitException('Hubspot returned 429', $retryAfter, $e);\n }\n\n throw $e;\n }\n }\n\n private function isHubspotRateLimit(Throwable $e): bool\n {\n return method_exists($e, 'getCode') && (int) $e->getCode() === 429;\n }\n\n private function parseRetryAfter(Throwable $e): int\n {\n if (method_exists($e, 'getResponseHeaders')) {\n $headers = $e->getResponseHeaders() ?: [];\n $value = $headers['Retry-After'] ?? $headers['retry-after'] ?? null;\n if (is_array($value)) {\n $value = $value[0] ?? null;\n }\n if (is_numeric($value)) {\n return (int) $value;\n }\n }\n\n return 10;\n }\n\n public function getMinimumApiVersion(): string\n {\n return self::MIN_API_VERSION;\n }\n\n public function getInstance(): Factory\n {\n return new Factory([\n 'key' => $this->accessToken,\n 'oauth2' => true,\n 'base_url' => $this->baseUrl,\n ]);\n }\n\n public function getNewInstance(): Discovery\n {\n return \\HubSpot\\Factory::createWithAccessToken($this->accessToken);\n }\n\n /**\n * Secondly and daily limits for Hubspot API\n *\n * Product Tier: Free & Starter | Professional & Enterprise | API add-on (any tier)\n * Burst: 100/10 seconds | 150/10 seconds | 200/10 seconds\n * Daily: 250,000 | 500,000 | 1,000,000\n *\n * Official documentation states: The search endpoints are rate limited to five requests per second.\n * Since with 5 RPS were still hitting secondly rate limits we lowered it to 4\n */\n public function getPaginatedData(array $payload, string $type, int $offset = 0): array\n {\n $total = 0;\n $lastId = null;\n $rows = [];\n foreach ($this->getPaginatedDataGenerator($payload, $type, $offset, $total, $lastId) as $row) {\n $rows[] = $row;\n }\n\n return ['results' => $rows, 'total' => $total, 'last_record' => $lastId];\n }\n\n /**\n * @throws HubspotException\n * @throws SocialAccountTokenInvalidException\n * @throws BadRequest\n */\n public function getPaginatedDataGenerator(\n array $payload,\n string $type,\n int $offset = 0,\n int &$total = 0,\n ?string &$lastRecordId = null\n ): \\Generator {\n return $this->paginationService->getPaginatedDataGenerator(\n $this,\n $payload,\n $type,\n $offset,\n $total,\n $lastRecordId\n );\n }\n\n /**\n * @throws DealApiException\n * @throws CrmException\n */\n public function getOpportunityById(string $crmId, array $fields): array\n {\n try {\n $deal = $this->executeRequest(fn () => $this->getNewInstance()->crm()->deals()->basicApi()->getById(\n $crmId,\n implode(',', $fields),\n 'companies,contacts'\n ));\n } catch (DealApiException $e) {\n $this->log->info('[Hubspot] Failed to fetch opportunity', [\n 'crm_id' => $crmId,\n 'reason' => $e->getMessage(),\n ]);\n\n throw $e;\n }\n\n if (! $deal instanceof DealWithAssociations) {\n throw new CrmException('Deal not found');\n }\n\n return [\n 'id' => $deal->getId(),\n 'properties' => $deal->getProperties(),\n 'associations' => $deal->getAssociations(),\n ];\n }\n\n /**\n * Generic batch read method for HubSpot objects\n *\n * @param string $objectType The object type ('deals', 'companies', 'contacts')\n * @param array<string> $crmIds Array of HubSpot object IDs (max 100)\n * @param array<string> $fields Array of property names to fetch\n *\n * @return array<string, array> Array keyed by CRM ID with object data\n */\n private function batchReadObjects(string $objectType, array $crmIds, array $fields): array\n {\n if (empty($crmIds)) {\n return [];\n }\n\n $this->validateBatchSize($objectType, $crmIds);\n $this->ensureValidToken();\n\n try {\n $batchConfig = $this->createBatchConfiguration($objectType);\n $batchReadRequest = $this->prepareBatchRequest($batchConfig, $crmIds, $fields);\n $response = $batchConfig['api']->read($batchReadRequest);\n\n $this->validateApiResponse($response, $objectType);\n\n $results = $this->processApiResults($response);\n $this->logBatchResults($objectType, $crmIds, $results);\n\n return $results;\n } catch (\\Throwable $e) {\n $this->handleBatchError($e, $objectType, $crmIds);\n }\n }\n\n private function validateBatchSize(string $objectType, array $crmIds): void\n {\n if (count($crmIds) > 100) {\n throw new \\InvalidArgumentException(\"Batch size cannot exceed 100 {$objectType}\");\n }\n }\n\n private function createBatchConfiguration(string $objectType): array\n {\n $configurations = [\n 'deals' => [\n 'batchReadRequest' => new \\HubSpot\\Client\\Crm\\Deals\\Model\\BatchReadInputSimplePublicObjectId(),\n 'inputClass' => \\HubSpot\\Client\\Crm\\Deals\\Model\\SimplePublicObjectId::class,\n 'api' => $this->getNewInstance()->crm()->deals()->batchApi(),\n ],\n 'companies' => [\n 'batchReadRequest' => new \\HubSpot\\Client\\Crm\\Companies\\Model\\BatchReadInputSimplePublicObjectId(),\n 'inputClass' => \\HubSpot\\Client\\Crm\\Companies\\Model\\SimplePublicObjectId::class,\n 'api' => $this->getNewInstance()->crm()->companies()->batchApi(),\n ],\n 'contacts' => [\n 'batchReadRequest' => new \\HubSpot\\Client\\Crm\\Contacts\\Model\\BatchReadInputSimplePublicObjectId(),\n 'inputClass' => \\HubSpot\\Client\\Crm\\Contacts\\Model\\SimplePublicObjectId::class,\n 'api' => $this->getNewInstance()->crm()->contacts()->batchApi(),\n ],\n ];\n\n if (! isset($configurations[$objectType])) {\n throw new \\InvalidArgumentException(\"Unsupported object type: {$objectType}\");\n }\n\n return $configurations[$objectType];\n }\n\n private function prepareBatchRequest(array $batchConfig, array $crmIds, array $fields): object\n {\n $batchReadRequest = $batchConfig['batchReadRequest'];\n $inputClass = $batchConfig['inputClass'];\n\n $inputs = array_map(function ($crmId) use ($inputClass) {\n $input = new $inputClass();\n $input->setId($crmId);\n\n return $input;\n }, $crmIds);\n\n $batchReadRequest->setInputs($inputs);\n $batchReadRequest->setProperties($fields);\n\n return $batchReadRequest;\n }\n\n private function validateApiResponse($response, string $objectType): void\n {\n if (! $response) {\n throw new CrmException(\"HubSpot API returned null response for {$objectType} batch read\");\n }\n }\n\n private function processApiResults($response): array\n {\n $results = [];\n $responseResults = $response->getResults();\n\n if ($responseResults) {\n foreach ($responseResults as $object) {\n if ($object && $object->getId()) {\n $results[$object->getId()] = [\n 'id' => $object->getId(),\n 'properties' => $object->getProperties() ?: [],\n ];\n }\n }\n }\n\n return $results;\n }\n\n private function logBatchResults(string $objectType, array $crmIds, array $results): void\n {\n $this->log->info(\"[HubSpot] Batch fetched {$objectType}\", [\n 'requested_count' => count($crmIds),\n 'returned_count' => count($results),\n 'crm_ids' => $crmIds,\n ]);\n }\n\n private function handleBatchError(\\Throwable $e, string $objectType, array $crmIds): void\n {\n $errorMessage = $e->getMessage() ?: 'Unknown error';\n $errorTrace = $e->getTraceAsString() ?: 'No trace available';\n\n $this->log->error(\"[HubSpot] Failed to batch fetch {$objectType}\", [\n 'crm_ids' => $crmIds,\n 'error' => $errorMessage,\n 'trace' => $errorTrace,\n ]);\n\n throw new CrmException(\"Failed to batch fetch {$objectType}: \" . $errorMessage);\n }\n\n /**\n * Batch read multiple opportunities by their CRM IDs\n *\n * @param array<string> $crmIds Array of HubSpot deal IDs (max 100)\n * @param array<string> $fields Array of property names to fetch\n *\n * @return array<string, array> Array keyed by CRM ID with opportunity data\n */\n public function getOpportunitiesByIds(array $crmIds, array $fields): array\n {\n return $this->batchReadObjects('deals', $crmIds, $fields);\n }\n\n /**\n * Batch read multiple companies by their CRM IDs\n *\n * @param array<string> $crmIds Array of HubSpot company IDs (max 100)\n * @param array<string> $fields Array of property names to fetch\n *\n * @return array<string, array> Array keyed by CRM ID with company data\n */\n public function getCompaniesByIds(array $crmIds, array $fields): array\n {\n return $this->batchReadObjects('companies', $crmIds, $fields);\n }\n\n /**\n * Batch read multiple contacts by their CRM IDs\n *\n * @param array<string> $crmIds Array of HubSpot contact IDs (max 100)\n * @param array<string> $fields Array of property names to fetch\n *\n * @return array<string, array> Array keyed by CRM ID with contact data\n */\n public function getContactsByIds(array $crmIds, array $fields): array\n {\n return $this->batchReadObjects('contacts', $crmIds, $fields);\n }\n\n /**\n * @throws CompanyApiException\n * @throws CrmException\n */\n public function getAccountById(string $crmId, array $fields): array\n {\n try {\n $company = $this->getNewInstance()->crm()->companies()->basicApi()->getById(\n $crmId,\n implode(',', $fields),\n );\n } catch (CompanyApiException $e) {\n $this->log->info('[Hubspot] Failed to fetch account', [\n 'crm_id' => $crmId,\n 'reason' => $e->getMessage(),\n ]);\n\n throw $e;\n }\n\n if (! $company instanceof CompaniesWithAssociations) {\n throw new CrmException('Account not found');\n }\n\n return [\n 'id' => $company->getId(),\n 'properties' => $company->getProperties(),\n ];\n }\n\n /**\n * @throws ContactApiException\n * @throws CrmException\n */\n public function getContactById(string $crmId, array $fields): array\n {\n try {\n $contact = $this->getNewInstance()->crm()->contacts()->basicApi()->getById(\n $crmId,\n implode(',', $fields)\n );\n } catch (ContactApiException $e) {\n $this->log->info('[Hubspot] Failed to fetch contact', [\n 'crm_id' => $crmId,\n 'reason' => $e->getMessage(),\n ]);\n\n throw $e;\n }\n\n if (! $contact instanceof ContactsWithAssociations) {\n throw new CrmException('Contact not found');\n }\n\n return [\n 'id' => $contact->getId(),\n 'properties' => $contact->getProperties(),\n ];\n }\n\n /**\n * This is email search request that Hubspot offers as GET (more generous quota)\n */\n public function getContactByEmail(string $email, array $fields = []): array\n {\n try {\n $contact = $this->getNewInstance()->crm()->contacts()->basicApi()->getById(\n $email,\n implode(',', $fields),\n null,\n false,\n 'email'\n );\n\n return [\n 'id' => $contact->getId(),\n 'properties' => $contact->getProperties(),\n ];\n } catch (ContactApiException $e) {\n $this->log->info('[Hubspot] Failed to fetch contact', [\n 'email' => $email,\n 'reason' => $e->getMessage(),\n ]);\n\n return [];\n }\n }\n\n /**\n * @throws CrmException\n */\n public function fetchProperty(string $objectType, string $propertyId): Property\n {\n $result = $this->getNewInstance()->crm()->properties()->coreApi()->getByName($objectType, $propertyId);\n\n if (! $result instanceof Property) {\n $this->log->error('[Hubspot] Failed to fetch property', [\n 'object_type' => $objectType,\n 'property_id' => $propertyId,\n 'reason' => $result->getMessage(),\n ]);\n\n throw new CrmException('Failed to fetch property');\n }\n\n return $result;\n }\n\n /**\n * @return array<CrmFieldOption>\n */\n public function fetchPropertyOptions(string $objectType, string $propertyId): array\n {\n /** @var array<CrmFieldOption> */\n return $this->fetchProperty($objectType, $propertyId)->getOptions();\n }\n\n /**\n * @return array<array{id:string, label:string, deleted:bool}>\n */\n public function fetchCallDispositions(): array\n {\n /** @var Response $response */\n $response = $this->getInstance()->engagements()->getCallDispositions();\n\n /**\n * @var array<array{\n * id:string,\n * label:string,\n * deleted: bool\n * }>\n */\n return $response->toArray();\n }\n\n /**\n * @return array<CrmFieldOption>\n */\n public function fetchOpportunityPipelineStages(): array\n {\n $stages = [];\n $apiResponse = $this->getNewInstance()->crm()->pipelines()->pipelinesApi()->getAll('deals');\n\n if ($apiResponse instanceof Error) {\n $this->log->error('[Hubspot] Failed to fetch opportunity pipelines', [\n 'reason' => $apiResponse->getMessage(),\n ]);\n\n return [];\n }\n\n foreach ($apiResponse->getResults() as $pipeline) {\n $pipelineStages = array_map(\n static function (PipelineStage $stage) {\n return [\n 'id' => $stage->getId(),\n 'label' => $stage->getLabel(),\n ];\n },\n $pipeline->getStages()\n );\n\n $stages = array_merge($stages, $pipelineStages);\n }\n\n return $stages;\n }\n\n public function fetchOpportunityPipelines(): array\n {\n $pipelines = [];\n\n try {\n $apiResponse = $this->makeRequest('/crm/v3/pipelines/deals');\n } catch (\\Exception $e) {\n $this->log->info('[Hubspot] Failed to fetch opportunity pipelines', [\n 'reason' => $e->getMessage(),\n ]);\n\n return [];\n }\n\n $response = $apiResponse->toArray();\n\n foreach ($response['results'] as $pipeline) {\n $pipelines[] = [\n 'id' => $pipeline['id'],\n 'label' => $pipeline['label'],\n ];\n }\n\n return $pipelines;\n }\n\n /**\n * @return array<CrmFieldOption>\n */\n public function fetchMeetingOutcomeFieldOptions(Field $field): array\n {\n return $field->getCrmProviderId() === 'meetingOutcome'\n ? $this->fetchMeetingOutcomeTypes()\n : $this->fetchCallActivityTypes();\n }\n\n public function fetchMeetingOutcomeTypes(): array\n {\n return $this->extractMeetingTypeOptions(\n 'https://api.hubapi.com/crm/v3/properties/meeting/hs_meeting_outcome'\n );\n }\n\n public function fetchCallActivityTypes(): array\n {\n return $this->extractMeetingTypeOptions(\n 'https://api.hubapi.com/crm/v3/properties/call/hs_activity_type'\n );\n }\n\n private function extractMeetingTypeOptions(string $endpoint): array\n {\n /** @var Response $response */\n $response = $this->getInstance()\n ->getClient()\n ->request('GET', $endpoint);\n\n /**\n * @var array<array{\n * value: string,\n * label: string,\n * displayOrder: int\n * }> $optionData\n */\n $optionData = $response->toArray()['options'] ?? [];\n\n $options = [];\n foreach ($optionData as $item) {\n $options[] = [\n 'id' => $item['value'],\n 'value' => $item['value'],\n 'label' => $item['label'],\n 'display_order' => $item['displayOrder'],\n ];\n }\n\n return $options;\n }\n\n /**\n * @return array<CrmFieldOption>\n */\n public function fetchDispositionFieldOptions(): array\n {\n $options = [];\n\n $dispositions = $this->fetchCallDispositions();\n\n foreach ($dispositions as $disposition) {\n if ($disposition['deleted'] !== false) {\n continue;\n }\n\n $option['value'] = $disposition['id'];\n $option['id'] = $disposition['id'];\n $option['label'] = $disposition['label'];\n\n $options[] = $option;\n }\n\n return $options;\n }\n\n /**\n * @return array<CrmFieldOption>\n */\n public function fetchOpportunityFieldOptions(Field $field): array\n {\n if ($field->isStageField()) {\n return $this->fetchOpportunityPipelineStages();\n }\n\n if ($field->isPipelineField()) {\n return $this->fetchOpportunityPipelines();\n }\n\n return $this->fetchPropertyOptions('deals', $field->getCrmProviderId());\n }\n\n /**\n * @throws BadRequest\n * @throws HubspotException\n */\n public function makeRequest(string $endpoint, $method = 'GET', $payload = [], ?string $queryString = null)\n {\n $endpoint = self::BASE_URL . $endpoint;\n\n if ($method === 'GET') {\n $response = $this->getInstance()->getClient()?->request(\n method: $method,\n endpoint: $endpoint,\n query_string: $queryString\n );\n } else {\n $response = $this->getInstance()->getClient()->request($method, $endpoint, [\n 'json' => ($payload),\n ]);\n }\n\n $max = $response->getHeaderLine('X-HubSpot-RateLimit-Max'); // \"110\"\n $remaining = $response->getHeaderLine('X-HubSpot-RateLimit-Remaining'); // \"109\"\n $interval = $response->getHeaderLine('X-HubSpot-RateLimit-Interval-Milliseconds'); // \"10000\"\n $body = json_decode((string) $response->getBody(), true);\n\n \\Illuminate\\Support\\Facades\\Log::channel('custom_channel')->info('$max ' . PHP_EOL . print_r($max, true));\n \\Illuminate\\Support\\Facades\\Log::channel('custom_channel')->info('$remaining ' . PHP_EOL . print_r($remaining, true));\n \\Illuminate\\Support\\Facades\\Log::channel('custom_channel')->info('$interval ' . PHP_EOL . print_r($interval, true));\n \\Illuminate\\Support\\Facades\\Log::channel('custom_channel')->info('$body ' . PHP_EOL . print_r($body, true));\n\n return $response;\n }\n\n /**\n * @throws BadRequest\n * @throws HubspotException\n */\n public function createMeeting(array $payload): Response\n {\n $endpoint = '/crm/v3/objects/meetings';\n\n return $this->makeRequest($endpoint, 'POST', $payload);\n }\n\n /**\n * @throws BadRequest\n * @throws HubspotException\n */\n public function updateMeeting(string $meetingId, array $payload): Response\n {\n $endpoint = '/crm/v3/objects/meetings/' . $meetingId;\n\n return $this->makeRequest($endpoint, 'PATCH', $payload);\n }\n\n /**\n * @throws \\Exception\n */\n public function createNote(\n string $body,\n string $ownerId,\n int $timestamp,\n string $objectId,\n NoteObject $noteObject\n ): ?string {\n try {\n $noteInput = new SimplePublicObjectInput([\n 'properties' => [\n 'hs_note_body' => $body,\n 'hubspot_owner_id' => $ownerId,\n 'hs_timestamp' => $timestamp,\n ],\n ]);\n\n // Create note\n $note = $this->getNewInstance()->crm()->objects()->basicApi()->create('note', $noteInput);\n\n $this->getNewInstance()->crm()->objects()->associationsApi()->create(\n 'note',\n $note->getId(),\n $this->getNoteObject($noteObject),\n $objectId,\n $this->getNoteAssociationType($noteObject),\n );\n\n return $note->getId();\n } catch (\\Exception $e) {\n $this->log->error('[Hubspot] Failed to create note', [\n 'objectId' => $objectId,\n 'noteObject' => $noteObject->getObjectType(),\n 'reason' => $e->getMessage(),\n ]);\n\n \\Sentry::captureException($e);\n }\n\n return null;\n }\n\n public function updateEngagement(string $objectId, array $engagement, array $metadata): void\n {\n $this->getInstance()->engagements()->update($objectId, $engagement, $metadata);\n }\n\n public function getEngagementData(string $engagementId): array\n {\n $engagement = $this->getInstance()->engagements()->get($engagementId);\n\n return $engagement->toArray();\n }\n\n public function createEngagement(array $engagement, array $associations, array $metadata): Response\n {\n return $this->getInstance()\n ->engagements()\n ->create($engagement, $associations, $metadata);\n }\n\n public function isUnauthorizedException(\\Exception $e): bool\n {\n // Check for specific HubSpot API exception types first\n if ($e instanceof BadRequest) {\n // BadRequest can contain 401 status codes\n return $e->getCode() === 401;\n }\n\n // Check for HTTP client exceptions with status codes\n if ($e instanceof \\GuzzleHttp\\Exception\\RequestException && $e->hasResponse()) {\n $response = $e->getResponse();\n if ($response !== null) {\n return $response->getStatusCode() === 401;\n }\n }\n\n // Check for Guzzle HTTP exceptions\n if ($e instanceof \\GuzzleHttp\\Exception\\ClientException) {\n return $e->getCode() === 401;\n }\n\n // Fallback to string matching as last resort, but be more specific\n $message = strtolower($e->getMessage());\n\n return str_contains($message, '401 unauthorized') ||\n str_contains($message, 'http 401') ||\n str_contains($message, 'status code 401') ||\n (preg_match('/\\b401\\b/', $message) && str_contains($message, 'unauthorized'));\n }\n\n /**\n * Validates and refreshes the access token if needed before API requests.\n * This ensures long-running processes don't fail due to token expiration.\n *\n * @throws SocialAccountTokenInvalidException\n */\n public function ensureValidToken(): void\n {\n if ($this->oauthAccount === null) {\n return;\n }\n\n $newToken = $this->tokenManager->ensureValidToken($this->oauthAccount);\n if ($newToken !== null) {\n $this->accessToken = $newToken;\n }\n }\n\n public function getConfig()\n {\n return $this->config;\n }\n\n // returns only active (archived=false)\n public function getOwners(): array\n {\n return $this->getNewInstance()->crm()->owners()->getAll();\n }\n\n /**\n * @param bool $archived\n *\n * @return array<Owner>|[]\n */\n public function getOwnersArchived(bool $archived = true): array\n {\n $endpoint = '/crm/v3/owners';\n $queryParams = [\n 'archived' => $archived ? 'true' : 'false',\n ];\n $queryString = http_build_query($queryParams);\n\n $owners = [];\n\n try {\n $response = $this->makeRequest(endpoint: $endpoint, queryString: $queryString);\n $responseData = $response?->toArray();\n\n foreach ($responseData['results'] as $result) {\n try {\n $owners[] = Owner::create($result);\n } catch (Throwable $e) {\n $this->log->error('[HubSpot] Failed to process owner data', [\n 'result' => $result,\n 'error' => $e->getMessage(),\n ]);\n\n continue;\n }\n }\n } catch (Throwable $e) {\n $this->log->error('HubSpot] Failed to fetch owners', [\n 'archived' => $archived,\n 'error' => $e->getMessage(),\n ]);\n\n return [];\n }\n\n return $owners;\n }\n\n public function getMeeting(string $engagementId): ObjectWithAssociations\n {\n return $this->getNewInstance()->crm()->objects()->basicApi()\n ->getById('meeting', $engagementId, null, 'contact,company,deal');\n }\n\n public function deleteEngagement(string $engagementId): void\n {\n $this->getInstance()->engagements()->delete((int) $engagementId);\n }\n\n public function getAssociationsData(array $ids, string $fromObject, string $toObject): array\n {\n $associationData = [];\n $idChunks = array_chunk($ids, self::ASSOCIATIONS_BATCH_SIZE_LIMIT);\n\n foreach ($idChunks as $idChunk) {\n try {\n $batchInput = new \\HubSpot\\Client\\Crm\\Associations\\Model\\BatchInputPublicObjectId();\n $batchInput->setInputs(array_map(function ($id) {\n $publicObjectId = new \\HubSpot\\Client\\Crm\\Associations\\Model\\PublicObjectId();\n $publicObjectId->setId($id);\n\n return $publicObjectId;\n }, $idChunk));\n\n $associatedObjectsData = $this\n ->getNewInstance()\n ->crm()\n ->associations()\n ->batchApi()\n ->read($fromObject, $toObject, $batchInput);\n\n if ($associatedObjectsData instanceof \\HubSpot\\Client\\Crm\\Associations\\Model\\BatchResponsePublicAssociationMulti) {\n foreach ($associatedObjectsData->getResults() as $association) {\n $from = $association->getFrom()->getId();\n $toAssociations = $association->getTo();\n\n if (! empty($toAssociations)) {\n $associationData[$from] = array_map(function ($item) {\n return $item->getId();\n }, $toAssociations);\n }\n }\n }\n } catch (\\Exception $e) {\n $this->log->error('[Hubspot] Failed to fetch associations', [\n 'from_object' => $fromObject,\n 'to_object' => $toObject,\n 'reason' => $e->getMessage(),\n ]);\n }\n }\n\n return $associationData;\n }\n\n /**\n * @throws \\Exception\n */\n private function getNoteAssociationType(NoteObject $noteObject): string\n {\n return match($noteObject) {\n NoteObject::Opportunity => 'note_to_deal',\n NoteObject::Lead, NoteObject::Contact => 'note_to_contact', // or 'note_to_lead' if your portal supports it\n NoteObject::Account => 'note_to_company',\n NoteObject::Call, NoteObject::Event => throw new \\Exception('Not supported'),\n };\n }\n\n /**\n * @throws \\Exception\n */\n private function getNoteObject(NoteObject $noteObject): string\n {\n return match($noteObject) {\n NoteObject::Opportunity => 'deal',\n NoteObject::Lead, NoteObject::Contact => 'contact',\n NoteObject::Account => 'company',\n NoteObject::Call, NoteObject::Event => throw new \\Exception('Not supported'),\n };\n }\n\n public function addAssociations(string $objectType, string $associationType, array $payload): Response\n {\n $endpoint = \"/crm/v4/associations/$objectType/$associationType/batch/create\";\n\n return $this->makeRequest($endpoint, 'POST', $payload);\n }\n\n public function removeAssociations(string $objectType, string $associationType, array $payload): Response\n {\n $endpoint = \"/crm/v4/associations/$objectType/$associationType/batch/archive\";\n\n return $this->makeRequest($endpoint, 'POST', $payload);\n }\n}","role_description":"text entry area","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"Project","depth":3,"on_screen":false,"role_description":"text"},{"role":"AXButton","text":"Project","depth":3,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"New File or Directory…","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.018055556,"height":0.026666667},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Expand Selected","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.018055556,"height":0.026666667},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Collapse All","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.018055556,"height":0.026666667},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Options","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.018055556,"height":0.026666667},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Hide","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.018055556,"height":0.026666667},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false}]...
|
-2199466652252128033
|
6666989725209335916
|
click
|
accessibility
|
NULL
|
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
[2026-05-07 11:36:44] local.INFO: Jiminny\Console\Commands\Command::run Memory usage before starting command {"command":"crm:sync-opportunity","memoryBeforeCommandInMb":60.0,"memoryPeakBeforeCommandInMb":99.727} {"correlation_id":"462baf35-d312-4641-9896-e8b15e8fdb82","trace_id":"aeb070fe-166a-48bf-99c0-acfcbfb40c1b"}
[2026-05-07 11:36:44] local.INFO: [SocialAccountService] Fetching token {"socialAccountId":1499,"provider":"hubspot"} {"correlation_id":"462baf35-d312-4641-9896-e8b15e8fdb82","trace_id":"aeb070fe-166a-48bf-99c0-acfcbfb40c1b"}
[2026-05-07 11:36:44] local.INFO: [SocialAccountService] Token retrieved {"socialAccountId":1499,"provider":"hubspot"} {"correlation_id":"462baf35-d312-4641-9896-e8b15e8fdb82","trace_id":"aeb070fe-166a-48bf-99c0-acfcbfb40c1b"}
[2026-05-07 11:36:44] local.INFO: [EncryptedTokenManager] Generating access token. {"mode":"legacy"} {"correlation_id":"462baf35-d312-4641-9896-e8b15e8fdb82","trace_id":"aeb070fe-166a-48bf-99c0-acfcbfb40c1b"}
[2026-05-07 11:36:44] local.INFO: [HubSpot] Syncing opportunities using strategy: lastModified {"team":2} {"correlation_id":"462baf35-d312-4641-9896-e8b15e8fdb82","trace_id":"aeb070fe-166a-48bf-99c0-acfcbfb40c1b"}
[2026-05-07 11:36:45] local.INFO: [Hubspot] Pagination completed {"team_id":2,"endpoint":"[URL_WITH_CREDENTIALS] CrmFieldOption array{id:string, label:string, value?:string}
*/
class Client extends BaseClient implements HubspotClientInterface
{
public const string MIN_API_VERSION = '2';
public const string BASE_URL = '[URL_WITH_CREDENTIALS] T
* @param callable(): T $apiCall
* @return T
*
* @throws RateLimitException
*/
private function executeRequest(callable $apiCall)
{
if (! $this->rateLimiter->canMakeRequest($this->config)) {
$retryAfter = $this->rateLimiter->requestAvailableIn($this->config);
$this->log->warning('[Hubspot] Rate limit exceeded, deferring request', [
'team_id' => $this->config->team_id,
'config_id' => $this->config->getId(),
'retry_after' => $retryAfter,
]);
throw new RateLimitException(
'Hubspot rate limit reached for configuration ' . $this->config->getId(),
$retryAfter,
);
}
$this->rateLimiter->incrementRequestCount($this->config);
try {
return $apiCall();
} catch (Throwable $e) {
if ($this->isHubspotRateLimit($e)) {
$retryAfter = $this->parseRetryAfter($e);
$this->log->warning('[Hubspot] Received 429 from API', [
'team_id' => $this->config->team_id,
'config_id' => $this->config->getId(),
'retry_after' => $retryAfter,
'reason' => $e->getMessage(),
]);
throw new RateLimitException('Hubspot returned 429', $retryAfter, $e);
}
throw $e;
}
}
private function isHubspotRateLimit(Throwable $e): bool
{
return method_exists($e, 'getCode') && (int) $e->getCode() === 429;
}
private function parseRetryAfter(Throwable $e): int
{
if (method_exists($e, 'getResponseHeaders')) {
$headers = $e->getResponseHeaders() ?: [];
$value = $headers['Retry-After'] ?? $headers['retry-after'] ?? null;
if (is_array($value)) {
$value = $value[0] ?? null;
}
if (is_numeric($value)) {
return (int) $value;
}
}
return 10;
}
public function getMinimumApiVersion(): string
{
return self::MIN_API_VERSION;
}
public function getInstance(): Factory
{
return new Factory([
'key' => $this->accessToken,
'oauth2' => true,
'base_url' => $this->baseUrl,
]);
}
public function getNewInstance(): Discovery
{
return \HubSpot\Factory::createWithAccessToken($this->accessToken);
}
/**
* Secondly and daily limits for Hubspot API
*
* Product Tier: Free & Starter | Professional & Enterprise | API add-on (any tier)
* Burst: 100/10 seconds | 150/10 seconds | 200/10 seconds
* Daily: 250,000 | 500,000 | 1,000,000
*
* Official documentation states: The search endpoints are rate limited to five requests per second.
* Since with 5 RPS were still hitting secondly rate limits we lowered it to 4
*/
public function getPaginatedData(array $payload, string $type, int $offset = 0): array
{
$total = 0;
$lastId = null;
$rows = [];
foreach ($this->getPaginatedDataGenerator($payload, $type, $offset, $total, $lastId) as $row) {
$rows[] = $row;
}
return ['results' => $rows, 'total' => $total, 'last_record' => $lastId];
}
/**
* @throws HubspotException
* @throws SocialAccountTokenInvalidException
* @throws BadRequest
*/
public function getPaginatedDataGenerator(
array $payload,
string $type,
int $offset = 0,
int &$total = 0,
?string &$lastRecordId = null
): \Generator {
return $this->paginationService->getPaginatedDataGenerator(
$this,
$payload,
$type,
$offset,
$total,
$lastRecordId
);
}
/**
* @throws DealApiException
* @throws CrmException
*/
public function getOpportunityById(string $crmId, array $fields): array
{
try {
$deal = $this->executeRequest(fn () => $this->getNewInstance()->crm()->deals()->basicApi()->getById(
$crmId,
implode(',', $fields),
'companies,contacts'
));
} catch (DealApiException $e) {
$this->log->info('[Hubspot] Failed to fetch opportunity', [
'crm_id' => $crmId,
'reason' => $e->getMessage(),
]);
throw $e;
}
if (! $deal instanceof DealWithAssociations) {
throw new CrmException('Deal not found');
}
return [
'id' => $deal->getId(),
'properties' => $deal->getProperties(),
'associations' => $deal->getAssociations(),
];
}
/**
* Generic batch read method for HubSpot objects
*
* @param string $objectType The object type ('deals', 'companies', 'contacts')
* @param array<string> $crmIds Array of HubSpot object IDs (max 100)
* @param array<string> $fields Array of property names to fetch
*
* @return array<string, array> Array keyed by CRM ID with object data
*/
private function batchReadObjects(string $objectType, array $crmIds, array $fields): array
{
if (empty($crmIds)) {
return [];
}
$this->validateBatchSize($objectType, $crmIds);
$this->ensureValidToken();
try {
$batchConfig = $this->createBatchConfiguration($objectType);
$batchReadRequest = $this->prepareBatchRequest($batchConfig, $crmIds, $fields);
$response = $batchConfig['api']->read($batchReadRequest);
$this->validateApiResponse($response, $objectType);
$results = $this->processApiResults($response);
$this->logBatchResults($objectType, $crmIds, $results);
return $results;
} catch (\Throwable $e) {
$this->handleBatchError($e, $objectType, $crmIds);
}
}
private function validateBatchSize(string $objectType, array $crmIds): void
{
if (count($crmIds) > 100) {
throw new \InvalidArgumentException("Batch size cannot exceed 100 {$objectType}");
}
}
private function createBatchConfiguration(string $objectType): array
{
$configurations = [
'deals' => [
'batchReadRequest' => new \HubSpot\Client\Crm\Deals\Model\BatchReadInputSimplePublicObjectId(),
'inputClass' => \HubSpot\Client\Crm\Deals\Model\SimplePublicObjectId::class,
'api' => $this->getNewInstance()->crm()->deals()->batchApi(),
],
'companies' => [
'batchReadRequest' => new \HubSpot\Client\Crm\Companies\Model\BatchReadInputSimplePublicObjectId(),
'inputClass' => \HubSpot\Client\Crm\Companies\Model\SimplePublicObjectId::class,
'api' => $this->getNewInstance()->crm()->companies()->batchApi(),
],
'contacts' => [
'batchReadRequest' => new \HubSpot\Client\Crm\Contacts\Model\BatchReadInputSimplePublicObjectId(),
'inputClass' => \HubSpot\Client\Crm\Contacts\Model\SimplePublicObjectId::class,
'api' => $this->getNewInstance()->crm()->contacts()->batchApi(),
],
];
if (! isset($configurations[$objectType])) {
throw new \InvalidArgumentException("Unsupported object type: {$objectType}");
}
return $configurations[$objectType];
}
private function prepareBatchRequest(array $batchConfig, array $crmIds, array $fields): object
{
$batchReadRequest = $batchConfig['batchReadRequest'];
$inputClass = $batchConfig['inputClass'];
$inputs = array_map(function ($crmId) use ($inputClass) {
$input = new $inputClass();
$input->setId($crmId);
return $input;
}, $crmIds);
$batchReadRequest->setInputs($inputs);
$batchReadRequest->setProperties($fields);
return $batchReadRequest;
}
private function validateApiResponse($response, string $objectType): void
{
if (! $response) {
throw new CrmException("HubSpot API returned null response for {$objectType} batch read");
}
}
private function processApiResults($response): array
{
$results = [];
$responseResults = $response->getResults();
if ($responseResults) {
foreach ($responseResults as $object) {
if ($object && $object->getId()) {
$results[$object->getId()] = [
'id' => $object->getId(),
'properties' => $object->getProperties() ?: [],
];
}
}
}
return $results;
}
private function logBatchResults(string $objectType, array $crmIds, array $results): void
{
$this->log->info("[HubSpot] Batch fetched {$objectType}", [
'requested_count' => count($crmIds),
'returned_count' => count($results),
'crm_ids' => $crmIds,
]);
}
private function handleBatchError(\Throwable $e, string $objectType, array $crmIds): void
{
$errorMessage = $e->getMessage() ?: 'Unknown error';
$errorTrace = $e->getTraceAsString() ?: 'No trace available';
$this->log->error("[HubSpot] Failed to batch fetch {$objectType}", [
'crm_ids' => $crmIds,
'error' => $errorMessage,
'trace' => $errorTrace,
]);
throw new CrmException("Failed to batch fetch {$objectType}: " . $errorMessage);
}
/**
* Batch read multiple opportunities by their CRM IDs
*
* @param array<string> $crmIds Array of HubSpot deal IDs (max 100)
* @param array<string> $fields Array of property names to fetch
*
* @return array<string, array> Array keyed by CRM ID with opportunity data
*/
public function getOpportunitiesByIds(array $crmIds, array $fields): array
{
return $this->batchReadObjects('deals', $crmIds, $fields);
}
/**
* Batch read multiple companies by their CRM IDs
*
* @param array<string> $crmIds Array of HubSpot company IDs (max 100)
* @param array<string> $fields Array of property names to fetch
*
* @return array<string, array> Array keyed by CRM ID with company data
*/
public function getCompaniesByIds(array $crmIds, array $fields): array
{
return $this->batchReadObjects('companies', $crmIds, $fields);
}
/**
* Batch read multiple contacts by their CRM IDs
*
* @param array<string> $crmIds Array of HubSpot contact IDs (max 100)
* @param array<string> $fields Array of property names to fetch
*
* @return array<string, array> Array keyed by CRM ID with contact data
*/
public function getContactsByIds(array $crmIds, array $fields): array
{
return $this->batchReadObjects('contacts', $crmIds, $fields);
}
/**
* @throws CompanyApiException
* @throws CrmException
*/
public function getAccountById(string $crmId, array $fields): array
{
try {
$company = $this->getNewInstance()->crm()->companies()->basicApi()->getById(
$crmId,
implode(',', $fields),
);
} catch (CompanyApiException $e) {
$this->log->info('[Hubspot] Failed to fetch account', [
'crm_id' => $crmId,
'reason' => $e->getMessage(),
]);
throw $e;
}
if (! $company instanceof CompaniesWithAssociations) {
throw new CrmException('Account not found');
}
return [
'id' => $company->getId(),
'properties' => $company->getProperties(),
];
}
/**
* @throws ContactApiException
* @throws CrmException
*/
public function getContactById(string $crmId, array $fields): array
{
try {
$contact = $this->getNewInstance()->crm()->contacts()->basicApi()->getById(
$crmId,
implode(',', $fields)
);
} catch (ContactApiException $e) {
$this->log->info('[Hubspot] Failed to fetch contact', [
'crm_id' => $crmId,
'reason' => $e->getMessage(),
]);
throw $e;
}
if (! $contact instanceof ContactsWithAssociations) {
throw new CrmException('Contact not found');
}
return [
'id' => $contact->getId(),
'properties' => $contact->getProperties(),
];
}
/**
* This is email search request that Hubspot offers as GET (more generous quota)
*/
public function getContactByEmail(string $email, array $fields = []): array
{
try {
$contact = $this->getNewInstance()->crm()->contacts()->basicApi()->getById(
$email,
implode(',', $fields),
null,
false,
'email'
);
return [
'id' => $contact->getId(),
'properties' => $contact->getProperties(),
];
} catch (ContactApiException $e) {
$this->log->info('[Hubspot] Failed to fetch contact', [
'email' => $email,
'reason' => $e->getMessage(),
]);
return [];
}
}
/**
* @throws CrmException
*/
public function fetchProperty(string $objectType, string $propertyId): Property
{
$result = $this->getNewInstance()->crm()->properties()->coreApi()->getByName($objectType, $propertyId);
if (! $result instanceof Property) {
$this->log->error('[Hubspot] Failed to fetch property', [
'object_type' => $objectType,
'property_id' => $propertyId,
'reason' => $result->getMessage(),
]);
throw new CrmException('Failed to fetch property');
}
return $result;
}
/**
* @return array<CrmFieldOption>
*/
public function fetchPropertyOptions(string $objectType, string $propertyId): array
{
/** @var array<CrmFieldOption> */
return $this->fetchProperty($objectType, $propertyId)->getOptions();
}
/**
* @return array<array{id:string, label:string, deleted:bool}>
*/
public function fetchCallDispositions(): array
{
/** @var Response $response */
$response = $this->getInstance()->engagements()->getCallDispositions();
/**
* @var array<array{
* id:string,
* label:string,
* deleted: bool
* }>
*/
return $response->toArray();
}
/**
* @return array<CrmFieldOption>
*/
public function fetchOpportunityPipelineStages(): array
{
$stages = [];
$apiResponse = $this->getNewInstance()->crm()->pipelines()->pipelinesApi()->getAll('deals');
if ($apiResponse instanceof Error) {
$this->log->error('[Hubspot] Failed to fetch opportunity pipelines', [
'reason' => $apiResponse->getMessage(),
]);
return [];
}
foreach ($apiResponse->getResults() as $pipeline) {
$pipelineStages = array_map(
static function (PipelineStage $stage) {
return [
'id' => $stage->getId(),
'label' => $stage->getLabel(),
];
},
$pipeline->getStages()
);
$stages = array_merge($stages, $pipelineStages);
}
return $stages;
}
public function fetchOpportunityPipelines(): array
{
$pipelines = [];
try {
$apiResponse = $this->makeRequest('/crm/v3/pipelines/deals');
} catch (\Exception $e) {
$this->log->info('[Hubspot] Failed to fetch opportunity pipelines', [
'reason' => $e->getMessage(),
]);
return [];
}
$response = $apiResponse->toArray();
foreach ($response['results'] as $pipeline) {
$pipelines[] = [
'id' => $pipeline['id'],
'label' => $pipeline['label'],
];
}
return $pipelines;
}
/**
* @return array<CrmFieldOption>
*/
public function fetchMeetingOutcomeFieldOptions(Field $field): array
{
return $field->getCrmProviderId() === 'meetingOutcome'
? $this->fetchMeetingOutcomeTypes()
: $this->fetchCallActivityTypes();
}
public function fetchMeetingOutcomeTypes(): array
{
return $this->extractMeetingTypeOptions(
'[URL_WITH_CREDENTIALS] Response $response */
$response = $this->getInstance()
->getClient()
->request('GET', $endpoint);
/**
* @var array<array{
* value: string,
* label: string,
* displayOrder: int
* }> $optionData
*/
$optionData = $response->toArray()['options'] ?? [];
$options = [];
foreach ($optionData as $item) {
$options[] = [
'id' => $item['value'],
'value' => $item['value'],
'label' => $item['label'],
'display_order' => $item['displayOrder'],
];
}
return $options;
}
/**
* @return array<CrmFieldOption>
*/
public function fetchDispositionFieldOptions(): array
{
$options = [];
$dispositions = $this->fetchCallDispositions();
foreach ($dispositions as $disposition) {
if ($disposition['deleted'] !== false) {
continue;
}
$option['value'] = $disposition['id'];
$option['id'] = $disposition['id'];
$option['label'] = $disposition['label'];
$options[] = $option;
}
return $options;
}
/**
* @return array<CrmFieldOption>
*/
public function fetchOpportunityFieldOptions(Field $field): array
{
if ($field->isStageField()) {
return $this->fetchOpportunityPipelineStages();
}
if ($field->isPipelineField()) {
return $this->fetchOpportunityPipelines();
}
return $this->fetchPropertyOptions('deals', $field->getCrmProviderId());
}
/**
* @throws BadRequest
* @throws HubspotException
*/
public function makeRequest(string $endpoint, $method = 'GET', $payload = [], ?string $queryString = null)
{
$endpoint = self::BASE_URL . $endpoint;
if ($method === 'GET') {
$response = $this->getInstance()->getClient()?->request(
method: $method,
endpoint: $endpoint,
query_string: $queryString
);
} else {
$response = $this->getInstance()->getClient()->request($method, $endpoint, [
'json' => ($payload),
]);
}
$max = $response->getHeaderLine('X-HubSpot-RateLimit-Max'); // "110"
$remaining = $response->getHeaderLine('X-HubSpot-RateLimit-Remaining'); // "109"
$interval = $response->getHeaderLine('X-HubSpot-RateLimit-Interval-Milliseconds'); // "10000"
$body = json_decode((string) $response->getBody(), true);
\Illuminate\Support\Facades\Log::channel('custom_channel')->info('$max ' . PHP_EOL . print_r($max, true));
\Illuminate\Support\Facades\Log::channel('custom_channel')->info('$remaining ' . PHP_EOL . print_r($remaining, true));
\Illuminate\Support\Facades\Log::channel('custom_channel')->info('$interval ' . PHP_EOL . print_r($interval, true));
\Illuminate\Support\Facades\Log::channel('custom_channel')->info('$body ' . PHP_EOL . print_r($body, true));
return $response;
}
/**
* @throws BadRequest
* @throws HubspotException
*/
public function createMeeting(array $payload): Response
{
$endpoint = '/crm/v3/objects/meetings';
return $this->makeRequest($endpoint, 'POST', $payload);
}
/**
* @throws BadRequest
* @throws HubspotException
*/
public function updateMeeting(string $meetingId, array $payload): Response
{
$endpoint = '/crm/v3/objects/meetings/' . $meetingId;
return $this->makeRequest($endpoint, 'PATCH', $payload);
}
/**
* @throws \Exception
*/
public function createNote(
string $body,
string $ownerId,
int $timestamp,
string $objectId,
NoteObject $noteObject
): ?string {
try {
$noteInput = new SimplePublicObjectInput([
'properties' => [
'hs_note_body' => $body,
'hubspot_owner_id' => $ownerId,
'hs_timestamp' => $timestamp,
],
]);
// Create note
$note = $this->getNewInstance()->crm()->objects()->basicApi()->create('note', $noteInput);
$this->getNewInstance()->crm()->objects()->associationsApi()->create(
'note',
$note->getId(),
$this->getNoteObject($noteObject),
$objectId,
$this->getNoteAssociationType($noteObject),
);
return $note->getId();
} catch (\Exception $e) {
$this->log->error('[Hubspot] Failed to create note', [
'objectId' => $objectId,
'noteObject' => $noteObject->getObjectType(),
'reason' => $e->getMessage(),
]);
\Sentry::captureException($e);
}
return null;
}
public function updateEngagement(string $objectId, array $engagement, array $metadata): void
{
$this->getInstance()->engagements()->update($objectId, $engagement, $metadata);
}
public function getEngagementData(string $engagementId): array
{
$engagement = $this->getInstance()->engagements()->get($engagementId);
return $engagement->toArray();
}
public function createEngagement(array $engagement, array $associations, array $metadata): Response
{
return $this->getInstance()
->engagements()
->create($engagement, $associations, $metadata);
}
public function isUnauthorizedException(\Exception $e): bool
{
// Check for specific HubSpot API exception types first
if ($e instanceof BadRequest) {
// BadRequest can contain 401 status codes
return $e->getCode() === 401;
}
// Check for HTTP client exceptions with status codes
if ($e instanceof \GuzzleHttp\Exception\RequestException && $e->hasResponse()) {
$response = $e->getResponse();
if ($response !== null) {
return $response->getStatusCode() === 401;
}
}
// Check for Guzzle HTTP exceptions
if ($e instanceof \GuzzleHttp\Exception\ClientException) {
return $e->getCode() === 401;
}
// Fallback to string matching as last resort, but be more specific
$message = strtolower($e->getMessage());
return str_contains($message, '401 unauthorized') ||
str_contains($message, 'http 401') ||
str_contains($message, 'status code 401') ||
(preg_match('/\b401\b/', $message) && str_contains($message, 'unauthorized'));
}
/**
* Validates and refreshes the access token if needed before API requests.
* This ensures long-running processes don't fail due to token expiration.
*
* @throws SocialAccountTokenInvalidException
*/
public function ensureValidToken(): void
{
if ($this->oauthAccount === null) {
return;
}
$newToken = $this->tokenManager->ensureValidToken($this->oauthAccount);
if ($newToken !== null) {
$this->accessToken = $newToken;
}
}
public function getConfig()
{
return $this->config;
}
// returns only active (archived=false)
public function getOwners(): array
{
return $this->getNewInstance()->crm()->owners()->getAll();
}
/**
* @param bool $archived
*
* @return array<Owner>|[]
*/
public function getOwnersArchived(bool $archived = true): array
{
$endpoint = '/crm/v3/owners';
$queryParams = [
'archived' => $archived ? 'true' : 'false',
];
$queryString = http_build_query($queryParams);
$owners = [];
try {
$response = $this->makeRequest(endpoint: $endpoint, queryString: $queryString);
$responseData = $response?->toArray();
foreach ($responseData['results'] as $result) {
try {
$owners[] = Owner::create($result);
} catch (Throwable $e) {
$this->log->error('[HubSpot] Failed to process owner data', [
'result' => $result,
'error' => $e->getMessage(),
]);
continue;
}
}
} catch (Throwable $e) {
$this->log->error('HubSpot] Failed to fetch owners', [
'archived' => $archived,
'error' => $e->getMessage(),
]);
return [];
}
return $owners;
}
public function getMeeting(string $engagementId): ObjectWithAssociations
{
return $this->getNewInstance()->crm()->objects()->basicApi()
->getById('meeting', $engagementId, null, 'contact,company,deal');
}
public function deleteEngagement(string $engagementId): void
{
$this->getInstance()->engagements()->delete((int) $engagementId);
}
public function getAssociationsData(array $ids, string $fromObject, string $toObject): array
{
$associationData = [];
$idChunks = array_chunk($ids, self::ASSOCIATIONS_BATCH_SIZE_LIMIT);
foreach ($idChunks as $idChunk) {
try {
$batchInput = new \HubSpot\Client\Crm\Associations\Model\BatchInputPublicObjectId();
$batchInput->setInputs(array_map(function ($id) {
$publicObjectId = new \HubSpot\Client\Crm\Associations\Model\PublicObjectId();
$publicObjectId->setId($id);
return $publicObjectId;
}, $idChunk));
$associatedObjectsData = $this
->getNewInstance()
->crm()
->associations()
->batchApi()
->read($fromObject, $toObject, $batchInput);
if ($associatedObjectsData instanceof \HubSpot\Client\Crm\Associations\Model\BatchResponsePublicAssociationMulti) {
foreach ($associatedObjectsData->getResults() as $association) {
$from = $association->getFrom()->getId();
$toAssociations = $association->getTo();
if (! empty($toAssociations)) {
$associationData[$from] = array_map(function ($item) {
return $item->getId();
}, $toAssociations);
}
}
}
} catch (\Exception $e) {
$this->log->error('[Hubspot] Failed to fetch associations', [
'from_object' => $fromObject,
'to_object' => $toObject,
'reason' => $e->getMessage(),
]);
}
}
return $associationData;
}
/**
* @throws \Exception
*/
private function getNoteAssociationType(NoteObject $noteObject): string
{
return match($noteObject) {
NoteObject::Opportunity => 'note_to_deal',
NoteObject::Lead, NoteObject::Contact => 'note_to_contact', // or 'note_to_lead' if your portal supports it
NoteObject::Account => 'note_to_company',
NoteObject::Call, NoteObject::Event => throw new \Exception('Not supported'),
};
}
/**
* @throws \Exception
*/
private function getNoteObject(NoteObject $noteObject): string
{
return match($noteObject) {
NoteObject::Opportunity => 'deal',
NoteObject::Lead, NoteObject::Contact => 'contact',
NoteObject::Account => 'company',
NoteObject::Call, NoteObject::Event => throw new \Exception('Not supported'),
};
}
public function addAssociations(string $objectType, string $associationType, array $payload): Response
{
$endpoint = "/crm/v4/associations/$objectType/$associationType/batch/create";
return $this->makeRequest($endpoint, 'POST', $payload);
}
public function removeAssociations(string $objectType, string $associationType, array $payload): Response
{
$endpoint = "/crm/v4/associations/$objectType/$associationType/batch/archive";
return $this->makeRequest($endpoint, 'POST', $payload);
}
}
Project
Project
New File or Directory…
Expand Selected
Collapse All
Options
Hide...
|
2740
|
NULL
|
NULL
|
NULL
|
|
2743
|
112
|
43
|
2026-05-07T11:38:07.941095+00:00
|
/Users/lukas/.screenpipe/data/data/2026-05-07/1778 /Users/lukas/.screenpipe/data/data/2026-05-07/1778153887941_m2.jpg...
|
PhpStorm
|
faVsco.js – laravel.log
|
True
|
NULL
|
monitor_2
|
NULL
|
NULL
|
NULL
|
NULL
|
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
[2026-05-07 11:36:44] local.INFO: Jiminny\Console\Commands\Command::run Memory usage before starting command {"command":"crm:sync-opportunity","memoryBeforeCommandInMb":60.0,"memoryPeakBeforeCommandInMb":99.727} {"correlation_id":"462baf35-d312-4641-9896-e8b15e8fdb82","trace_id":"aeb070fe-166a-48bf-99c0-acfcbfb40c1b"}
[2026-05-07 11:36:44] local.INFO: [SocialAccountService] Fetching token {"socialAccountId":1499,"provider":"hubspot"} {"correlation_id":"462baf35-d312-4641-9896-e8b15e8fdb82","trace_id":"aeb070fe-166a-48bf-99c0-acfcbfb40c1b"}
[2026-05-07 11:36:44] local.INFO: [SocialAccountService] Token retrieved {"socialAccountId":1499,"provider":"hubspot"} {"correlation_id":"462baf35-d312-4641-9896-e8b15e8fdb82","trace_id":"aeb070fe-166a-48bf-99c0-acfcbfb40c1b"}
[2026-05-07 11:36:44] local.INFO: [EncryptedTokenManager] Generating access token. {"mode":"legacy"} {"correlation_id":"462baf35-d312-4641-9896-e8b15e8fdb82","trace_id":"aeb070fe-166a-48bf-99c0-acfcbfb40c1b"}
[2026-05-07 11:36:44] local.INFO: [HubSpot] Syncing opportunities using strategy: lastModified {"team":2} {"correlation_id":"462baf35-d312-4641-9896-e8b15e8fdb82","trace_id":"aeb070fe-166a-48bf-99c0-acfcbfb40c1b"}
[2026-05-07 11:36:45] local.INFO: [Hubspot] Pagination completed {"team_id":2,"endpoint":"[URL_WITH_CREDENTIALS] CrmFieldOption array{id:string, label:string, value?:string}
*/
class Client extends BaseClient implements HubspotClientInterface
{
public const string MIN_API_VERSION = '2';
public const string BASE_URL = '[URL_WITH_CREDENTIALS] T
* @param callable(): T $apiCall
* @return T
*
* @throws RateLimitException
*/
private function executeRequest(callable $apiCall)
{
if (! $this->rateLimiter->canMakeRequest($this->config)) {
$retryAfter = $this->rateLimiter->requestAvailableIn($this->config);
$this->log->warning('[Hubspot] Rate limit exceeded, deferring request', [
'team_id' => $this->config->team_id,
'config_id' => $this->config->getId(),
'retry_after' => $retryAfter,
]);
throw new RateLimitException(
'Hubspot rate limit reached for configuration ' . $this->config->getId(),
$retryAfter,
);
}
$this->rateLimiter->incrementRequestCount($this->config);
try {
return $apiCall();
} catch (Throwable $e) {
if ($this->isHubspotRateLimit($e)) {
$retryAfter = $this->parseRetryAfter($e);
$this->log->warning('[Hubspot] Received 429 from API', [
'team_id' => $this->config->team_id,
'config_id' => $this->config->getId(),
'retry_after' => $retryAfter,
'reason' => $e->getMessage(),
]);
throw new RateLimitException('Hubspot returned 429', $retryAfter, $e);
}
throw $e;
}
}
private function isHubspotRateLimit(Throwable $e): bool
{
return method_exists($e, 'getCode') && (int) $e->getCode() === 429;
}
private function parseRetryAfter(Throwable $e): int
{
if (method_exists($e, 'getResponseHeaders')) {
$headers = $e->getResponseHeaders() ?: [];
$value = $headers['Retry-After'] ?? $headers['retry-after'] ?? null;
if (is_array($value)) {
$value = $value[0] ?? null;
}
if (is_numeric($value)) {
return (int) $value;
}
}
return 10;
}
public function getMinimumApiVersion(): string
{
return self::MIN_API_VERSION;
}
public function getInstance(): Factory
{
return new Factory([
'key' => $this->accessToken,
'oauth2' => true,
'base_url' => $this->baseUrl,
]);
}
public function getNewInstance(): Discovery
{
return \HubSpot\Factory::createWithAccessToken($this->accessToken);
}
/**
* Secondly and daily limits for Hubspot API
*
* Product Tier: Free & Starter | Professional & Enterprise | API add-on (any tier)
* Burst: 100/10 seconds | 150/10 seconds | 200/10 seconds
* Daily: 250,000 | 500,000 | 1,000,000
*
* Official documentation states: The search endpoints are rate limited to five requests per second.
* Since with 5 RPS were still hitting secondly rate limits we lowered it to 4
*/
public function getPaginatedData(array $payload, string $type, int $offset = 0): array
{
$total = 0;
$lastId = null;
$rows = [];
foreach ($this->getPaginatedDataGenerator($payload, $type, $offset, $total, $lastId) as $row) {
$rows[] = $row;
}
return ['results' => $rows, 'total' => $total, 'last_record' => $lastId];
}
/**
* @throws HubspotException
* @throws SocialAccountTokenInvalidException
* @throws BadRequest
*/
public function getPaginatedDataGenerator(
array $payload,
string $type,
int $offset = 0,
int &$total = 0,
?string &$lastRecordId = null
): \Generator {
return $this->paginationService->getPaginatedDataGenerator(
$this,
$payload,
$type,
$offset,
$total,
$lastRecordId
);
}
/**
* @throws DealApiException
* @throws CrmException
*/
public function getOpportunityById(string $crmId, array $fields): array
{
try {
$deal = $this->executeRequest(fn () => $this->getNewInstance()->crm()->deals()->basicApi()->getById(
$crmId,
implode(',', $fields),
'companies,contacts'
));
} catch (DealApiException $e) {
$this->log->info('[Hubspot] Failed to fetch opportunity', [
'crm_id' => $crmId,
'reason' => $e->getMessage(),
]);
throw $e;
}
if (! $deal instanceof DealWithAssociations) {
throw new CrmException('Deal not found');
}
return [
'id' => $deal->getId(),
'properties' => $deal->getProperties(),
'associations' => $deal->getAssociations(),
];
}
/**
* Generic batch read method for HubSpot objects
*
* @param string $objectType The object type ('deals', 'companies', 'contacts')
* @param array<string> $crmIds Array of HubSpot object IDs (max 100)
* @param array<string> $fields Array of property names to fetch
*
* @return array<string, array> Array keyed by CRM ID with object data
*/
private function batchReadObjects(string $objectType, array $crmIds, array $fields): array
{
if (empty($crmIds)) {
return [];
}
$this->validateBatchSize($objectType, $crmIds);
$this->ensureValidToken();
try {
$batchConfig = $this->createBatchConfiguration($objectType);
$batchReadRequest = $this->prepareBatchRequest($batchConfig, $crmIds, $fields);
$response = $batchConfig['api']->read($batchReadRequest);
$this->validateApiResponse($response, $objectType);
$results = $this->processApiResults($response);
$this->logBatchResults($objectType, $crmIds, $results);
return $results;
} catch (\Throwable $e) {
$this->handleBatchError($e, $objectType, $crmIds);
}
}
private function validateBatchSize(string $objectType, array $crmIds): void
{
if (count($crmIds) > 100) {
throw new \InvalidArgumentException("Batch size cannot exceed 100 {$objectType}");
}
}
private function createBatchConfiguration(string $objectType): array
{
$configurations = [
'deals' => [
'batchReadRequest' => new \HubSpot\Client\Crm\Deals\Model\BatchReadInputSimplePublicObjectId(),
'inputClass' => \HubSpot\Client\Crm\Deals\Model\SimplePublicObjectId::class,
'api' => $this->getNewInstance()->crm()->deals()->batchApi(),
],
'companies' => [
'batchReadRequest' => new \HubSpot\Client\Crm\Companies\Model\BatchReadInputSimplePublicObjectId(),
'inputClass' => \HubSpot\Client\Crm\Companies\Model\SimplePublicObjectId::class,
'api' => $this->getNewInstance()->crm()->companies()->batchApi(),
],
'contacts' => [
'batchReadRequest' => new \HubSpot\Client\Crm\Contacts\Model\BatchReadInputSimplePublicObjectId(),
'inputClass' => \HubSpot\Client\Crm\Contacts\Model\SimplePublicObjectId::class,
'api' => $this->getNewInstance()->crm()->contacts()->batchApi(),
],
];
if (! isset($configurations[$objectType])) {
throw new \InvalidArgumentException("Unsupported object type: {$objectType}");
}
return $configurations[$objectType];
}
private function prepareBatchRequest(array $batchConfig, array $crmIds, array $fields): object
{
$batchReadRequest = $batchConfig['batchReadRequest'];
$inputClass = $batchConfig['inputClass'];
$inputs = array_map(function ($crmId) use ($inputClass) {
$input = new $inputClass();
$input->setId($crmId);
return $input;
}, $crmIds);
$batchReadRequest->setInputs($inputs);
$batchReadRequest->setProperties($fields);
return $batchReadRequest;
}
private function validateApiResponse($response, string $objectType): void
{
if (! $response) {
throw new CrmException("HubSpot API returned null response for {$objectType} batch read");
}
}
private function processApiResults($response): array
{
$results = [];
$responseResults = $response->getResults();
if ($responseResults) {
foreach ($responseResults as $object) {
if ($object && $object->getId()) {
$results[$object->getId()] = [
'id' => $object->getId(),
'properties' => $object->getProperties() ?: [],
];
}
}
}
return $results;
}
private function logBatchResults(string $objectType, array $crmIds, array $results): void
{
$this->log->info("[HubSpot] Batch fetched {$objectType}", [
'requested_count' => count($crmIds),
'returned_count' => count($results),
'crm_ids' => $crmIds,
]);
}
private function handleBatchError(\Throwable $e, string $objectType, array $crmIds): void
{
$errorMessage = $e->getMessage() ?: 'Unknown error';
$errorTrace = $e->getTraceAsString() ?: 'No trace available';
$this->log->error("[HubSpot] Failed to batch fetch {$objectType}", [
'crm_ids' => $crmIds,
'error' => $errorMessage,
'trace' => $errorTrace,
]);
throw new CrmException("Failed to batch fetch {$objectType}: " . $errorMessage);
}
/**
* Batch read multiple opportunities by their CRM IDs
*
* @param array<string> $crmIds Array of HubSpot deal IDs (max 100)
* @param array<string> $fields Array of property names to fetch
*
* @return array<string, array> Array keyed by CRM ID with opportunity data
*/
public function getOpportunitiesByIds(array $crmIds, array $fields): array
{
return $this->batchReadObjects('deals', $crmIds, $fields);
}
/**
* Batch read multiple companies by their CRM IDs
*
* @param array<string> $crmIds Array of HubSpot company IDs (max 100)
* @param array<string> $fields Array of property names to fetch
*
* @return array<string, array> Array keyed by CRM ID with company data
*/
public function getCompaniesByIds(array $crmIds, array $fields): array
{
return $this->batchReadObjects('companies', $crmIds, $fields);
}
/**
* Batch read multiple contacts by their CRM IDs
*
* @param array<string> $crmIds Array of HubSpot contact IDs (max 100)
* @param array<string> $fields Array of property names to fetch
*
* @return array<string, array> Array keyed by CRM ID with contact data
*/
public function getContactsByIds(array $crmIds, array $fields): array
{
return $this->batchReadObjects('contacts', $crmIds, $fields);
}
/**
* @throws CompanyApiException
* @throws CrmException
*/
public function getAccountById(string $crmId, array $fields): array
{
try {
$company = $this->getNewInstance()->crm()->companies()->basicApi()->getById(
$crmId,
implode(',', $fields),
);
} catch (CompanyApiException $e) {
$this->log->info('[Hubspot] Failed to fetch account', [
'crm_id' => $crmId,
'reason' => $e->getMessage(),
]);
throw $e;
}
if (! $company instanceof CompaniesWithAssociations) {
throw new CrmException('Account not found');
}
return [
'id' => $company->getId(),
'properties' => $company->getProperties(),
];
}
/**
* @throws ContactApiException
* @throws CrmException
*/
public function getContactById(string $crmId, array $fields): array
{
try {
$contact = $this->getNewInstance()->crm()->contacts()->basicApi()->getById(
$crmId,
implode(',', $fields)
);
} catch (ContactApiException $e) {
$this->log->info('[Hubspot] Failed to fetch contact', [
'crm_id' => $crmId,
'reason' => $e->getMessage(),
]);
throw $e;
}
if (! $contact instanceof ContactsWithAssociations) {
throw new CrmException('Contact not found');
}
return [
'id' => $contact->getId(),
'properties' => $contact->getProperties(),
];
}
/**
* This is email search request that Hubspot offers as GET (more generous quota)
*/
public function getContactByEmail(string $email, array $fields = []): array
{
try {
$contact = $this->getNewInstance()->crm()->contacts()->basicApi()->getById(
$email,
implode(',', $fields),
null,
false,
'email'
);
return [
'id' => $contact->getId(),
'properties' => $contact->getProperties(),
];
} catch (ContactApiException $e) {
$this->log->info('[Hubspot] Failed to fetch contact', [
'email' => $email,
'reason' => $e->getMessage(),
]);
return [];
}
}
/**
* @throws CrmException
*/
public function fetchProperty(string $objectType, string $propertyId): Property
{
$result = $this->getNewInstance()->crm()->properties()->coreApi()->getByName($objectType, $propertyId);
if (! $result instanceof Property) {
$this->log->error('[Hubspot] Failed to fetch property', [
'object_type' => $objectType,
'property_id' => $propertyId,
'reason' => $result->getMessage(),
]);
throw new CrmException('Failed to fetch property');
}
return $result;
}
/**
* @return array<CrmFieldOption>
*/
public function fetchPropertyOptions(string $objectType, string $propertyId): array
{
/** @var array<CrmFieldOption> */
return $this->fetchProperty($objectType, $propertyId)->getOptions();
}
/**
* @return array<array{id:string, label:string, deleted:bool}>
*/
public function fetchCallDispositions(): array
{
/** @var Response $response */
$response = $this->getInstance()->engagements()->getCallDispositions();
/**
* @var array<array{
* id:string,
* label:string,
* deleted: bool
* }>
*/
return $response->toArray();
}
/**
* @return array<CrmFieldOption>
*/
public function fetchOpportunityPipelineStages(): array
{
$stages = [];
$apiResponse = $this->getNewInstance()->crm()->pipelines()->pipelinesApi()->getAll('deals');
if ($apiResponse instanceof Error) {
$this->log->error('[Hubspot] Failed to fetch opportunity pipelines', [
'reason' => $apiResponse->getMessage(),
]);
return [];
}
foreach ($apiResponse->getResults() as $pipeline) {
$pipelineStages = array_map(
static function (PipelineStage $stage) {
return [
'id' => $stage->getId(),
'label' => $stage->getLabel(),
];
},
$pipeline->getStages()
);
$stages = array_merge($stages, $pipelineStages);
}
return $stages;
}
public function fetchOpportunityPipelines(): array
{
$pipelines = [];
try {
$apiResponse = $this->makeRequest('/crm/v3/pipelines/deals');
} catch (\Exception $e) {
$this->log->info('[Hubspot] Failed to fetch opportunity pipelines', [
'reason' => $e->getMessage(),
]);
return [];
}
$response = $apiResponse->toArray();
foreach ($response['results'] as $pipeline) {
$pipelines[] = [
'id' => $pipeline['id'],
'label' => $pipeline['label'],
];
}
return $pipelines;
}
/**
* @return array<CrmFieldOption>
*/
public function fetchMeetingOutcomeFieldOptions(Field $field): array
{
return $field->getCrmProviderId() === 'meetingOutcome'
? $this->fetchMeetingOutcomeTypes()
: $this->fetchCallActivityTypes();
}
public function fetchMeetingOutcomeTypes(): array
{
return $this->extractMeetingTypeOptions(
'[URL_WITH_CREDENTIALS] Response $response */
$response = $this->getInstance()
->getClient()
->request('GET', $endpoint);
/**
* @var array<array{
* value: string,
* label: string,
* displayOrder: int
* }> $optionData
*/
$optionData = $response->toArray()['options'] ?? [];
$options = [];
foreach ($optionData as $item) {
$options[] = [
'id' => $item['value'],
'value' => $item['value'],
'label' => $item['label'],
'display_order' => $item['displayOrder'],
];
}
return $options;
}
/**
* @return array<CrmFieldOption>
*/
public function fetchDispositionFieldOptions(): array
{
$options = [];
$dispositions = $this->fetchCallDispositions();
foreach ($dispositions as $disposition) {
if ($disposition['deleted'] !== false) {
continue;
}
$option['value'] = $disposition['id'];
$option['id'] = $disposition['id'];
$option['label'] = $disposition['label'];
$options[] = $option;
}
return $options;
}
/**
* @return array<CrmFieldOption>
*/
public function fetchOpportunityFieldOptions(Field $field): array
{
if ($field->isStageField()) {
return $this->fetchOpportunityPipelineStages();
}
if ($field->isPipelineField()) {
return $this->fetchOpportunityPipelines();
}
return $this->fetchPropertyOptions('deals', $field->getCrmProviderId());
}
/**
* @throws BadRequest
* @throws HubspotException
*/
public function makeRequest(string $endpoint, $method = 'GET', $payload = [], ?string $queryString = null)
{
$endpoint = self::BASE_URL . $endpoint;
if ($method === 'GET') {
$response = $this->getInstance()->getClient()?->request(
method: $method,
endpoint: $endpoint,
query_string: $queryString
);
} else {
$response = $this->getInstance()->getClient()->request($method, $endpoint, [
'json' => ($payload),
]);
}
$max = $response->getHeaderLine('X-HubSpot-RateLimit-Max'); // "110"
$remaining = $response->getHeaderLine('X-HubSpot-RateLimit-Remaining'); // "109"
$interval = $response->getHeaderLine('X-HubSpot-RateLimit-Interval-Milliseconds'); // "10000"
$body = json_decode((string) $response->getBody(), true);
\Illuminate\Support\Facades\Log::channel('custom_channel')->info('$max ' . PHP_EOL . print_r($max, true));
\Illuminate\Support\Facades\Log::channel('custom_channel')->info('$remaining ' . PHP_EOL . print_r($remaining, true));
\Illuminate\Support\Facades\Log::channel('custom_channel')->info('$interval ' . PHP_EOL . print_r($interval, true));
\Illuminate\Support\Facades\Log::channel('custom_channel')->info('$body ' . PHP_EOL . print_r($body, true));
return $response;
}
/**
* @throws BadRequest
* @throws HubspotException
*/
public function createMeeting(array $payload): Response
{
$endpoint = '/crm/v3/objects/meetings';
return $this->makeRequest($endpoint, 'POST', $payload);
}
/**
* @throws BadRequest
* @throws HubspotException
*/
public function updateMeeting(string $meetingId, array $payload): Response
{
$endpoint = '/crm/v3/objects/meetings/' . $meetingId;
return $this->makeRequest($endpoint, 'PATCH', $payload);
}
/**
* @throws \Exception
*/
public function createNote(
string $body,
string $ownerId,
int $timestamp,
string $objectId,
NoteObject $noteObject
): ?string {
try {
$noteInput = new SimplePublicObjectInput([
'properties' => [
'hs_note_body' => $body,
'hubspot_owner_id' => $ownerId,
'hs_timestamp' => $timestamp,
],
]);
// Create note
$note = $this->getNewInstance()->crm()->objects()->basicApi()->create('note', $noteInput);
$this->getNewInstance()->crm()->objects()->associationsApi()->create(
'note',
$note->getId(),
$this->getNoteObject($noteObject),
$objectId,
$this->getNoteAssociationType($noteObject),
);
return $note->getId();
} catch (\Exception $e) {
$this->log->error('[Hubspot] Failed to create note', [
'objectId' => $objectId,
'noteObject' => $noteObject->getObjectType(),
'reason' => $e->getMessage(),
]);
\Sentry::captureException($e);
}
return null;
}
public function updateEngagement(string $objectId, array $engagement, array $metadata): void
{
$this->getInstance()->engagements()->update($objectId, $engagement, $metadata);
}
public function getEngagementData(string $engagementId): array
{
$engagement = $this->getInstance()->engagements()->get($engagementId);
return $engagement->toArray();
}
public function createEngagement(array $engagement, array $associations, array $metadata): Response
{
return $this->getInstance()
->engagements()
->create($engagement, $associations, $metadata);
}
public function isUnauthorizedException(\Exception $e): bool
{
// Check for specific HubSpot API exception types first
if ($e instanceof BadRequest) {
// BadRequest can contain 401 status codes
return $e->getCode() === 401;
}
// Check for HTTP client exceptions with status codes
if ($e instanceof \GuzzleHttp\Exception\RequestException && $e->hasResponse()) {
$response = $e->getResponse();
if ($response !== null) {
return $response->getStatusCode() === 401;
}
}
// Check for Guzzle HTTP exceptions
if ($e instanceof \GuzzleHttp\Exception\ClientException) {
return $e->getCode() === 401;
}
// Fallback to string matching as last resort, but be more specific
$message = strtolower($e->getMessage());
return str_contains($message, '401 unauthorized') ||
str_contains($message, 'http 401') ||
str_contains($message, 'status code 401') ||
(preg_match('/\b401\b/', $message) && str_contains($message, 'unauthorized'));
}
/**
* Validates and refreshes the access token if needed before API requests.
* This ensures long-running processes don't fail due to token expiration.
*
* @throws SocialAccountTokenInvalidException
*/
public function ensureValidToken(): void
{
if ($this->oauthAccount === null) {
return;
}
$newToken = $this->tokenManager->ensureValidToken($this->oauthAccount);
if ($newToken !== null) {
$this->accessToken = $newToken;
}
}
public function getConfig()
{
return $this->config;
}
// returns only active (archived=false)
public function getOwners(): array
{
return $this->getNewInstance()->crm()->owners()->getAll();
}
/**
* @param bool $archived
*
* @return array<Owner>|[]
*/
public function getOwnersArchived(bool $archived = true): array
{
$endpoint = '/crm/v3/owners';
$queryParams = [
'archived' => $archived ? 'true' : 'false',
];
$queryString = http_build_query($queryParams);
$owners = [];
try {
$response = $this->makeRequest(endpoint: $endpoint, queryString: $queryString);
$responseData = $response?->toArray();
foreach ($responseData['results'] as $result) {
try {
$owners[] = Owner::create($result);
} catch (Throwable $e) {
$this->log->error('[HubSpot] Failed to process owner data', [
'result' => $result,
'error' => $e->getMessage(),
]);
continue;
}
}
} catch (Throwable $e) {
$this->log->error('HubSpot] Failed to fetch owners', [
'archived' => $archived,
'error' => $e->getMessage(),
]);
return [];
}
return $owners;
}
public function getMeeting(string $engagementId): ObjectWithAssociations
{
return $this->getNewInstance()->crm()->objects()->basicApi()
->getById('meeting', $engagementId, null, 'contact,company,deal');
}
public function deleteEngagement(string $engagementId): void
{
$this->getInstance()->engagements()->delete((int) $engagementId);
}
public function getAssociationsData(array $ids, string $fromObject, string $toObject): array
{
$associationData = [];
$idChunks = array_chunk($ids, self::ASSOCIATIONS_BATCH_SIZE_LIMIT);
foreach ($idChunks as $idChunk) {
try {
$batchInput = new \HubSpot\Client\Crm\Associations\Model\BatchInputPublicObjectId();
$batchInput->setInputs(array_map(function ($id) {
$publicObjectId = new \HubSpot\Client\Crm\Associations\Model\PublicObjectId();
$publicObjectId->setId($id);
return $publicObjectId;
}, $idChunk));
$associatedObjectsData = $this
->getNewInstance()
->crm()
->associations()
->batchApi()
->read($fromObject, $toObject, $batchInput);
if ($associatedObjectsData instanceof \HubSpot\Client\Crm\Associations\Model\BatchResponsePublicAssociationMulti) {
foreach ($associatedObjectsData->getResults() as $association) {
$from = $association->getFrom()->getId();
$toAssociations = $association->getTo();
if (! empty($toAssociations)) {
$associationData[$from] = array_map(function ($item) {
return $item->getId();
}, $toAssociations);
}
}
}
} catch (\Exception $e) {
$this->log->error('[Hubspot] Failed to fetch associations', [
'from_object' => $fromObject,
'to_object' => $toObject,
'reason' => $e->getMessage(),
]);
}
}
return $associationData;
}
/**
* @throws \Exception
*/
private function getNoteAssociationType(NoteObject $noteObject): string
{
return match($noteObject) {
NoteObject::Opportunity => 'note_to_deal',
NoteObject::Lead, NoteObject::Contact => 'note_to_contact', // or 'note_to_lead' if your portal supports it
NoteObject::Account => 'note_to_company',
NoteObject::Call, NoteObject::Event => throw new \Exception('Not supported'),
};
}
/**
* @throws \Exception
*/
private function getNoteObject(NoteObject $noteObject): string
{
return match($noteObject) {
NoteObject::Opportunity => 'deal',
NoteObject::Lead, NoteObject::Contact => 'contact',
NoteObject::Account => 'company',
NoteObject::Call, NoteObject::Event => throw new \Exception('Not supported'),
};
}
public function addAssociations(string $objectType, string $associationType, array $payload): Response
{
$endpoint = "/crm/v4/associations/$objectType/$associationType/batch/create";
return $this->makeRequest($endpoint, 'POST', $payload);
}
public function removeAssociations(string $objectType, string $associationType, array $payload): Response
{
$endpoint = "/crm/v4/associations/$objectType/$associationType/batch/archive";
return $this->makeRequest($endpoint, 'POST', $payload);
}
}
Project
Project
New File or Directory…
Expand Selected
Collapse All
Options
Hide...
|
[{"role":"AXButton","text" [{"role":"AXButton","text":"Project: faVsco.js, menu","depth":5,"bounds":{"left":0.025930852,"top":0.019952115,"width":0.03856383,"height":0.025538707},"on_screen":true,"help_text":"~/jiminny/app","role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"master, menu","depth":5,"bounds":{"left":0.064494684,"top":0.019952115,"width":0.034242023,"height":0.025538707},"on_screen":true,"help_text":"Git Branch: master","role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Start Listening for PHP Debug Connections","depth":5,"bounds":{"left":0.8081782,"top":0.019952115,"width":0.011303191,"height":0.025538707},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"AskJiminnyReportActivityServiceTest","depth":6,"bounds":{"left":0.8234708,"top":0.019952115,"width":0.09208777,"height":0.025538707},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Run 'AskJiminnyReportActivityServiceTest'","depth":6,"bounds":{"left":0.9155585,"top":0.019952115,"width":0.011303191,"height":0.025538707},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Debug 'AskJiminnyReportActivityServiceTest'","depth":6,"bounds":{"left":0.9268617,"top":0.019952115,"width":0.011303191,"height":0.025538707},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"More Actions","depth":6,"bounds":{"left":0.9381649,"top":0.019952115,"width":0.011303191,"height":0.025538707},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"JetBrains AI","depth":5,"bounds":{"left":0.96609044,"top":0.019952115,"width":0.011303191,"height":0.025538707},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Search Everywhere","depth":5,"bounds":{"left":0.9773936,"top":0.019952115,"width":0.011303191,"height":0.025538707},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"IDE and Project Settings","depth":5,"bounds":{"left":0.9886968,"top":0.019952115,"width":0.011303186,"height":0.025538707},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXTextArea","text":"[2026-05-07 11:36:44] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage before starting command {\"command\":\"crm:sync-opportunity\",\"memoryBeforeCommandInMb\":60.0,\"memoryPeakBeforeCommandInMb\":99.727} {\"correlation_id\":\"462baf35-d312-4641-9896-e8b15e8fdb82\",\"trace_id\":\"aeb070fe-166a-48bf-99c0-acfcbfb40c1b\"}\n[2026-05-07 11:36:44] local.INFO: [SocialAccountService] Fetching token {\"socialAccountId\":1499,\"provider\":\"hubspot\"} {\"correlation_id\":\"462baf35-d312-4641-9896-e8b15e8fdb82\",\"trace_id\":\"aeb070fe-166a-48bf-99c0-acfcbfb40c1b\"}\n[2026-05-07 11:36:44] local.INFO: [SocialAccountService] Token retrieved {\"socialAccountId\":1499,\"provider\":\"hubspot\"} {\"correlation_id\":\"462baf35-d312-4641-9896-e8b15e8fdb82\",\"trace_id\":\"aeb070fe-166a-48bf-99c0-acfcbfb40c1b\"}\n[2026-05-07 11:36:44] local.INFO: [EncryptedTokenManager] Generating access token. {\"mode\":\"legacy\"} {\"correlation_id\":\"462baf35-d312-4641-9896-e8b15e8fdb82\",\"trace_id\":\"aeb070fe-166a-48bf-99c0-acfcbfb40c1b\"}\n[2026-05-07 11:36:44] local.INFO: [HubSpot] Syncing opportunities using strategy: lastModified {\"team\":2} {\"correlation_id\":\"462baf35-d312-4641-9896-e8b15e8fdb82\",\"trace_id\":\"aeb070fe-166a-48bf-99c0-acfcbfb40c1b\"}\n[2026-05-07 11:36:45] local.INFO: [Hubspot] Pagination completed {\"team_id\":2,\"endpoint\":\"https://api.hubapi.com/crm/v3/objects/deals/search\",\"total_requests\":1,\"total_records_fetched\":6,\"total_elapsed_seconds\":0.48,\"average_seconds_per_request\":0.48} {\"correlation_id\":\"462baf35-d312-4641-9896-e8b15e8fdb82\",\"trace_id\":\"aeb070fe-166a-48bf-99c0-acfcbfb40c1b\"}\n[2026-05-07 11:36:46] local.INFO: [HubSpot] importOpportunityBatch timing {\"teamId\":2,\"deal_count\":6,\"total_ms\":659,\"company_assoc_api_ms\":276,\"contact_assoc_api_ms\":203,\"prepare_entities_ms\":19,\"prepare_accounts_ms\":3,\"prepare_contacts_ms\":17,\"total_companies\":5,\"missing_companies\":0,\"total_contacts\":23,\"missing_contacts\":0,\"deals_loop_ms\":152,\"avg_deal_ms\":25,\"slow_deals_count\":0,\"slow_deals\":[]} {\"correlation_id\":\"462baf35-d312-4641-9896-e8b15e8fdb82\",\"trace_id\":\"aeb070fe-166a-48bf-99c0-acfcbfb40c1b\"}\n[2026-05-07 11:36:46] local.INFO: [HubSpot] Synced opportunities {\"team\":2,\"strategies\":\"lastModified\",\"sync_count\":5,\"total\":6,\"last_synced_id\":\"297846423\",\"duration_ms\":1161.39} {\"correlation_id\":\"462baf35-d312-4641-9896-e8b15e8fdb82\",\"trace_id\":\"aeb070fe-166a-48bf-99c0-acfcbfb40c1b\"}\n[2026-05-07 11:36:46] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage for command {\"command\":\"crm:sync-opportunity\",\"memoryBeforeCommandInMb\":60.0,\"memoryAfterCommandInMB\":62.0,\"memoryPeakBeforeCommandInMb\":99.727,\"memoryPeakAfterCommandInMB\":99.727} {\"correlation_id\":\"462baf35-d312-4641-9896-e8b15e8fdb82\",\"trace_id\":\"aeb070fe-166a-48bf-99c0-acfcbfb40c1b\"}\n[2026-05-07 11:36:52] local.INFO: [HubSpot Journal Polling] Getting offset from database {\"offset\":\"\",\"jiminny_team_id\":1} {\"correlation_id\":\"8568abe7-be33-417c-aa2f-b1bd53fb4adc\",\"trace_id\":\"582bff07-5ea4-4469-8ca8-efa41b04d46d\"}\n[2026-05-07 11:36:52] local.INFO: [HubSpot Journal API] Fetching latest journal entry {\"url\":\"https://api.hubapi.com/webhooks/v4/journal/latest\"} {\"correlation_id\":\"8568abe7-be33-417c-aa2f-b1bd53fb4adc\",\"trace_id\":\"582bff07-5ea4-4469-8ca8-efa41b04d46d\"}\n[2026-05-07 11:36:52] local.INFO: [HubSpot Journal Polling] No data {\"correlation_id\":\"8568abe7-be33-417c-aa2f-b1bd53fb4adc\",\"trace_id\":\"582bff07-5ea4-4469-8ca8-efa41b04d46d\"}\n[2026-05-07 11:36:52] local.WARNING: [HubSpot Journal Polling] Maximum empty results reached, stopping {\"empty_results\":5,\"max_empty_results\":5} {\"correlation_id\":\"8568abe7-be33-417c-aa2f-b1bd53fb4adc\",\"trace_id\":\"582bff07-5ea4-4469-8ca8-efa41b04d46d\"}\n[2026-05-07 11:36:52] local.WARNING: [HubSpot Journal Polling] Maximum empty results reached, stopping {\"empty_results\":5,\"max_empty_results\":5} {\"correlation_id\":\"8568abe7-be33-417c-aa2f-b1bd53fb4adc\",\"trace_id\":\"582bff07-5ea4-4469-8ca8-efa41b04d46d\"}\n[2026-05-07 11:36:52] local.INFO: [HubSpot Journal Polling] Service ending {\"runtime_seconds\":56,\"total_cycles\":5,\"files_downloaded\":0,\"empty_files\":0,\"other_portal_skipped\":0,\"total_events\":0,\"events_per_file\":0,\"avg_api_ms\":176.5,\"avg_download_ms\":0.0,\"avg_transform_ms\":0.0,\"avg_process_ms\":0.0,\"peak_memory_mb\":99.73} {\"correlation_id\":\"8568abe7-be33-417c-aa2f-b1bd53fb4adc\",\"trace_id\":\"582bff07-5ea4-4469-8ca8-efa41b04d46d\"}\n[2026-05-07 11:36:52] local.INFO: [HubSpot Journal Polling] Released polling lock {\"correlation_id\":\"8568abe7-be33-417c-aa2f-b1bd53fb4adc\",\"trace_id\":\"582bff07-5ea4-4469-8ca8-efa41b04d46d\"}\n[2026-05-07 11:37:05] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage before starting command {\"command\":\"meeting-bot:schedule-bot\",\"memoryBeforeCommandInMb\":60.0,\"memoryPeakBeforeCommandInMb\":99.727} {\"correlation_id\":\"34ae70b9-ee92-4e0c-bb97-3ecc204278be\",\"trace_id\":\"0d02e69d-5e33-4059-938c-d41350a39b72\"}\n[2026-05-07 11:37:05] local.INFO: [ScheduleBotCommand] Number of activities to be captured: 0 {\"correlation_id\":\"34ae70b9-ee92-4e0c-bb97-3ecc204278be\",\"trace_id\":\"0d02e69d-5e33-4059-938c-d41350a39b72\"}\n[2026-05-07 11:37:05] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage for command {\"command\":\"meeting-bot:schedule-bot\",\"memoryBeforeCommandInMb\":60.0,\"memoryAfterCommandInMB\":62.0,\"memoryPeakBeforeCommandInMb\":99.727,\"memoryPeakAfterCommandInMB\":99.727} {\"correlation_id\":\"34ae70b9-ee92-4e0c-bb97-3ecc204278be\",\"trace_id\":\"0d02e69d-5e33-4059-938c-d41350a39b72\"}\n[2026-05-07 11:37:06] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage before starting command {\"command\":\"dialers:monitor-activities\",\"memoryBeforeCommandInMb\":62.0,\"memoryPeakBeforeCommandInMb\":99.727} {\"correlation_id\":\"6052f7ad-dde1-41de-8051-5ba8d54babdd\",\"trace_id\":\"2e216848-ddf9-4f8c-8808-93a09cef8f5f\"}\n[2026-05-07 11:37:06] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage for command {\"command\":\"dialers:monitor-activities\",\"memoryBeforeCommandInMb\":62.0,\"memoryAfterCommandInMB\":62.0,\"memoryPeakBeforeCommandInMb\":99.727,\"memoryPeakAfterCommandInMB\":99.727} {\"correlation_id\":\"6052f7ad-dde1-41de-8051-5ba8d54babdd\",\"trace_id\":\"2e216848-ddf9-4f8c-8808-93a09cef8f5f\"}\n[2026-05-07 11:37:07] local.NOTICE: Monitoring start {\"correlation_id\":\"64decce4-a339-4c1b-8a73-4f612ef2eea2\",\"trace_id\":\"07e59e6c-d681-41f9-ba04-5cab00e9a754\"}\n[2026-05-07 11:37:07] local.NOTICE: Monitoring end {\"correlation_id\":\"64decce4-a339-4c1b-8a73-4f612ef2eea2\",\"trace_id\":\"07e59e6c-d681-41f9-ba04-5cab00e9a754\"}\n[2026-05-07 11:37:09] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage before starting command {\"command\":\"mailbox:skip-lists:refresh\",\"memoryBeforeCommandInMb\":62.0,\"memoryPeakBeforeCommandInMb\":99.727} {\"correlation_id\":\"0326b1c8-69ac-4d09-aae5-e808abc71f46\",\"trace_id\":\"2d16668b-26b0-44cf-a533-493500255667\"}\n[2026-05-07 11:37:09] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage for command {\"command\":\"mailbox:skip-lists:refresh\",\"memoryBeforeCommandInMb\":62.0,\"memoryAfterCommandInMB\":62.0,\"memoryPeakBeforeCommandInMb\":99.727,\"memoryPeakAfterCommandInMB\":99.727} {\"correlation_id\":\"0326b1c8-69ac-4d09-aae5-e808abc71f46\",\"trace_id\":\"2d16668b-26b0-44cf-a533-493500255667\"}\n[2026-05-07 11:37:10] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage before starting command {\"command\":\"mailbox:batch:process\",\"memoryBeforeCommandInMb\":62.0,\"memoryPeakBeforeCommandInMb\":99.727} {\"correlation_id\":\"aa512302-070c-4f99-b3ef-1a43c71b6297\",\"trace_id\":\"9e5c4036-6a42-4c64-a9b3-f11143254293\"}\n[2026-05-07 11:37:10] local.INFO: [EmailSchedule] STARTING batch process {\"host\":\"docker_lamp_1\"} {\"correlation_id\":\"aa512302-070c-4f99-b3ef-1a43c71b6297\",\"trace_id\":\"9e5c4036-6a42-4c64-a9b3-f11143254293\"}\n[2026-05-07 11:37:10] local.INFO: [EmailSchedule] FINISHED batch process {\"host\":\"docker_lamp_1\",\"processed\":0} {\"correlation_id\":\"aa512302-070c-4f99-b3ef-1a43c71b6297\",\"trace_id\":\"9e5c4036-6a42-4c64-a9b3-f11143254293\"}\n[2026-05-07 11:37:10] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage for command {\"command\":\"mailbox:batch:process\",\"memoryBeforeCommandInMb\":62.0,\"memoryAfterCommandInMB\":62.0,\"memoryPeakBeforeCommandInMb\":99.727,\"memoryPeakAfterCommandInMB\":99.727} {\"correlation_id\":\"aa512302-070c-4f99-b3ef-1a43c71b6297\",\"trace_id\":\"9e5c4036-6a42-4c64-a9b3-f11143254293\"}\n[2026-05-07 11:37:13] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage before starting command {\"command\":\"mailbox:batch:create\",\"memoryBeforeCommandInMb\":62.0,\"memoryPeakBeforeCommandInMb\":99.727} {\"correlation_id\":\"b3d4318e-3db2-413c-8fe1-8827ec834020\",\"trace_id\":\"870e454c-3a47-419e-88f1-2321d38b8937\"}\n[2026-05-07 11:37:13] local.INFO: [EmailSchedule] STARTING batch create {\"host\":\"docker_lamp_1\"} {\"correlation_id\":\"b3d4318e-3db2-413c-8fe1-8827ec834020\",\"trace_id\":\"870e454c-3a47-419e-88f1-2321d38b8937\"}\n[2026-05-07 11:37:13] local.INFO: [EmailSchedule] FINISHED batch create {\"host\":\"docker_lamp_1\"} {\"correlation_id\":\"b3d4318e-3db2-413c-8fe1-8827ec834020\",\"trace_id\":\"870e454c-3a47-419e-88f1-2321d38b8937\"}\n[2026-05-07 11:37:13] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage for command {\"command\":\"mailbox:batch:create\",\"memoryBeforeCommandInMb\":62.0,\"memoryAfterCommandInMB\":62.0,\"memoryPeakBeforeCommandInMb\":99.727,\"memoryPeakAfterCommandInMB\":99.727} {\"correlation_id\":\"b3d4318e-3db2-413c-8fe1-8827ec834020\",\"trace_id\":\"870e454c-3a47-419e-88f1-2321d38b8937\"}\n[2026-05-07 11:37:14] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage before starting command {\"command\":\"activity:sync\",\"memoryBeforeCommandInMb\":62.0,\"memoryPeakBeforeCommandInMb\":99.727} {\"correlation_id\":\"3e5f2a44-295d-4c28-b5e9-1a649c1f49ae\",\"trace_id\":\"f4d85ec2-b1d2-4418-bb65-814c209e628f\"}\n[2026-05-07 11:37:15] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage for command {\"command\":\"activity:sync\",\"memoryBeforeCommandInMb\":62.0,\"memoryAfterCommandInMB\":62.0,\"memoryPeakBeforeCommandInMb\":99.727,\"memoryPeakAfterCommandInMB\":99.727} {\"correlation_id\":\"3e5f2a44-295d-4c28-b5e9-1a649c1f49ae\",\"trace_id\":\"f4d85ec2-b1d2-4418-bb65-814c209e628f\"}\n[2026-05-07 11:37:21] local.INFO: [Commands/AsyncUpdateEsEntities] Starting ES update worker {\"pid\":20519,\"workerId\":\"\",\"target\":\"activities\"} {\"correlation_id\":\"1a81e470-75ac-4728-9d45-9f879fc96c5b\",\"trace_id\":\"7184ea88-9f01-42bc-9508-8e4ad0c1204b\"}\n[2026-05-07 11:37:22] local.INFO: [Jiminny\\Jobs\\Mailbox\\CreateBatches] processed 2 inboxes and created 0 batches {\"userId\":null,\"batchSize\":30,\"maxBatches\":1000} {\"correlation_id\":\"798a3e91-233c-4b61-aa8e-80a7b84ac45f\",\"trace_id\":\"870e454c-3a47-419e-88f1-2321d38b8937\"}\n[2026-05-07 11:37:53] local.INFO: [Commands/AsyncUpdateEsEntities] Starting ES update worker {\"pid\":20661,\"workerId\":\"\",\"target\":\"activities\"} {\"correlation_id\":\"293fa4a8-dc12-452e-88a3-f7408f8ed676\",\"trace_id\":\"e3d77ea8-2ff3-4b4e-a388-8f21192e51f3\"}\n[2026-05-07 11:38:03] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage before starting command {\"command\":\"crm:sync-opportunity\",\"memoryBeforeCommandInMb\":62.0,\"memoryPeakBeforeCommandInMb\":99.727} {\"correlation_id\":\"eb6c1736-f06b-4fbc-80f1-e1283f2021fb\",\"trace_id\":\"cef5d248-2fbf-443a-915a-81470cec6a21\"}\n[2026-05-07 11:38:04] local.INFO: [SocialAccountService] Fetching token {\"socialAccountId\":1499,\"provider\":\"hubspot\"} {\"correlation_id\":\"eb6c1736-f06b-4fbc-80f1-e1283f2021fb\",\"trace_id\":\"cef5d248-2fbf-443a-915a-81470cec6a21\"}\n[2026-05-07 11:38:04] local.INFO: [SocialAccountService] Token retrieved {\"socialAccountId\":1499,\"provider\":\"hubspot\"} {\"correlation_id\":\"eb6c1736-f06b-4fbc-80f1-e1283f2021fb\",\"trace_id\":\"cef5d248-2fbf-443a-915a-81470cec6a21\"}\n[2026-05-07 11:38:04] local.INFO: [EncryptedTokenManager] Generating access token. {\"mode\":\"legacy\"} {\"correlation_id\":\"eb6c1736-f06b-4fbc-80f1-e1283f2021fb\",\"trace_id\":\"cef5d248-2fbf-443a-915a-81470cec6a21\"}\n[2026-05-07 11:38:04] local.INFO: [HubSpot] Syncing opportunities using strategy: lastModified {\"team\":2} {\"correlation_id\":\"eb6c1736-f06b-4fbc-80f1-e1283f2021fb\",\"trace_id\":\"cef5d248-2fbf-443a-915a-81470cec6a21\"}\n[2026-05-07 11:38:04] local.INFO: [Hubspot] Pagination completed {\"team_id\":2,\"endpoint\":\"https://api.hubapi.com/crm/v3/objects/deals/search\",\"total_requests\":1,\"total_records_fetched\":6,\"total_elapsed_seconds\":0.56,\"average_seconds_per_request\":0.56} {\"correlation_id\":\"eb6c1736-f06b-4fbc-80f1-e1283f2021fb\",\"trace_id\":\"cef5d248-2fbf-443a-915a-81470cec6a21\"}","depth":4,"bounds":{"left":0.52859044,"top":0.0726257,"width":0.45977393,"height":0.9273743},"on_screen":true,"value":"[2026-05-07 11:36:44] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage before starting command {\"command\":\"crm:sync-opportunity\",\"memoryBeforeCommandInMb\":60.0,\"memoryPeakBeforeCommandInMb\":99.727} {\"correlation_id\":\"462baf35-d312-4641-9896-e8b15e8fdb82\",\"trace_id\":\"aeb070fe-166a-48bf-99c0-acfcbfb40c1b\"}\n[2026-05-07 11:36:44] local.INFO: [SocialAccountService] Fetching token {\"socialAccountId\":1499,\"provider\":\"hubspot\"} {\"correlation_id\":\"462baf35-d312-4641-9896-e8b15e8fdb82\",\"trace_id\":\"aeb070fe-166a-48bf-99c0-acfcbfb40c1b\"}\n[2026-05-07 11:36:44] local.INFO: [SocialAccountService] Token retrieved {\"socialAccountId\":1499,\"provider\":\"hubspot\"} {\"correlation_id\":\"462baf35-d312-4641-9896-e8b15e8fdb82\",\"trace_id\":\"aeb070fe-166a-48bf-99c0-acfcbfb40c1b\"}\n[2026-05-07 11:36:44] local.INFO: [EncryptedTokenManager] Generating access token. {\"mode\":\"legacy\"} {\"correlation_id\":\"462baf35-d312-4641-9896-e8b15e8fdb82\",\"trace_id\":\"aeb070fe-166a-48bf-99c0-acfcbfb40c1b\"}\n[2026-05-07 11:36:44] local.INFO: [HubSpot] Syncing opportunities using strategy: lastModified {\"team\":2} {\"correlation_id\":\"462baf35-d312-4641-9896-e8b15e8fdb82\",\"trace_id\":\"aeb070fe-166a-48bf-99c0-acfcbfb40c1b\"}\n[2026-05-07 11:36:45] local.INFO: [Hubspot] Pagination completed {\"team_id\":2,\"endpoint\":\"https://api.hubapi.com/crm/v3/objects/deals/search\",\"total_requests\":1,\"total_records_fetched\":6,\"total_elapsed_seconds\":0.48,\"average_seconds_per_request\":0.48} {\"correlation_id\":\"462baf35-d312-4641-9896-e8b15e8fdb82\",\"trace_id\":\"aeb070fe-166a-48bf-99c0-acfcbfb40c1b\"}\n[2026-05-07 11:36:46] local.INFO: [HubSpot] importOpportunityBatch timing {\"teamId\":2,\"deal_count\":6,\"total_ms\":659,\"company_assoc_api_ms\":276,\"contact_assoc_api_ms\":203,\"prepare_entities_ms\":19,\"prepare_accounts_ms\":3,\"prepare_contacts_ms\":17,\"total_companies\":5,\"missing_companies\":0,\"total_contacts\":23,\"missing_contacts\":0,\"deals_loop_ms\":152,\"avg_deal_ms\":25,\"slow_deals_count\":0,\"slow_deals\":[]} {\"correlation_id\":\"462baf35-d312-4641-9896-e8b15e8fdb82\",\"trace_id\":\"aeb070fe-166a-48bf-99c0-acfcbfb40c1b\"}\n[2026-05-07 11:36:46] local.INFO: [HubSpot] Synced opportunities {\"team\":2,\"strategies\":\"lastModified\",\"sync_count\":5,\"total\":6,\"last_synced_id\":\"297846423\",\"duration_ms\":1161.39} {\"correlation_id\":\"462baf35-d312-4641-9896-e8b15e8fdb82\",\"trace_id\":\"aeb070fe-166a-48bf-99c0-acfcbfb40c1b\"}\n[2026-05-07 11:36:46] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage for command {\"command\":\"crm:sync-opportunity\",\"memoryBeforeCommandInMb\":60.0,\"memoryAfterCommandInMB\":62.0,\"memoryPeakBeforeCommandInMb\":99.727,\"memoryPeakAfterCommandInMB\":99.727} {\"correlation_id\":\"462baf35-d312-4641-9896-e8b15e8fdb82\",\"trace_id\":\"aeb070fe-166a-48bf-99c0-acfcbfb40c1b\"}\n[2026-05-07 11:36:52] local.INFO: [HubSpot Journal Polling] Getting offset from database {\"offset\":\"\",\"jiminny_team_id\":1} {\"correlation_id\":\"8568abe7-be33-417c-aa2f-b1bd53fb4adc\",\"trace_id\":\"582bff07-5ea4-4469-8ca8-efa41b04d46d\"}\n[2026-05-07 11:36:52] local.INFO: [HubSpot Journal API] Fetching latest journal entry {\"url\":\"https://api.hubapi.com/webhooks/v4/journal/latest\"} {\"correlation_id\":\"8568abe7-be33-417c-aa2f-b1bd53fb4adc\",\"trace_id\":\"582bff07-5ea4-4469-8ca8-efa41b04d46d\"}\n[2026-05-07 11:36:52] local.INFO: [HubSpot Journal Polling] No data {\"correlation_id\":\"8568abe7-be33-417c-aa2f-b1bd53fb4adc\",\"trace_id\":\"582bff07-5ea4-4469-8ca8-efa41b04d46d\"}\n[2026-05-07 11:36:52] local.WARNING: [HubSpot Journal Polling] Maximum empty results reached, stopping {\"empty_results\":5,\"max_empty_results\":5} {\"correlation_id\":\"8568abe7-be33-417c-aa2f-b1bd53fb4adc\",\"trace_id\":\"582bff07-5ea4-4469-8ca8-efa41b04d46d\"}\n[2026-05-07 11:36:52] local.WARNING: [HubSpot Journal Polling] Maximum empty results reached, stopping {\"empty_results\":5,\"max_empty_results\":5} {\"correlation_id\":\"8568abe7-be33-417c-aa2f-b1bd53fb4adc\",\"trace_id\":\"582bff07-5ea4-4469-8ca8-efa41b04d46d\"}\n[2026-05-07 11:36:52] local.INFO: [HubSpot Journal Polling] Service ending {\"runtime_seconds\":56,\"total_cycles\":5,\"files_downloaded\":0,\"empty_files\":0,\"other_portal_skipped\":0,\"total_events\":0,\"events_per_file\":0,\"avg_api_ms\":176.5,\"avg_download_ms\":0.0,\"avg_transform_ms\":0.0,\"avg_process_ms\":0.0,\"peak_memory_mb\":99.73} {\"correlation_id\":\"8568abe7-be33-417c-aa2f-b1bd53fb4adc\",\"trace_id\":\"582bff07-5ea4-4469-8ca8-efa41b04d46d\"}\n[2026-05-07 11:36:52] local.INFO: [HubSpot Journal Polling] Released polling lock {\"correlation_id\":\"8568abe7-be33-417c-aa2f-b1bd53fb4adc\",\"trace_id\":\"582bff07-5ea4-4469-8ca8-efa41b04d46d\"}\n[2026-05-07 11:37:05] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage before starting command {\"command\":\"meeting-bot:schedule-bot\",\"memoryBeforeCommandInMb\":60.0,\"memoryPeakBeforeCommandInMb\":99.727} {\"correlation_id\":\"34ae70b9-ee92-4e0c-bb97-3ecc204278be\",\"trace_id\":\"0d02e69d-5e33-4059-938c-d41350a39b72\"}\n[2026-05-07 11:37:05] local.INFO: [ScheduleBotCommand] Number of activities to be captured: 0 {\"correlation_id\":\"34ae70b9-ee92-4e0c-bb97-3ecc204278be\",\"trace_id\":\"0d02e69d-5e33-4059-938c-d41350a39b72\"}\n[2026-05-07 11:37:05] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage for command {\"command\":\"meeting-bot:schedule-bot\",\"memoryBeforeCommandInMb\":60.0,\"memoryAfterCommandInMB\":62.0,\"memoryPeakBeforeCommandInMb\":99.727,\"memoryPeakAfterCommandInMB\":99.727} {\"correlation_id\":\"34ae70b9-ee92-4e0c-bb97-3ecc204278be\",\"trace_id\":\"0d02e69d-5e33-4059-938c-d41350a39b72\"}\n[2026-05-07 11:37:06] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage before starting command {\"command\":\"dialers:monitor-activities\",\"memoryBeforeCommandInMb\":62.0,\"memoryPeakBeforeCommandInMb\":99.727} {\"correlation_id\":\"6052f7ad-dde1-41de-8051-5ba8d54babdd\",\"trace_id\":\"2e216848-ddf9-4f8c-8808-93a09cef8f5f\"}\n[2026-05-07 11:37:06] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage for command {\"command\":\"dialers:monitor-activities\",\"memoryBeforeCommandInMb\":62.0,\"memoryAfterCommandInMB\":62.0,\"memoryPeakBeforeCommandInMb\":99.727,\"memoryPeakAfterCommandInMB\":99.727} {\"correlation_id\":\"6052f7ad-dde1-41de-8051-5ba8d54babdd\",\"trace_id\":\"2e216848-ddf9-4f8c-8808-93a09cef8f5f\"}\n[2026-05-07 11:37:07] local.NOTICE: Monitoring start {\"correlation_id\":\"64decce4-a339-4c1b-8a73-4f612ef2eea2\",\"trace_id\":\"07e59e6c-d681-41f9-ba04-5cab00e9a754\"}\n[2026-05-07 11:37:07] local.NOTICE: Monitoring end {\"correlation_id\":\"64decce4-a339-4c1b-8a73-4f612ef2eea2\",\"trace_id\":\"07e59e6c-d681-41f9-ba04-5cab00e9a754\"}\n[2026-05-07 11:37:09] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage before starting command {\"command\":\"mailbox:skip-lists:refresh\",\"memoryBeforeCommandInMb\":62.0,\"memoryPeakBeforeCommandInMb\":99.727} {\"correlation_id\":\"0326b1c8-69ac-4d09-aae5-e808abc71f46\",\"trace_id\":\"2d16668b-26b0-44cf-a533-493500255667\"}\n[2026-05-07 11:37:09] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage for command {\"command\":\"mailbox:skip-lists:refresh\",\"memoryBeforeCommandInMb\":62.0,\"memoryAfterCommandInMB\":62.0,\"memoryPeakBeforeCommandInMb\":99.727,\"memoryPeakAfterCommandInMB\":99.727} {\"correlation_id\":\"0326b1c8-69ac-4d09-aae5-e808abc71f46\",\"trace_id\":\"2d16668b-26b0-44cf-a533-493500255667\"}\n[2026-05-07 11:37:10] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage before starting command {\"command\":\"mailbox:batch:process\",\"memoryBeforeCommandInMb\":62.0,\"memoryPeakBeforeCommandInMb\":99.727} {\"correlation_id\":\"aa512302-070c-4f99-b3ef-1a43c71b6297\",\"trace_id\":\"9e5c4036-6a42-4c64-a9b3-f11143254293\"}\n[2026-05-07 11:37:10] local.INFO: [EmailSchedule] STARTING batch process {\"host\":\"docker_lamp_1\"} {\"correlation_id\":\"aa512302-070c-4f99-b3ef-1a43c71b6297\",\"trace_id\":\"9e5c4036-6a42-4c64-a9b3-f11143254293\"}\n[2026-05-07 11:37:10] local.INFO: [EmailSchedule] FINISHED batch process {\"host\":\"docker_lamp_1\",\"processed\":0} {\"correlation_id\":\"aa512302-070c-4f99-b3ef-1a43c71b6297\",\"trace_id\":\"9e5c4036-6a42-4c64-a9b3-f11143254293\"}\n[2026-05-07 11:37:10] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage for command {\"command\":\"mailbox:batch:process\",\"memoryBeforeCommandInMb\":62.0,\"memoryAfterCommandInMB\":62.0,\"memoryPeakBeforeCommandInMb\":99.727,\"memoryPeakAfterCommandInMB\":99.727} {\"correlation_id\":\"aa512302-070c-4f99-b3ef-1a43c71b6297\",\"trace_id\":\"9e5c4036-6a42-4c64-a9b3-f11143254293\"}\n[2026-05-07 11:37:13] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage before starting command {\"command\":\"mailbox:batch:create\",\"memoryBeforeCommandInMb\":62.0,\"memoryPeakBeforeCommandInMb\":99.727} {\"correlation_id\":\"b3d4318e-3db2-413c-8fe1-8827ec834020\",\"trace_id\":\"870e454c-3a47-419e-88f1-2321d38b8937\"}\n[2026-05-07 11:37:13] local.INFO: [EmailSchedule] STARTING batch create {\"host\":\"docker_lamp_1\"} {\"correlation_id\":\"b3d4318e-3db2-413c-8fe1-8827ec834020\",\"trace_id\":\"870e454c-3a47-419e-88f1-2321d38b8937\"}\n[2026-05-07 11:37:13] local.INFO: [EmailSchedule] FINISHED batch create {\"host\":\"docker_lamp_1\"} {\"correlation_id\":\"b3d4318e-3db2-413c-8fe1-8827ec834020\",\"trace_id\":\"870e454c-3a47-419e-88f1-2321d38b8937\"}\n[2026-05-07 11:37:13] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage for command {\"command\":\"mailbox:batch:create\",\"memoryBeforeCommandInMb\":62.0,\"memoryAfterCommandInMB\":62.0,\"memoryPeakBeforeCommandInMb\":99.727,\"memoryPeakAfterCommandInMB\":99.727} {\"correlation_id\":\"b3d4318e-3db2-413c-8fe1-8827ec834020\",\"trace_id\":\"870e454c-3a47-419e-88f1-2321d38b8937\"}\n[2026-05-07 11:37:14] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage before starting command {\"command\":\"activity:sync\",\"memoryBeforeCommandInMb\":62.0,\"memoryPeakBeforeCommandInMb\":99.727} {\"correlation_id\":\"3e5f2a44-295d-4c28-b5e9-1a649c1f49ae\",\"trace_id\":\"f4d85ec2-b1d2-4418-bb65-814c209e628f\"}\n[2026-05-07 11:37:15] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage for command {\"command\":\"activity:sync\",\"memoryBeforeCommandInMb\":62.0,\"memoryAfterCommandInMB\":62.0,\"memoryPeakBeforeCommandInMb\":99.727,\"memoryPeakAfterCommandInMB\":99.727} {\"correlation_id\":\"3e5f2a44-295d-4c28-b5e9-1a649c1f49ae\",\"trace_id\":\"f4d85ec2-b1d2-4418-bb65-814c209e628f\"}\n[2026-05-07 11:37:21] local.INFO: [Commands/AsyncUpdateEsEntities] Starting ES update worker {\"pid\":20519,\"workerId\":\"\",\"target\":\"activities\"} {\"correlation_id\":\"1a81e470-75ac-4728-9d45-9f879fc96c5b\",\"trace_id\":\"7184ea88-9f01-42bc-9508-8e4ad0c1204b\"}\n[2026-05-07 11:37:22] local.INFO: [Jiminny\\Jobs\\Mailbox\\CreateBatches] processed 2 inboxes and created 0 batches {\"userId\":null,\"batchSize\":30,\"maxBatches\":1000} {\"correlation_id\":\"798a3e91-233c-4b61-aa8e-80a7b84ac45f\",\"trace_id\":\"870e454c-3a47-419e-88f1-2321d38b8937\"}\n[2026-05-07 11:37:53] local.INFO: [Commands/AsyncUpdateEsEntities] Starting ES update worker {\"pid\":20661,\"workerId\":\"\",\"target\":\"activities\"} {\"correlation_id\":\"293fa4a8-dc12-452e-88a3-f7408f8ed676\",\"trace_id\":\"e3d77ea8-2ff3-4b4e-a388-8f21192e51f3\"}\n[2026-05-07 11:38:03] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage before starting command {\"command\":\"crm:sync-opportunity\",\"memoryBeforeCommandInMb\":62.0,\"memoryPeakBeforeCommandInMb\":99.727} {\"correlation_id\":\"eb6c1736-f06b-4fbc-80f1-e1283f2021fb\",\"trace_id\":\"cef5d248-2fbf-443a-915a-81470cec6a21\"}\n[2026-05-07 11:38:04] local.INFO: [SocialAccountService] Fetching token {\"socialAccountId\":1499,\"provider\":\"hubspot\"} {\"correlation_id\":\"eb6c1736-f06b-4fbc-80f1-e1283f2021fb\",\"trace_id\":\"cef5d248-2fbf-443a-915a-81470cec6a21\"}\n[2026-05-07 11:38:04] local.INFO: [SocialAccountService] Token retrieved {\"socialAccountId\":1499,\"provider\":\"hubspot\"} {\"correlation_id\":\"eb6c1736-f06b-4fbc-80f1-e1283f2021fb\",\"trace_id\":\"cef5d248-2fbf-443a-915a-81470cec6a21\"}\n[2026-05-07 11:38:04] local.INFO: [EncryptedTokenManager] Generating access token. {\"mode\":\"legacy\"} {\"correlation_id\":\"eb6c1736-f06b-4fbc-80f1-e1283f2021fb\",\"trace_id\":\"cef5d248-2fbf-443a-915a-81470cec6a21\"}\n[2026-05-07 11:38:04] local.INFO: [HubSpot] Syncing opportunities using strategy: lastModified {\"team\":2} {\"correlation_id\":\"eb6c1736-f06b-4fbc-80f1-e1283f2021fb\",\"trace_id\":\"cef5d248-2fbf-443a-915a-81470cec6a21\"}\n[2026-05-07 11:38:04] local.INFO: [Hubspot] Pagination completed {\"team_id\":2,\"endpoint\":\"https://api.hubapi.com/crm/v3/objects/deals/search\",\"total_requests\":1,\"total_records_fetched\":6,\"total_elapsed_seconds\":0.56,\"average_seconds_per_request\":0.56} {\"correlation_id\":\"eb6c1736-f06b-4fbc-80f1-e1283f2021fb\",\"trace_id\":\"cef5d248-2fbf-443a-915a-81470cec6a21\"}","role_description":"text entry area","is_enabled":true,"is_focused":true,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Sync Changes","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.008643617,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Hide This Notification","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.008643617,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Code changed:","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.042220745,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Hide","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.008643617,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"2","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.007978723,"height":0.0},"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"60","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.010305851,"height":0.0},"on_screen":false,"role_description":"text"},{"role":"AXButton","text":"Previous Highlighted Error","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.00731383,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Next Highlighted Error","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.006981383,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXTextArea","text":"<?php\n\ndeclare(strict_types=1);\n\nnamespace Jiminny\\Services\\Crm\\Hubspot;\n\nuse HubSpot\\Client\\Crm\\Deals\\ApiException as DealApiException;\nuse HubSpot\\Client\\Crm\\Contacts\\ApiException as ContactApiException;\nuse HubSpot\\Client\\Crm\\Companies\\ApiException as CompanyApiException;\nuse HubSpot\\Client\\Crm\\Contacts\\Model\\SimplePublicObjectWithAssociations as ContactsWithAssociations;\nuse HubSpot\\Client\\Crm\\Companies\\Model\\SimplePublicObjectWithAssociations as CompaniesWithAssociations;\nuse HubSpot\\Client\\Crm\\Deals\\Model\\SimplePublicObjectWithAssociations as DealWithAssociations;\nuse HubSpot\\Client\\Crm\\Objects\\Model\\SimplePublicObjectInput;\nuse HubSpot\\Client\\Crm\\Objects\\Model\\SimplePublicObjectWithAssociations as ObjectWithAssociations;\nuse HubSpot\\Client\\Crm\\Pipelines\\Model\\Error;\nuse HubSpot\\Client\\Crm\\Pipelines\\Model\\PipelineStage;\nuse HubSpot\\Client\\Crm\\Properties\\Model\\Property;\nuse HubSpot\\Discovery\\Discovery;\nuse Jiminny\\Component\\Utility\\Service\\ProviderRateLimiter;\nuse Jiminny\\Exceptions\\CrmException;\nuse Jiminny\\Exceptions\\RateLimitException;\nuse Jiminny\\Exceptions\\SocialAccountTokenInvalidException;\nuse Jiminny\\Jobs\\Crm\\NoteObject;\nuse Jiminny\\Models\\Crm\\Field;\nuse Jiminny\\Services\\Crm\\BaseClient;\nuse Jiminny\\Services\\Crm\\Hubspot\\DTO\\Response\\Owner;\nuse Jiminny\\Services\\SocialAccountService;\nuse SevenShores\\Hubspot\\Exceptions\\BadRequest;\nuse SevenShores\\Hubspot\\Exceptions\\HubspotException;\nuse SevenShores\\Hubspot\\Factory;\nuse SevenShores\\Hubspot\\Http\\Response;\nuse Jiminny\\Services\\Crm\\Hubspot\\Pagination\\HubspotPaginationService;\nuse Throwable;\n\n/**\n * @phpstan-type CrmFieldOption array{id:string, label:string, value?:string}\n */\nclass Client extends BaseClient implements HubspotClientInterface\n{\n public const string MIN_API_VERSION = '2';\n\n public const string BASE_URL = 'https://api.hubapi.com';\n\n public const int ASSOCIATIONS_BATCH_SIZE_LIMIT = 1000;\n\n private HubspotPaginationService $paginationService;\n private HubspotTokenManager $tokenManager;\n private ProviderRateLimiter $rateLimiter;\n\n public function __construct(\n SocialAccountService $socialAccountService,\n HubspotPaginationService $paginationService,\n HubspotTokenManager $tokenManager,\n ProviderRateLimiter $rateLimiter,\n ) {\n parent::__construct($socialAccountService);\n $this->paginationService = $paginationService;\n $this->tokenManager = $tokenManager;\n $this->rateLimiter = $rateLimiter;\n\n $this->setBaseUrl(self::BASE_URL);\n $this->setVersion(self::MIN_API_VERSION);\n }\n\n /**\n * Single entry point for every HubSpot API call. Enforces the per-portal\n * rate limit configured in the rate_limits table (morphed to the current\n * Configuration) and reacts to a real 429 from HubSpot by translating it\n * into a RateLimitException carrying Retry-After.\n *\n * Wrap any outbound HubSpot call (SDK or raw HTTP) like:\n *\n * $this->executeRequest(fn () => $this->getNewInstance()->crm()->...);\n *\n * @template T\n * @param callable(): T $apiCall\n * @return T\n *\n * @throws RateLimitException\n */\n private function executeRequest(callable $apiCall)\n {\n if (! $this->rateLimiter->canMakeRequest($this->config)) {\n $retryAfter = $this->rateLimiter->requestAvailableIn($this->config);\n\n $this->log->warning('[Hubspot] Rate limit exceeded, deferring request', [\n 'team_id' => $this->config->team_id,\n 'config_id' => $this->config->getId(),\n 'retry_after' => $retryAfter,\n ]);\n\n throw new RateLimitException(\n 'Hubspot rate limit reached for configuration ' . $this->config->getId(),\n $retryAfter,\n );\n }\n\n $this->rateLimiter->incrementRequestCount($this->config);\n\n try {\n return $apiCall();\n } catch (Throwable $e) {\n if ($this->isHubspotRateLimit($e)) {\n $retryAfter = $this->parseRetryAfter($e);\n\n $this->log->warning('[Hubspot] Received 429 from API', [\n 'team_id' => $this->config->team_id,\n 'config_id' => $this->config->getId(),\n 'retry_after' => $retryAfter,\n 'reason' => $e->getMessage(),\n ]);\n\n throw new RateLimitException('Hubspot returned 429', $retryAfter, $e);\n }\n\n throw $e;\n }\n }\n\n private function isHubspotRateLimit(Throwable $e): bool\n {\n return method_exists($e, 'getCode') && (int) $e->getCode() === 429;\n }\n\n private function parseRetryAfter(Throwable $e): int\n {\n if (method_exists($e, 'getResponseHeaders')) {\n $headers = $e->getResponseHeaders() ?: [];\n $value = $headers['Retry-After'] ?? $headers['retry-after'] ?? null;\n if (is_array($value)) {\n $value = $value[0] ?? null;\n }\n if (is_numeric($value)) {\n return (int) $value;\n }\n }\n\n return 10;\n }\n\n public function getMinimumApiVersion(): string\n {\n return self::MIN_API_VERSION;\n }\n\n public function getInstance(): Factory\n {\n return new Factory([\n 'key' => $this->accessToken,\n 'oauth2' => true,\n 'base_url' => $this->baseUrl,\n ]);\n }\n\n public function getNewInstance(): Discovery\n {\n return \\HubSpot\\Factory::createWithAccessToken($this->accessToken);\n }\n\n /**\n * Secondly and daily limits for Hubspot API\n *\n * Product Tier: Free & Starter | Professional & Enterprise | API add-on (any tier)\n * Burst: 100/10 seconds | 150/10 seconds | 200/10 seconds\n * Daily: 250,000 | 500,000 | 1,000,000\n *\n * Official documentation states: The search endpoints are rate limited to five requests per second.\n * Since with 5 RPS were still hitting secondly rate limits we lowered it to 4\n */\n public function getPaginatedData(array $payload, string $type, int $offset = 0): array\n {\n $total = 0;\n $lastId = null;\n $rows = [];\n foreach ($this->getPaginatedDataGenerator($payload, $type, $offset, $total, $lastId) as $row) {\n $rows[] = $row;\n }\n\n return ['results' => $rows, 'total' => $total, 'last_record' => $lastId];\n }\n\n /**\n * @throws HubspotException\n * @throws SocialAccountTokenInvalidException\n * @throws BadRequest\n */\n public function getPaginatedDataGenerator(\n array $payload,\n string $type,\n int $offset = 0,\n int &$total = 0,\n ?string &$lastRecordId = null\n ): \\Generator {\n return $this->paginationService->getPaginatedDataGenerator(\n $this,\n $payload,\n $type,\n $offset,\n $total,\n $lastRecordId\n );\n }\n\n /**\n * @throws DealApiException\n * @throws CrmException\n */\n public function getOpportunityById(string $crmId, array $fields): array\n {\n try {\n $deal = $this->executeRequest(fn () => $this->getNewInstance()->crm()->deals()->basicApi()->getById(\n $crmId,\n implode(',', $fields),\n 'companies,contacts'\n ));\n } catch (DealApiException $e) {\n $this->log->info('[Hubspot] Failed to fetch opportunity', [\n 'crm_id' => $crmId,\n 'reason' => $e->getMessage(),\n ]);\n\n throw $e;\n }\n\n if (! $deal instanceof DealWithAssociations) {\n throw new CrmException('Deal not found');\n }\n\n return [\n 'id' => $deal->getId(),\n 'properties' => $deal->getProperties(),\n 'associations' => $deal->getAssociations(),\n ];\n }\n\n /**\n * Generic batch read method for HubSpot objects\n *\n * @param string $objectType The object type ('deals', 'companies', 'contacts')\n * @param array<string> $crmIds Array of HubSpot object IDs (max 100)\n * @param array<string> $fields Array of property names to fetch\n *\n * @return array<string, array> Array keyed by CRM ID with object data\n */\n private function batchReadObjects(string $objectType, array $crmIds, array $fields): array\n {\n if (empty($crmIds)) {\n return [];\n }\n\n $this->validateBatchSize($objectType, $crmIds);\n $this->ensureValidToken();\n\n try {\n $batchConfig = $this->createBatchConfiguration($objectType);\n $batchReadRequest = $this->prepareBatchRequest($batchConfig, $crmIds, $fields);\n $response = $batchConfig['api']->read($batchReadRequest);\n\n $this->validateApiResponse($response, $objectType);\n\n $results = $this->processApiResults($response);\n $this->logBatchResults($objectType, $crmIds, $results);\n\n return $results;\n } catch (\\Throwable $e) {\n $this->handleBatchError($e, $objectType, $crmIds);\n }\n }\n\n private function validateBatchSize(string $objectType, array $crmIds): void\n {\n if (count($crmIds) > 100) {\n throw new \\InvalidArgumentException(\"Batch size cannot exceed 100 {$objectType}\");\n }\n }\n\n private function createBatchConfiguration(string $objectType): array\n {\n $configurations = [\n 'deals' => [\n 'batchReadRequest' => new \\HubSpot\\Client\\Crm\\Deals\\Model\\BatchReadInputSimplePublicObjectId(),\n 'inputClass' => \\HubSpot\\Client\\Crm\\Deals\\Model\\SimplePublicObjectId::class,\n 'api' => $this->getNewInstance()->crm()->deals()->batchApi(),\n ],\n 'companies' => [\n 'batchReadRequest' => new \\HubSpot\\Client\\Crm\\Companies\\Model\\BatchReadInputSimplePublicObjectId(),\n 'inputClass' => \\HubSpot\\Client\\Crm\\Companies\\Model\\SimplePublicObjectId::class,\n 'api' => $this->getNewInstance()->crm()->companies()->batchApi(),\n ],\n 'contacts' => [\n 'batchReadRequest' => new \\HubSpot\\Client\\Crm\\Contacts\\Model\\BatchReadInputSimplePublicObjectId(),\n 'inputClass' => \\HubSpot\\Client\\Crm\\Contacts\\Model\\SimplePublicObjectId::class,\n 'api' => $this->getNewInstance()->crm()->contacts()->batchApi(),\n ],\n ];\n\n if (! isset($configurations[$objectType])) {\n throw new \\InvalidArgumentException(\"Unsupported object type: {$objectType}\");\n }\n\n return $configurations[$objectType];\n }\n\n private function prepareBatchRequest(array $batchConfig, array $crmIds, array $fields): object\n {\n $batchReadRequest = $batchConfig['batchReadRequest'];\n $inputClass = $batchConfig['inputClass'];\n\n $inputs = array_map(function ($crmId) use ($inputClass) {\n $input = new $inputClass();\n $input->setId($crmId);\n\n return $input;\n }, $crmIds);\n\n $batchReadRequest->setInputs($inputs);\n $batchReadRequest->setProperties($fields);\n\n return $batchReadRequest;\n }\n\n private function validateApiResponse($response, string $objectType): void\n {\n if (! $response) {\n throw new CrmException(\"HubSpot API returned null response for {$objectType} batch read\");\n }\n }\n\n private function processApiResults($response): array\n {\n $results = [];\n $responseResults = $response->getResults();\n\n if ($responseResults) {\n foreach ($responseResults as $object) {\n if ($object && $object->getId()) {\n $results[$object->getId()] = [\n 'id' => $object->getId(),\n 'properties' => $object->getProperties() ?: [],\n ];\n }\n }\n }\n\n return $results;\n }\n\n private function logBatchResults(string $objectType, array $crmIds, array $results): void\n {\n $this->log->info(\"[HubSpot] Batch fetched {$objectType}\", [\n 'requested_count' => count($crmIds),\n 'returned_count' => count($results),\n 'crm_ids' => $crmIds,\n ]);\n }\n\n private function handleBatchError(\\Throwable $e, string $objectType, array $crmIds): void\n {\n $errorMessage = $e->getMessage() ?: 'Unknown error';\n $errorTrace = $e->getTraceAsString() ?: 'No trace available';\n\n $this->log->error(\"[HubSpot] Failed to batch fetch {$objectType}\", [\n 'crm_ids' => $crmIds,\n 'error' => $errorMessage,\n 'trace' => $errorTrace,\n ]);\n\n throw new CrmException(\"Failed to batch fetch {$objectType}: \" . $errorMessage);\n }\n\n /**\n * Batch read multiple opportunities by their CRM IDs\n *\n * @param array<string> $crmIds Array of HubSpot deal IDs (max 100)\n * @param array<string> $fields Array of property names to fetch\n *\n * @return array<string, array> Array keyed by CRM ID with opportunity data\n */\n public function getOpportunitiesByIds(array $crmIds, array $fields): array\n {\n return $this->batchReadObjects('deals', $crmIds, $fields);\n }\n\n /**\n * Batch read multiple companies by their CRM IDs\n *\n * @param array<string> $crmIds Array of HubSpot company IDs (max 100)\n * @param array<string> $fields Array of property names to fetch\n *\n * @return array<string, array> Array keyed by CRM ID with company data\n */\n public function getCompaniesByIds(array $crmIds, array $fields): array\n {\n return $this->batchReadObjects('companies', $crmIds, $fields);\n }\n\n /**\n * Batch read multiple contacts by their CRM IDs\n *\n * @param array<string> $crmIds Array of HubSpot contact IDs (max 100)\n * @param array<string> $fields Array of property names to fetch\n *\n * @return array<string, array> Array keyed by CRM ID with contact data\n */\n public function getContactsByIds(array $crmIds, array $fields): array\n {\n return $this->batchReadObjects('contacts', $crmIds, $fields);\n }\n\n /**\n * @throws CompanyApiException\n * @throws CrmException\n */\n public function getAccountById(string $crmId, array $fields): array\n {\n try {\n $company = $this->getNewInstance()->crm()->companies()->basicApi()->getById(\n $crmId,\n implode(',', $fields),\n );\n } catch (CompanyApiException $e) {\n $this->log->info('[Hubspot] Failed to fetch account', [\n 'crm_id' => $crmId,\n 'reason' => $e->getMessage(),\n ]);\n\n throw $e;\n }\n\n if (! $company instanceof CompaniesWithAssociations) {\n throw new CrmException('Account not found');\n }\n\n return [\n 'id' => $company->getId(),\n 'properties' => $company->getProperties(),\n ];\n }\n\n /**\n * @throws ContactApiException\n * @throws CrmException\n */\n public function getContactById(string $crmId, array $fields): array\n {\n try {\n $contact = $this->getNewInstance()->crm()->contacts()->basicApi()->getById(\n $crmId,\n implode(',', $fields)\n );\n } catch (ContactApiException $e) {\n $this->log->info('[Hubspot] Failed to fetch contact', [\n 'crm_id' => $crmId,\n 'reason' => $e->getMessage(),\n ]);\n\n throw $e;\n }\n\n if (! $contact instanceof ContactsWithAssociations) {\n throw new CrmException('Contact not found');\n }\n\n return [\n 'id' => $contact->getId(),\n 'properties' => $contact->getProperties(),\n ];\n }\n\n /**\n * This is email search request that Hubspot offers as GET (more generous quota)\n */\n public function getContactByEmail(string $email, array $fields = []): array\n {\n try {\n $contact = $this->getNewInstance()->crm()->contacts()->basicApi()->getById(\n $email,\n implode(',', $fields),\n null,\n false,\n 'email'\n );\n\n return [\n 'id' => $contact->getId(),\n 'properties' => $contact->getProperties(),\n ];\n } catch (ContactApiException $e) {\n $this->log->info('[Hubspot] Failed to fetch contact', [\n 'email' => $email,\n 'reason' => $e->getMessage(),\n ]);\n\n return [];\n }\n }\n\n /**\n * @throws CrmException\n */\n public function fetchProperty(string $objectType, string $propertyId): Property\n {\n $result = $this->getNewInstance()->crm()->properties()->coreApi()->getByName($objectType, $propertyId);\n\n if (! $result instanceof Property) {\n $this->log->error('[Hubspot] Failed to fetch property', [\n 'object_type' => $objectType,\n 'property_id' => $propertyId,\n 'reason' => $result->getMessage(),\n ]);\n\n throw new CrmException('Failed to fetch property');\n }\n\n return $result;\n }\n\n /**\n * @return array<CrmFieldOption>\n */\n public function fetchPropertyOptions(string $objectType, string $propertyId): array\n {\n /** @var array<CrmFieldOption> */\n return $this->fetchProperty($objectType, $propertyId)->getOptions();\n }\n\n /**\n * @return array<array{id:string, label:string, deleted:bool}>\n */\n public function fetchCallDispositions(): array\n {\n /** @var Response $response */\n $response = $this->getInstance()->engagements()->getCallDispositions();\n\n /**\n * @var array<array{\n * id:string,\n * label:string,\n * deleted: bool\n * }>\n */\n return $response->toArray();\n }\n\n /**\n * @return array<CrmFieldOption>\n */\n public function fetchOpportunityPipelineStages(): array\n {\n $stages = [];\n $apiResponse = $this->getNewInstance()->crm()->pipelines()->pipelinesApi()->getAll('deals');\n\n if ($apiResponse instanceof Error) {\n $this->log->error('[Hubspot] Failed to fetch opportunity pipelines', [\n 'reason' => $apiResponse->getMessage(),\n ]);\n\n return [];\n }\n\n foreach ($apiResponse->getResults() as $pipeline) {\n $pipelineStages = array_map(\n static function (PipelineStage $stage) {\n return [\n 'id' => $stage->getId(),\n 'label' => $stage->getLabel(),\n ];\n },\n $pipeline->getStages()\n );\n\n $stages = array_merge($stages, $pipelineStages);\n }\n\n return $stages;\n }\n\n public function fetchOpportunityPipelines(): array\n {\n $pipelines = [];\n\n try {\n $apiResponse = $this->makeRequest('/crm/v3/pipelines/deals');\n } catch (\\Exception $e) {\n $this->log->info('[Hubspot] Failed to fetch opportunity pipelines', [\n 'reason' => $e->getMessage(),\n ]);\n\n return [];\n }\n\n $response = $apiResponse->toArray();\n\n foreach ($response['results'] as $pipeline) {\n $pipelines[] = [\n 'id' => $pipeline['id'],\n 'label' => $pipeline['label'],\n ];\n }\n\n return $pipelines;\n }\n\n /**\n * @return array<CrmFieldOption>\n */\n public function fetchMeetingOutcomeFieldOptions(Field $field): array\n {\n return $field->getCrmProviderId() === 'meetingOutcome'\n ? $this->fetchMeetingOutcomeTypes()\n : $this->fetchCallActivityTypes();\n }\n\n public function fetchMeetingOutcomeTypes(): array\n {\n return $this->extractMeetingTypeOptions(\n 'https://api.hubapi.com/crm/v3/properties/meeting/hs_meeting_outcome'\n );\n }\n\n public function fetchCallActivityTypes(): array\n {\n return $this->extractMeetingTypeOptions(\n 'https://api.hubapi.com/crm/v3/properties/call/hs_activity_type'\n );\n }\n\n private function extractMeetingTypeOptions(string $endpoint): array\n {\n /** @var Response $response */\n $response = $this->getInstance()\n ->getClient()\n ->request('GET', $endpoint);\n\n /**\n * @var array<array{\n * value: string,\n * label: string,\n * displayOrder: int\n * }> $optionData\n */\n $optionData = $response->toArray()['options'] ?? [];\n\n $options = [];\n foreach ($optionData as $item) {\n $options[] = [\n 'id' => $item['value'],\n 'value' => $item['value'],\n 'label' => $item['label'],\n 'display_order' => $item['displayOrder'],\n ];\n }\n\n return $options;\n }\n\n /**\n * @return array<CrmFieldOption>\n */\n public function fetchDispositionFieldOptions(): array\n {\n $options = [];\n\n $dispositions = $this->fetchCallDispositions();\n\n foreach ($dispositions as $disposition) {\n if ($disposition['deleted'] !== false) {\n continue;\n }\n\n $option['value'] = $disposition['id'];\n $option['id'] = $disposition['id'];\n $option['label'] = $disposition['label'];\n\n $options[] = $option;\n }\n\n return $options;\n }\n\n /**\n * @return array<CrmFieldOption>\n */\n public function fetchOpportunityFieldOptions(Field $field): array\n {\n if ($field->isStageField()) {\n return $this->fetchOpportunityPipelineStages();\n }\n\n if ($field->isPipelineField()) {\n return $this->fetchOpportunityPipelines();\n }\n\n return $this->fetchPropertyOptions('deals', $field->getCrmProviderId());\n }\n\n /**\n * @throws BadRequest\n * @throws HubspotException\n */\n public function makeRequest(string $endpoint, $method = 'GET', $payload = [], ?string $queryString = null)\n {\n $endpoint = self::BASE_URL . $endpoint;\n\n if ($method === 'GET') {\n $response = $this->getInstance()->getClient()?->request(\n method: $method,\n endpoint: $endpoint,\n query_string: $queryString\n );\n } else {\n $response = $this->getInstance()->getClient()->request($method, $endpoint, [\n 'json' => ($payload),\n ]);\n }\n\n $max = $response->getHeaderLine('X-HubSpot-RateLimit-Max'); // \"110\"\n $remaining = $response->getHeaderLine('X-HubSpot-RateLimit-Remaining'); // \"109\"\n $interval = $response->getHeaderLine('X-HubSpot-RateLimit-Interval-Milliseconds'); // \"10000\"\n $body = json_decode((string) $response->getBody(), true);\n\n \\Illuminate\\Support\\Facades\\Log::channel('custom_channel')->info('$max ' . PHP_EOL . print_r($max, true));\n \\Illuminate\\Support\\Facades\\Log::channel('custom_channel')->info('$remaining ' . PHP_EOL . print_r($remaining, true));\n \\Illuminate\\Support\\Facades\\Log::channel('custom_channel')->info('$interval ' . PHP_EOL . print_r($interval, true));\n \\Illuminate\\Support\\Facades\\Log::channel('custom_channel')->info('$body ' . PHP_EOL . print_r($body, true));\n\n return $response;\n }\n\n /**\n * @throws BadRequest\n * @throws HubspotException\n */\n public function createMeeting(array $payload): Response\n {\n $endpoint = '/crm/v3/objects/meetings';\n\n return $this->makeRequest($endpoint, 'POST', $payload);\n }\n\n /**\n * @throws BadRequest\n * @throws HubspotException\n */\n public function updateMeeting(string $meetingId, array $payload): Response\n {\n $endpoint = '/crm/v3/objects/meetings/' . $meetingId;\n\n return $this->makeRequest($endpoint, 'PATCH', $payload);\n }\n\n /**\n * @throws \\Exception\n */\n public function createNote(\n string $body,\n string $ownerId,\n int $timestamp,\n string $objectId,\n NoteObject $noteObject\n ): ?string {\n try {\n $noteInput = new SimplePublicObjectInput([\n 'properties' => [\n 'hs_note_body' => $body,\n 'hubspot_owner_id' => $ownerId,\n 'hs_timestamp' => $timestamp,\n ],\n ]);\n\n // Create note\n $note = $this->getNewInstance()->crm()->objects()->basicApi()->create('note', $noteInput);\n\n $this->getNewInstance()->crm()->objects()->associationsApi()->create(\n 'note',\n $note->getId(),\n $this->getNoteObject($noteObject),\n $objectId,\n $this->getNoteAssociationType($noteObject),\n );\n\n return $note->getId();\n } catch (\\Exception $e) {\n $this->log->error('[Hubspot] Failed to create note', [\n 'objectId' => $objectId,\n 'noteObject' => $noteObject->getObjectType(),\n 'reason' => $e->getMessage(),\n ]);\n\n \\Sentry::captureException($e);\n }\n\n return null;\n }\n\n public function updateEngagement(string $objectId, array $engagement, array $metadata): void\n {\n $this->getInstance()->engagements()->update($objectId, $engagement, $metadata);\n }\n\n public function getEngagementData(string $engagementId): array\n {\n $engagement = $this->getInstance()->engagements()->get($engagementId);\n\n return $engagement->toArray();\n }\n\n public function createEngagement(array $engagement, array $associations, array $metadata): Response\n {\n return $this->getInstance()\n ->engagements()\n ->create($engagement, $associations, $metadata);\n }\n\n public function isUnauthorizedException(\\Exception $e): bool\n {\n // Check for specific HubSpot API exception types first\n if ($e instanceof BadRequest) {\n // BadRequest can contain 401 status codes\n return $e->getCode() === 401;\n }\n\n // Check for HTTP client exceptions with status codes\n if ($e instanceof \\GuzzleHttp\\Exception\\RequestException && $e->hasResponse()) {\n $response = $e->getResponse();\n if ($response !== null) {\n return $response->getStatusCode() === 401;\n }\n }\n\n // Check for Guzzle HTTP exceptions\n if ($e instanceof \\GuzzleHttp\\Exception\\ClientException) {\n return $e->getCode() === 401;\n }\n\n // Fallback to string matching as last resort, but be more specific\n $message = strtolower($e->getMessage());\n\n return str_contains($message, '401 unauthorized') ||\n str_contains($message, 'http 401') ||\n str_contains($message, 'status code 401') ||\n (preg_match('/\\b401\\b/', $message) && str_contains($message, 'unauthorized'));\n }\n\n /**\n * Validates and refreshes the access token if needed before API requests.\n * This ensures long-running processes don't fail due to token expiration.\n *\n * @throws SocialAccountTokenInvalidException\n */\n public function ensureValidToken(): void\n {\n if ($this->oauthAccount === null) {\n return;\n }\n\n $newToken = $this->tokenManager->ensureValidToken($this->oauthAccount);\n if ($newToken !== null) {\n $this->accessToken = $newToken;\n }\n }\n\n public function getConfig()\n {\n return $this->config;\n }\n\n // returns only active (archived=false)\n public function getOwners(): array\n {\n return $this->getNewInstance()->crm()->owners()->getAll();\n }\n\n /**\n * @param bool $archived\n *\n * @return array<Owner>|[]\n */\n public function getOwnersArchived(bool $archived = true): array\n {\n $endpoint = '/crm/v3/owners';\n $queryParams = [\n 'archived' => $archived ? 'true' : 'false',\n ];\n $queryString = http_build_query($queryParams);\n\n $owners = [];\n\n try {\n $response = $this->makeRequest(endpoint: $endpoint, queryString: $queryString);\n $responseData = $response?->toArray();\n\n foreach ($responseData['results'] as $result) {\n try {\n $owners[] = Owner::create($result);\n } catch (Throwable $e) {\n $this->log->error('[HubSpot] Failed to process owner data', [\n 'result' => $result,\n 'error' => $e->getMessage(),\n ]);\n\n continue;\n }\n }\n } catch (Throwable $e) {\n $this->log->error('HubSpot] Failed to fetch owners', [\n 'archived' => $archived,\n 'error' => $e->getMessage(),\n ]);\n\n return [];\n }\n\n return $owners;\n }\n\n public function getMeeting(string $engagementId): ObjectWithAssociations\n {\n return $this->getNewInstance()->crm()->objects()->basicApi()\n ->getById('meeting', $engagementId, null, 'contact,company,deal');\n }\n\n public function deleteEngagement(string $engagementId): void\n {\n $this->getInstance()->engagements()->delete((int) $engagementId);\n }\n\n public function getAssociationsData(array $ids, string $fromObject, string $toObject): array\n {\n $associationData = [];\n $idChunks = array_chunk($ids, self::ASSOCIATIONS_BATCH_SIZE_LIMIT);\n\n foreach ($idChunks as $idChunk) {\n try {\n $batchInput = new \\HubSpot\\Client\\Crm\\Associations\\Model\\BatchInputPublicObjectId();\n $batchInput->setInputs(array_map(function ($id) {\n $publicObjectId = new \\HubSpot\\Client\\Crm\\Associations\\Model\\PublicObjectId();\n $publicObjectId->setId($id);\n\n return $publicObjectId;\n }, $idChunk));\n\n $associatedObjectsData = $this\n ->getNewInstance()\n ->crm()\n ->associations()\n ->batchApi()\n ->read($fromObject, $toObject, $batchInput);\n\n if ($associatedObjectsData instanceof \\HubSpot\\Client\\Crm\\Associations\\Model\\BatchResponsePublicAssociationMulti) {\n foreach ($associatedObjectsData->getResults() as $association) {\n $from = $association->getFrom()->getId();\n $toAssociations = $association->getTo();\n\n if (! empty($toAssociations)) {\n $associationData[$from] = array_map(function ($item) {\n return $item->getId();\n }, $toAssociations);\n }\n }\n }\n } catch (\\Exception $e) {\n $this->log->error('[Hubspot] Failed to fetch associations', [\n 'from_object' => $fromObject,\n 'to_object' => $toObject,\n 'reason' => $e->getMessage(),\n ]);\n }\n }\n\n return $associationData;\n }\n\n /**\n * @throws \\Exception\n */\n private function getNoteAssociationType(NoteObject $noteObject): string\n {\n return match($noteObject) {\n NoteObject::Opportunity => 'note_to_deal',\n NoteObject::Lead, NoteObject::Contact => 'note_to_contact', // or 'note_to_lead' if your portal supports it\n NoteObject::Account => 'note_to_company',\n NoteObject::Call, NoteObject::Event => throw new \\Exception('Not supported'),\n };\n }\n\n /**\n * @throws \\Exception\n */\n private function getNoteObject(NoteObject $noteObject): string\n {\n return match($noteObject) {\n NoteObject::Opportunity => 'deal',\n NoteObject::Lead, NoteObject::Contact => 'contact',\n NoteObject::Account => 'company',\n NoteObject::Call, NoteObject::Event => throw new \\Exception('Not supported'),\n };\n }\n\n public function addAssociations(string $objectType, string $associationType, array $payload): Response\n {\n $endpoint = \"/crm/v4/associations/$objectType/$associationType/batch/create\";\n\n return $this->makeRequest($endpoint, 'POST', $payload);\n }\n\n public function removeAssociations(string $objectType, string $associationType, array $payload): Response\n {\n $endpoint = \"/crm/v4/associations/$objectType/$associationType/batch/archive\";\n\n return $this->makeRequest($endpoint, 'POST', $payload);\n }\n}","depth":4,"on_screen":true,"value":"<?php\n\ndeclare(strict_types=1);\n\nnamespace Jiminny\\Services\\Crm\\Hubspot;\n\nuse HubSpot\\Client\\Crm\\Deals\\ApiException as DealApiException;\nuse HubSpot\\Client\\Crm\\Contacts\\ApiException as ContactApiException;\nuse HubSpot\\Client\\Crm\\Companies\\ApiException as CompanyApiException;\nuse HubSpot\\Client\\Crm\\Contacts\\Model\\SimplePublicObjectWithAssociations as ContactsWithAssociations;\nuse HubSpot\\Client\\Crm\\Companies\\Model\\SimplePublicObjectWithAssociations as CompaniesWithAssociations;\nuse HubSpot\\Client\\Crm\\Deals\\Model\\SimplePublicObjectWithAssociations as DealWithAssociations;\nuse HubSpot\\Client\\Crm\\Objects\\Model\\SimplePublicObjectInput;\nuse HubSpot\\Client\\Crm\\Objects\\Model\\SimplePublicObjectWithAssociations as ObjectWithAssociations;\nuse HubSpot\\Client\\Crm\\Pipelines\\Model\\Error;\nuse HubSpot\\Client\\Crm\\Pipelines\\Model\\PipelineStage;\nuse HubSpot\\Client\\Crm\\Properties\\Model\\Property;\nuse HubSpot\\Discovery\\Discovery;\nuse Jiminny\\Component\\Utility\\Service\\ProviderRateLimiter;\nuse Jiminny\\Exceptions\\CrmException;\nuse Jiminny\\Exceptions\\RateLimitException;\nuse Jiminny\\Exceptions\\SocialAccountTokenInvalidException;\nuse Jiminny\\Jobs\\Crm\\NoteObject;\nuse Jiminny\\Models\\Crm\\Field;\nuse Jiminny\\Services\\Crm\\BaseClient;\nuse Jiminny\\Services\\Crm\\Hubspot\\DTO\\Response\\Owner;\nuse Jiminny\\Services\\SocialAccountService;\nuse SevenShores\\Hubspot\\Exceptions\\BadRequest;\nuse SevenShores\\Hubspot\\Exceptions\\HubspotException;\nuse SevenShores\\Hubspot\\Factory;\nuse SevenShores\\Hubspot\\Http\\Response;\nuse Jiminny\\Services\\Crm\\Hubspot\\Pagination\\HubspotPaginationService;\nuse Throwable;\n\n/**\n * @phpstan-type CrmFieldOption array{id:string, label:string, value?:string}\n */\nclass Client extends BaseClient implements HubspotClientInterface\n{\n public const string MIN_API_VERSION = '2';\n\n public const string BASE_URL = 'https://api.hubapi.com';\n\n public const int ASSOCIATIONS_BATCH_SIZE_LIMIT = 1000;\n\n private HubspotPaginationService $paginationService;\n private HubspotTokenManager $tokenManager;\n private ProviderRateLimiter $rateLimiter;\n\n public function __construct(\n SocialAccountService $socialAccountService,\n HubspotPaginationService $paginationService,\n HubspotTokenManager $tokenManager,\n ProviderRateLimiter $rateLimiter,\n ) {\n parent::__construct($socialAccountService);\n $this->paginationService = $paginationService;\n $this->tokenManager = $tokenManager;\n $this->rateLimiter = $rateLimiter;\n\n $this->setBaseUrl(self::BASE_URL);\n $this->setVersion(self::MIN_API_VERSION);\n }\n\n /**\n * Single entry point for every HubSpot API call. Enforces the per-portal\n * rate limit configured in the rate_limits table (morphed to the current\n * Configuration) and reacts to a real 429 from HubSpot by translating it\n * into a RateLimitException carrying Retry-After.\n *\n * Wrap any outbound HubSpot call (SDK or raw HTTP) like:\n *\n * $this->executeRequest(fn () => $this->getNewInstance()->crm()->...);\n *\n * @template T\n * @param callable(): T $apiCall\n * @return T\n *\n * @throws RateLimitException\n */\n private function executeRequest(callable $apiCall)\n {\n if (! $this->rateLimiter->canMakeRequest($this->config)) {\n $retryAfter = $this->rateLimiter->requestAvailableIn($this->config);\n\n $this->log->warning('[Hubspot] Rate limit exceeded, deferring request', [\n 'team_id' => $this->config->team_id,\n 'config_id' => $this->config->getId(),\n 'retry_after' => $retryAfter,\n ]);\n\n throw new RateLimitException(\n 'Hubspot rate limit reached for configuration ' . $this->config->getId(),\n $retryAfter,\n );\n }\n\n $this->rateLimiter->incrementRequestCount($this->config);\n\n try {\n return $apiCall();\n } catch (Throwable $e) {\n if ($this->isHubspotRateLimit($e)) {\n $retryAfter = $this->parseRetryAfter($e);\n\n $this->log->warning('[Hubspot] Received 429 from API', [\n 'team_id' => $this->config->team_id,\n 'config_id' => $this->config->getId(),\n 'retry_after' => $retryAfter,\n 'reason' => $e->getMessage(),\n ]);\n\n throw new RateLimitException('Hubspot returned 429', $retryAfter, $e);\n }\n\n throw $e;\n }\n }\n\n private function isHubspotRateLimit(Throwable $e): bool\n {\n return method_exists($e, 'getCode') && (int) $e->getCode() === 429;\n }\n\n private function parseRetryAfter(Throwable $e): int\n {\n if (method_exists($e, 'getResponseHeaders')) {\n $headers = $e->getResponseHeaders() ?: [];\n $value = $headers['Retry-After'] ?? $headers['retry-after'] ?? null;\n if (is_array($value)) {\n $value = $value[0] ?? null;\n }\n if (is_numeric($value)) {\n return (int) $value;\n }\n }\n\n return 10;\n }\n\n public function getMinimumApiVersion(): string\n {\n return self::MIN_API_VERSION;\n }\n\n public function getInstance(): Factory\n {\n return new Factory([\n 'key' => $this->accessToken,\n 'oauth2' => true,\n 'base_url' => $this->baseUrl,\n ]);\n }\n\n public function getNewInstance(): Discovery\n {\n return \\HubSpot\\Factory::createWithAccessToken($this->accessToken);\n }\n\n /**\n * Secondly and daily limits for Hubspot API\n *\n * Product Tier: Free & Starter | Professional & Enterprise | API add-on (any tier)\n * Burst: 100/10 seconds | 150/10 seconds | 200/10 seconds\n * Daily: 250,000 | 500,000 | 1,000,000\n *\n * Official documentation states: The search endpoints are rate limited to five requests per second.\n * Since with 5 RPS were still hitting secondly rate limits we lowered it to 4\n */\n public function getPaginatedData(array $payload, string $type, int $offset = 0): array\n {\n $total = 0;\n $lastId = null;\n $rows = [];\n foreach ($this->getPaginatedDataGenerator($payload, $type, $offset, $total, $lastId) as $row) {\n $rows[] = $row;\n }\n\n return ['results' => $rows, 'total' => $total, 'last_record' => $lastId];\n }\n\n /**\n * @throws HubspotException\n * @throws SocialAccountTokenInvalidException\n * @throws BadRequest\n */\n public function getPaginatedDataGenerator(\n array $payload,\n string $type,\n int $offset = 0,\n int &$total = 0,\n ?string &$lastRecordId = null\n ): \\Generator {\n return $this->paginationService->getPaginatedDataGenerator(\n $this,\n $payload,\n $type,\n $offset,\n $total,\n $lastRecordId\n );\n }\n\n /**\n * @throws DealApiException\n * @throws CrmException\n */\n public function getOpportunityById(string $crmId, array $fields): array\n {\n try {\n $deal = $this->executeRequest(fn () => $this->getNewInstance()->crm()->deals()->basicApi()->getById(\n $crmId,\n implode(',', $fields),\n 'companies,contacts'\n ));\n } catch (DealApiException $e) {\n $this->log->info('[Hubspot] Failed to fetch opportunity', [\n 'crm_id' => $crmId,\n 'reason' => $e->getMessage(),\n ]);\n\n throw $e;\n }\n\n if (! $deal instanceof DealWithAssociations) {\n throw new CrmException('Deal not found');\n }\n\n return [\n 'id' => $deal->getId(),\n 'properties' => $deal->getProperties(),\n 'associations' => $deal->getAssociations(),\n ];\n }\n\n /**\n * Generic batch read method for HubSpot objects\n *\n * @param string $objectType The object type ('deals', 'companies', 'contacts')\n * @param array<string> $crmIds Array of HubSpot object IDs (max 100)\n * @param array<string> $fields Array of property names to fetch\n *\n * @return array<string, array> Array keyed by CRM ID with object data\n */\n private function batchReadObjects(string $objectType, array $crmIds, array $fields): array\n {\n if (empty($crmIds)) {\n return [];\n }\n\n $this->validateBatchSize($objectType, $crmIds);\n $this->ensureValidToken();\n\n try {\n $batchConfig = $this->createBatchConfiguration($objectType);\n $batchReadRequest = $this->prepareBatchRequest($batchConfig, $crmIds, $fields);\n $response = $batchConfig['api']->read($batchReadRequest);\n\n $this->validateApiResponse($response, $objectType);\n\n $results = $this->processApiResults($response);\n $this->logBatchResults($objectType, $crmIds, $results);\n\n return $results;\n } catch (\\Throwable $e) {\n $this->handleBatchError($e, $objectType, $crmIds);\n }\n }\n\n private function validateBatchSize(string $objectType, array $crmIds): void\n {\n if (count($crmIds) > 100) {\n throw new \\InvalidArgumentException(\"Batch size cannot exceed 100 {$objectType}\");\n }\n }\n\n private function createBatchConfiguration(string $objectType): array\n {\n $configurations = [\n 'deals' => [\n 'batchReadRequest' => new \\HubSpot\\Client\\Crm\\Deals\\Model\\BatchReadInputSimplePublicObjectId(),\n 'inputClass' => \\HubSpot\\Client\\Crm\\Deals\\Model\\SimplePublicObjectId::class,\n 'api' => $this->getNewInstance()->crm()->deals()->batchApi(),\n ],\n 'companies' => [\n 'batchReadRequest' => new \\HubSpot\\Client\\Crm\\Companies\\Model\\BatchReadInputSimplePublicObjectId(),\n 'inputClass' => \\HubSpot\\Client\\Crm\\Companies\\Model\\SimplePublicObjectId::class,\n 'api' => $this->getNewInstance()->crm()->companies()->batchApi(),\n ],\n 'contacts' => [\n 'batchReadRequest' => new \\HubSpot\\Client\\Crm\\Contacts\\Model\\BatchReadInputSimplePublicObjectId(),\n 'inputClass' => \\HubSpot\\Client\\Crm\\Contacts\\Model\\SimplePublicObjectId::class,\n 'api' => $this->getNewInstance()->crm()->contacts()->batchApi(),\n ],\n ];\n\n if (! isset($configurations[$objectType])) {\n throw new \\InvalidArgumentException(\"Unsupported object type: {$objectType}\");\n }\n\n return $configurations[$objectType];\n }\n\n private function prepareBatchRequest(array $batchConfig, array $crmIds, array $fields): object\n {\n $batchReadRequest = $batchConfig['batchReadRequest'];\n $inputClass = $batchConfig['inputClass'];\n\n $inputs = array_map(function ($crmId) use ($inputClass) {\n $input = new $inputClass();\n $input->setId($crmId);\n\n return $input;\n }, $crmIds);\n\n $batchReadRequest->setInputs($inputs);\n $batchReadRequest->setProperties($fields);\n\n return $batchReadRequest;\n }\n\n private function validateApiResponse($response, string $objectType): void\n {\n if (! $response) {\n throw new CrmException(\"HubSpot API returned null response for {$objectType} batch read\");\n }\n }\n\n private function processApiResults($response): array\n {\n $results = [];\n $responseResults = $response->getResults();\n\n if ($responseResults) {\n foreach ($responseResults as $object) {\n if ($object && $object->getId()) {\n $results[$object->getId()] = [\n 'id' => $object->getId(),\n 'properties' => $object->getProperties() ?: [],\n ];\n }\n }\n }\n\n return $results;\n }\n\n private function logBatchResults(string $objectType, array $crmIds, array $results): void\n {\n $this->log->info(\"[HubSpot] Batch fetched {$objectType}\", [\n 'requested_count' => count($crmIds),\n 'returned_count' => count($results),\n 'crm_ids' => $crmIds,\n ]);\n }\n\n private function handleBatchError(\\Throwable $e, string $objectType, array $crmIds): void\n {\n $errorMessage = $e->getMessage() ?: 'Unknown error';\n $errorTrace = $e->getTraceAsString() ?: 'No trace available';\n\n $this->log->error(\"[HubSpot] Failed to batch fetch {$objectType}\", [\n 'crm_ids' => $crmIds,\n 'error' => $errorMessage,\n 'trace' => $errorTrace,\n ]);\n\n throw new CrmException(\"Failed to batch fetch {$objectType}: \" . $errorMessage);\n }\n\n /**\n * Batch read multiple opportunities by their CRM IDs\n *\n * @param array<string> $crmIds Array of HubSpot deal IDs (max 100)\n * @param array<string> $fields Array of property names to fetch\n *\n * @return array<string, array> Array keyed by CRM ID with opportunity data\n */\n public function getOpportunitiesByIds(array $crmIds, array $fields): array\n {\n return $this->batchReadObjects('deals', $crmIds, $fields);\n }\n\n /**\n * Batch read multiple companies by their CRM IDs\n *\n * @param array<string> $crmIds Array of HubSpot company IDs (max 100)\n * @param array<string> $fields Array of property names to fetch\n *\n * @return array<string, array> Array keyed by CRM ID with company data\n */\n public function getCompaniesByIds(array $crmIds, array $fields): array\n {\n return $this->batchReadObjects('companies', $crmIds, $fields);\n }\n\n /**\n * Batch read multiple contacts by their CRM IDs\n *\n * @param array<string> $crmIds Array of HubSpot contact IDs (max 100)\n * @param array<string> $fields Array of property names to fetch\n *\n * @return array<string, array> Array keyed by CRM ID with contact data\n */\n public function getContactsByIds(array $crmIds, array $fields): array\n {\n return $this->batchReadObjects('contacts', $crmIds, $fields);\n }\n\n /**\n * @throws CompanyApiException\n * @throws CrmException\n */\n public function getAccountById(string $crmId, array $fields): array\n {\n try {\n $company = $this->getNewInstance()->crm()->companies()->basicApi()->getById(\n $crmId,\n implode(',', $fields),\n );\n } catch (CompanyApiException $e) {\n $this->log->info('[Hubspot] Failed to fetch account', [\n 'crm_id' => $crmId,\n 'reason' => $e->getMessage(),\n ]);\n\n throw $e;\n }\n\n if (! $company instanceof CompaniesWithAssociations) {\n throw new CrmException('Account not found');\n }\n\n return [\n 'id' => $company->getId(),\n 'properties' => $company->getProperties(),\n ];\n }\n\n /**\n * @throws ContactApiException\n * @throws CrmException\n */\n public function getContactById(string $crmId, array $fields): array\n {\n try {\n $contact = $this->getNewInstance()->crm()->contacts()->basicApi()->getById(\n $crmId,\n implode(',', $fields)\n );\n } catch (ContactApiException $e) {\n $this->log->info('[Hubspot] Failed to fetch contact', [\n 'crm_id' => $crmId,\n 'reason' => $e->getMessage(),\n ]);\n\n throw $e;\n }\n\n if (! $contact instanceof ContactsWithAssociations) {\n throw new CrmException('Contact not found');\n }\n\n return [\n 'id' => $contact->getId(),\n 'properties' => $contact->getProperties(),\n ];\n }\n\n /**\n * This is email search request that Hubspot offers as GET (more generous quota)\n */\n public function getContactByEmail(string $email, array $fields = []): array\n {\n try {\n $contact = $this->getNewInstance()->crm()->contacts()->basicApi()->getById(\n $email,\n implode(',', $fields),\n null,\n false,\n 'email'\n );\n\n return [\n 'id' => $contact->getId(),\n 'properties' => $contact->getProperties(),\n ];\n } catch (ContactApiException $e) {\n $this->log->info('[Hubspot] Failed to fetch contact', [\n 'email' => $email,\n 'reason' => $e->getMessage(),\n ]);\n\n return [];\n }\n }\n\n /**\n * @throws CrmException\n */\n public function fetchProperty(string $objectType, string $propertyId): Property\n {\n $result = $this->getNewInstance()->crm()->properties()->coreApi()->getByName($objectType, $propertyId);\n\n if (! $result instanceof Property) {\n $this->log->error('[Hubspot] Failed to fetch property', [\n 'object_type' => $objectType,\n 'property_id' => $propertyId,\n 'reason' => $result->getMessage(),\n ]);\n\n throw new CrmException('Failed to fetch property');\n }\n\n return $result;\n }\n\n /**\n * @return array<CrmFieldOption>\n */\n public function fetchPropertyOptions(string $objectType, string $propertyId): array\n {\n /** @var array<CrmFieldOption> */\n return $this->fetchProperty($objectType, $propertyId)->getOptions();\n }\n\n /**\n * @return array<array{id:string, label:string, deleted:bool}>\n */\n public function fetchCallDispositions(): array\n {\n /** @var Response $response */\n $response = $this->getInstance()->engagements()->getCallDispositions();\n\n /**\n * @var array<array{\n * id:string,\n * label:string,\n * deleted: bool\n * }>\n */\n return $response->toArray();\n }\n\n /**\n * @return array<CrmFieldOption>\n */\n public function fetchOpportunityPipelineStages(): array\n {\n $stages = [];\n $apiResponse = $this->getNewInstance()->crm()->pipelines()->pipelinesApi()->getAll('deals');\n\n if ($apiResponse instanceof Error) {\n $this->log->error('[Hubspot] Failed to fetch opportunity pipelines', [\n 'reason' => $apiResponse->getMessage(),\n ]);\n\n return [];\n }\n\n foreach ($apiResponse->getResults() as $pipeline) {\n $pipelineStages = array_map(\n static function (PipelineStage $stage) {\n return [\n 'id' => $stage->getId(),\n 'label' => $stage->getLabel(),\n ];\n },\n $pipeline->getStages()\n );\n\n $stages = array_merge($stages, $pipelineStages);\n }\n\n return $stages;\n }\n\n public function fetchOpportunityPipelines(): array\n {\n $pipelines = [];\n\n try {\n $apiResponse = $this->makeRequest('/crm/v3/pipelines/deals');\n } catch (\\Exception $e) {\n $this->log->info('[Hubspot] Failed to fetch opportunity pipelines', [\n 'reason' => $e->getMessage(),\n ]);\n\n return [];\n }\n\n $response = $apiResponse->toArray();\n\n foreach ($response['results'] as $pipeline) {\n $pipelines[] = [\n 'id' => $pipeline['id'],\n 'label' => $pipeline['label'],\n ];\n }\n\n return $pipelines;\n }\n\n /**\n * @return array<CrmFieldOption>\n */\n public function fetchMeetingOutcomeFieldOptions(Field $field): array\n {\n return $field->getCrmProviderId() === 'meetingOutcome'\n ? $this->fetchMeetingOutcomeTypes()\n : $this->fetchCallActivityTypes();\n }\n\n public function fetchMeetingOutcomeTypes(): array\n {\n return $this->extractMeetingTypeOptions(\n 'https://api.hubapi.com/crm/v3/properties/meeting/hs_meeting_outcome'\n );\n }\n\n public function fetchCallActivityTypes(): array\n {\n return $this->extractMeetingTypeOptions(\n 'https://api.hubapi.com/crm/v3/properties/call/hs_activity_type'\n );\n }\n\n private function extractMeetingTypeOptions(string $endpoint): array\n {\n /** @var Response $response */\n $response = $this->getInstance()\n ->getClient()\n ->request('GET', $endpoint);\n\n /**\n * @var array<array{\n * value: string,\n * label: string,\n * displayOrder: int\n * }> $optionData\n */\n $optionData = $response->toArray()['options'] ?? [];\n\n $options = [];\n foreach ($optionData as $item) {\n $options[] = [\n 'id' => $item['value'],\n 'value' => $item['value'],\n 'label' => $item['label'],\n 'display_order' => $item['displayOrder'],\n ];\n }\n\n return $options;\n }\n\n /**\n * @return array<CrmFieldOption>\n */\n public function fetchDispositionFieldOptions(): array\n {\n $options = [];\n\n $dispositions = $this->fetchCallDispositions();\n\n foreach ($dispositions as $disposition) {\n if ($disposition['deleted'] !== false) {\n continue;\n }\n\n $option['value'] = $disposition['id'];\n $option['id'] = $disposition['id'];\n $option['label'] = $disposition['label'];\n\n $options[] = $option;\n }\n\n return $options;\n }\n\n /**\n * @return array<CrmFieldOption>\n */\n public function fetchOpportunityFieldOptions(Field $field): array\n {\n if ($field->isStageField()) {\n return $this->fetchOpportunityPipelineStages();\n }\n\n if ($field->isPipelineField()) {\n return $this->fetchOpportunityPipelines();\n }\n\n return $this->fetchPropertyOptions('deals', $field->getCrmProviderId());\n }\n\n /**\n * @throws BadRequest\n * @throws HubspotException\n */\n public function makeRequest(string $endpoint, $method = 'GET', $payload = [], ?string $queryString = null)\n {\n $endpoint = self::BASE_URL . $endpoint;\n\n if ($method === 'GET') {\n $response = $this->getInstance()->getClient()?->request(\n method: $method,\n endpoint: $endpoint,\n query_string: $queryString\n );\n } else {\n $response = $this->getInstance()->getClient()->request($method, $endpoint, [\n 'json' => ($payload),\n ]);\n }\n\n $max = $response->getHeaderLine('X-HubSpot-RateLimit-Max'); // \"110\"\n $remaining = $response->getHeaderLine('X-HubSpot-RateLimit-Remaining'); // \"109\"\n $interval = $response->getHeaderLine('X-HubSpot-RateLimit-Interval-Milliseconds'); // \"10000\"\n $body = json_decode((string) $response->getBody(), true);\n\n \\Illuminate\\Support\\Facades\\Log::channel('custom_channel')->info('$max ' . PHP_EOL . print_r($max, true));\n \\Illuminate\\Support\\Facades\\Log::channel('custom_channel')->info('$remaining ' . PHP_EOL . print_r($remaining, true));\n \\Illuminate\\Support\\Facades\\Log::channel('custom_channel')->info('$interval ' . PHP_EOL . print_r($interval, true));\n \\Illuminate\\Support\\Facades\\Log::channel('custom_channel')->info('$body ' . PHP_EOL . print_r($body, true));\n\n return $response;\n }\n\n /**\n * @throws BadRequest\n * @throws HubspotException\n */\n public function createMeeting(array $payload): Response\n {\n $endpoint = '/crm/v3/objects/meetings';\n\n return $this->makeRequest($endpoint, 'POST', $payload);\n }\n\n /**\n * @throws BadRequest\n * @throws HubspotException\n */\n public function updateMeeting(string $meetingId, array $payload): Response\n {\n $endpoint = '/crm/v3/objects/meetings/' . $meetingId;\n\n return $this->makeRequest($endpoint, 'PATCH', $payload);\n }\n\n /**\n * @throws \\Exception\n */\n public function createNote(\n string $body,\n string $ownerId,\n int $timestamp,\n string $objectId,\n NoteObject $noteObject\n ): ?string {\n try {\n $noteInput = new SimplePublicObjectInput([\n 'properties' => [\n 'hs_note_body' => $body,\n 'hubspot_owner_id' => $ownerId,\n 'hs_timestamp' => $timestamp,\n ],\n ]);\n\n // Create note\n $note = $this->getNewInstance()->crm()->objects()->basicApi()->create('note', $noteInput);\n\n $this->getNewInstance()->crm()->objects()->associationsApi()->create(\n 'note',\n $note->getId(),\n $this->getNoteObject($noteObject),\n $objectId,\n $this->getNoteAssociationType($noteObject),\n );\n\n return $note->getId();\n } catch (\\Exception $e) {\n $this->log->error('[Hubspot] Failed to create note', [\n 'objectId' => $objectId,\n 'noteObject' => $noteObject->getObjectType(),\n 'reason' => $e->getMessage(),\n ]);\n\n \\Sentry::captureException($e);\n }\n\n return null;\n }\n\n public function updateEngagement(string $objectId, array $engagement, array $metadata): void\n {\n $this->getInstance()->engagements()->update($objectId, $engagement, $metadata);\n }\n\n public function getEngagementData(string $engagementId): array\n {\n $engagement = $this->getInstance()->engagements()->get($engagementId);\n\n return $engagement->toArray();\n }\n\n public function createEngagement(array $engagement, array $associations, array $metadata): Response\n {\n return $this->getInstance()\n ->engagements()\n ->create($engagement, $associations, $metadata);\n }\n\n public function isUnauthorizedException(\\Exception $e): bool\n {\n // Check for specific HubSpot API exception types first\n if ($e instanceof BadRequest) {\n // BadRequest can contain 401 status codes\n return $e->getCode() === 401;\n }\n\n // Check for HTTP client exceptions with status codes\n if ($e instanceof \\GuzzleHttp\\Exception\\RequestException && $e->hasResponse()) {\n $response = $e->getResponse();\n if ($response !== null) {\n return $response->getStatusCode() === 401;\n }\n }\n\n // Check for Guzzle HTTP exceptions\n if ($e instanceof \\GuzzleHttp\\Exception\\ClientException) {\n return $e->getCode() === 401;\n }\n\n // Fallback to string matching as last resort, but be more specific\n $message = strtolower($e->getMessage());\n\n return str_contains($message, '401 unauthorized') ||\n str_contains($message, 'http 401') ||\n str_contains($message, 'status code 401') ||\n (preg_match('/\\b401\\b/', $message) && str_contains($message, 'unauthorized'));\n }\n\n /**\n * Validates and refreshes the access token if needed before API requests.\n * This ensures long-running processes don't fail due to token expiration.\n *\n * @throws SocialAccountTokenInvalidException\n */\n public function ensureValidToken(): void\n {\n if ($this->oauthAccount === null) {\n return;\n }\n\n $newToken = $this->tokenManager->ensureValidToken($this->oauthAccount);\n if ($newToken !== null) {\n $this->accessToken = $newToken;\n }\n }\n\n public function getConfig()\n {\n return $this->config;\n }\n\n // returns only active (archived=false)\n public function getOwners(): array\n {\n return $this->getNewInstance()->crm()->owners()->getAll();\n }\n\n /**\n * @param bool $archived\n *\n * @return array<Owner>|[]\n */\n public function getOwnersArchived(bool $archived = true): array\n {\n $endpoint = '/crm/v3/owners';\n $queryParams = [\n 'archived' => $archived ? 'true' : 'false',\n ];\n $queryString = http_build_query($queryParams);\n\n $owners = [];\n\n try {\n $response = $this->makeRequest(endpoint: $endpoint, queryString: $queryString);\n $responseData = $response?->toArray();\n\n foreach ($responseData['results'] as $result) {\n try {\n $owners[] = Owner::create($result);\n } catch (Throwable $e) {\n $this->log->error('[HubSpot] Failed to process owner data', [\n 'result' => $result,\n 'error' => $e->getMessage(),\n ]);\n\n continue;\n }\n }\n } catch (Throwable $e) {\n $this->log->error('HubSpot] Failed to fetch owners', [\n 'archived' => $archived,\n 'error' => $e->getMessage(),\n ]);\n\n return [];\n }\n\n return $owners;\n }\n\n public function getMeeting(string $engagementId): ObjectWithAssociations\n {\n return $this->getNewInstance()->crm()->objects()->basicApi()\n ->getById('meeting', $engagementId, null, 'contact,company,deal');\n }\n\n public function deleteEngagement(string $engagementId): void\n {\n $this->getInstance()->engagements()->delete((int) $engagementId);\n }\n\n public function getAssociationsData(array $ids, string $fromObject, string $toObject): array\n {\n $associationData = [];\n $idChunks = array_chunk($ids, self::ASSOCIATIONS_BATCH_SIZE_LIMIT);\n\n foreach ($idChunks as $idChunk) {\n try {\n $batchInput = new \\HubSpot\\Client\\Crm\\Associations\\Model\\BatchInputPublicObjectId();\n $batchInput->setInputs(array_map(function ($id) {\n $publicObjectId = new \\HubSpot\\Client\\Crm\\Associations\\Model\\PublicObjectId();\n $publicObjectId->setId($id);\n\n return $publicObjectId;\n }, $idChunk));\n\n $associatedObjectsData = $this\n ->getNewInstance()\n ->crm()\n ->associations()\n ->batchApi()\n ->read($fromObject, $toObject, $batchInput);\n\n if ($associatedObjectsData instanceof \\HubSpot\\Client\\Crm\\Associations\\Model\\BatchResponsePublicAssociationMulti) {\n foreach ($associatedObjectsData->getResults() as $association) {\n $from = $association->getFrom()->getId();\n $toAssociations = $association->getTo();\n\n if (! empty($toAssociations)) {\n $associationData[$from] = array_map(function ($item) {\n return $item->getId();\n }, $toAssociations);\n }\n }\n }\n } catch (\\Exception $e) {\n $this->log->error('[Hubspot] Failed to fetch associations', [\n 'from_object' => $fromObject,\n 'to_object' => $toObject,\n 'reason' => $e->getMessage(),\n ]);\n }\n }\n\n return $associationData;\n }\n\n /**\n * @throws \\Exception\n */\n private function getNoteAssociationType(NoteObject $noteObject): string\n {\n return match($noteObject) {\n NoteObject::Opportunity => 'note_to_deal',\n NoteObject::Lead, NoteObject::Contact => 'note_to_contact', // or 'note_to_lead' if your portal supports it\n NoteObject::Account => 'note_to_company',\n NoteObject::Call, NoteObject::Event => throw new \\Exception('Not supported'),\n };\n }\n\n /**\n * @throws \\Exception\n */\n private function getNoteObject(NoteObject $noteObject): string\n {\n return match($noteObject) {\n NoteObject::Opportunity => 'deal',\n NoteObject::Lead, NoteObject::Contact => 'contact',\n NoteObject::Account => 'company',\n NoteObject::Call, NoteObject::Event => throw new \\Exception('Not supported'),\n };\n }\n\n public function addAssociations(string $objectType, string $associationType, array $payload): Response\n {\n $endpoint = \"/crm/v4/associations/$objectType/$associationType/batch/create\";\n\n return $this->makeRequest($endpoint, 'POST', $payload);\n }\n\n public function removeAssociations(string $objectType, string $associationType, array $payload): Response\n {\n $endpoint = \"/crm/v4/associations/$objectType/$associationType/batch/archive\";\n\n return $this->makeRequest($endpoint, 'POST', $payload);\n }\n}","role_description":"text entry area","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"Project","depth":3,"on_screen":false,"role_description":"text"},{"role":"AXButton","text":"Project","depth":3,"bounds":{"left":0.011968086,"top":0.047885075,"width":0.024268618,"height":0.024740623},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"New File or Directory…","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.008643617,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Expand Selected","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.008643617,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Collapse All","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.008643617,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Options","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.008643617,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Hide","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.008643617,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false}]...
|
-2199466652252128033
|
6666989725209335916
|
click
|
accessibility
|
NULL
|
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
[2026-05-07 11:36:44] local.INFO: Jiminny\Console\Commands\Command::run Memory usage before starting command {"command":"crm:sync-opportunity","memoryBeforeCommandInMb":60.0,"memoryPeakBeforeCommandInMb":99.727} {"correlation_id":"462baf35-d312-4641-9896-e8b15e8fdb82","trace_id":"aeb070fe-166a-48bf-99c0-acfcbfb40c1b"}
[2026-05-07 11:36:44] local.INFO: [SocialAccountService] Fetching token {"socialAccountId":1499,"provider":"hubspot"} {"correlation_id":"462baf35-d312-4641-9896-e8b15e8fdb82","trace_id":"aeb070fe-166a-48bf-99c0-acfcbfb40c1b"}
[2026-05-07 11:36:44] local.INFO: [SocialAccountService] Token retrieved {"socialAccountId":1499,"provider":"hubspot"} {"correlation_id":"462baf35-d312-4641-9896-e8b15e8fdb82","trace_id":"aeb070fe-166a-48bf-99c0-acfcbfb40c1b"}
[2026-05-07 11:36:44] local.INFO: [EncryptedTokenManager] Generating access token. {"mode":"legacy"} {"correlation_id":"462baf35-d312-4641-9896-e8b15e8fdb82","trace_id":"aeb070fe-166a-48bf-99c0-acfcbfb40c1b"}
[2026-05-07 11:36:44] local.INFO: [HubSpot] Syncing opportunities using strategy: lastModified {"team":2} {"correlation_id":"462baf35-d312-4641-9896-e8b15e8fdb82","trace_id":"aeb070fe-166a-48bf-99c0-acfcbfb40c1b"}
[2026-05-07 11:36:45] local.INFO: [Hubspot] Pagination completed {"team_id":2,"endpoint":"[URL_WITH_CREDENTIALS] CrmFieldOption array{id:string, label:string, value?:string}
*/
class Client extends BaseClient implements HubspotClientInterface
{
public const string MIN_API_VERSION = '2';
public const string BASE_URL = '[URL_WITH_CREDENTIALS] T
* @param callable(): T $apiCall
* @return T
*
* @throws RateLimitException
*/
private function executeRequest(callable $apiCall)
{
if (! $this->rateLimiter->canMakeRequest($this->config)) {
$retryAfter = $this->rateLimiter->requestAvailableIn($this->config);
$this->log->warning('[Hubspot] Rate limit exceeded, deferring request', [
'team_id' => $this->config->team_id,
'config_id' => $this->config->getId(),
'retry_after' => $retryAfter,
]);
throw new RateLimitException(
'Hubspot rate limit reached for configuration ' . $this->config->getId(),
$retryAfter,
);
}
$this->rateLimiter->incrementRequestCount($this->config);
try {
return $apiCall();
} catch (Throwable $e) {
if ($this->isHubspotRateLimit($e)) {
$retryAfter = $this->parseRetryAfter($e);
$this->log->warning('[Hubspot] Received 429 from API', [
'team_id' => $this->config->team_id,
'config_id' => $this->config->getId(),
'retry_after' => $retryAfter,
'reason' => $e->getMessage(),
]);
throw new RateLimitException('Hubspot returned 429', $retryAfter, $e);
}
throw $e;
}
}
private function isHubspotRateLimit(Throwable $e): bool
{
return method_exists($e, 'getCode') && (int) $e->getCode() === 429;
}
private function parseRetryAfter(Throwable $e): int
{
if (method_exists($e, 'getResponseHeaders')) {
$headers = $e->getResponseHeaders() ?: [];
$value = $headers['Retry-After'] ?? $headers['retry-after'] ?? null;
if (is_array($value)) {
$value = $value[0] ?? null;
}
if (is_numeric($value)) {
return (int) $value;
}
}
return 10;
}
public function getMinimumApiVersion(): string
{
return self::MIN_API_VERSION;
}
public function getInstance(): Factory
{
return new Factory([
'key' => $this->accessToken,
'oauth2' => true,
'base_url' => $this->baseUrl,
]);
}
public function getNewInstance(): Discovery
{
return \HubSpot\Factory::createWithAccessToken($this->accessToken);
}
/**
* Secondly and daily limits for Hubspot API
*
* Product Tier: Free & Starter | Professional & Enterprise | API add-on (any tier)
* Burst: 100/10 seconds | 150/10 seconds | 200/10 seconds
* Daily: 250,000 | 500,000 | 1,000,000
*
* Official documentation states: The search endpoints are rate limited to five requests per second.
* Since with 5 RPS were still hitting secondly rate limits we lowered it to 4
*/
public function getPaginatedData(array $payload, string $type, int $offset = 0): array
{
$total = 0;
$lastId = null;
$rows = [];
foreach ($this->getPaginatedDataGenerator($payload, $type, $offset, $total, $lastId) as $row) {
$rows[] = $row;
}
return ['results' => $rows, 'total' => $total, 'last_record' => $lastId];
}
/**
* @throws HubspotException
* @throws SocialAccountTokenInvalidException
* @throws BadRequest
*/
public function getPaginatedDataGenerator(
array $payload,
string $type,
int $offset = 0,
int &$total = 0,
?string &$lastRecordId = null
): \Generator {
return $this->paginationService->getPaginatedDataGenerator(
$this,
$payload,
$type,
$offset,
$total,
$lastRecordId
);
}
/**
* @throws DealApiException
* @throws CrmException
*/
public function getOpportunityById(string $crmId, array $fields): array
{
try {
$deal = $this->executeRequest(fn () => $this->getNewInstance()->crm()->deals()->basicApi()->getById(
$crmId,
implode(',', $fields),
'companies,contacts'
));
} catch (DealApiException $e) {
$this->log->info('[Hubspot] Failed to fetch opportunity', [
'crm_id' => $crmId,
'reason' => $e->getMessage(),
]);
throw $e;
}
if (! $deal instanceof DealWithAssociations) {
throw new CrmException('Deal not found');
}
return [
'id' => $deal->getId(),
'properties' => $deal->getProperties(),
'associations' => $deal->getAssociations(),
];
}
/**
* Generic batch read method for HubSpot objects
*
* @param string $objectType The object type ('deals', 'companies', 'contacts')
* @param array<string> $crmIds Array of HubSpot object IDs (max 100)
* @param array<string> $fields Array of property names to fetch
*
* @return array<string, array> Array keyed by CRM ID with object data
*/
private function batchReadObjects(string $objectType, array $crmIds, array $fields): array
{
if (empty($crmIds)) {
return [];
}
$this->validateBatchSize($objectType, $crmIds);
$this->ensureValidToken();
try {
$batchConfig = $this->createBatchConfiguration($objectType);
$batchReadRequest = $this->prepareBatchRequest($batchConfig, $crmIds, $fields);
$response = $batchConfig['api']->read($batchReadRequest);
$this->validateApiResponse($response, $objectType);
$results = $this->processApiResults($response);
$this->logBatchResults($objectType, $crmIds, $results);
return $results;
} catch (\Throwable $e) {
$this->handleBatchError($e, $objectType, $crmIds);
}
}
private function validateBatchSize(string $objectType, array $crmIds): void
{
if (count($crmIds) > 100) {
throw new \InvalidArgumentException("Batch size cannot exceed 100 {$objectType}");
}
}
private function createBatchConfiguration(string $objectType): array
{
$configurations = [
'deals' => [
'batchReadRequest' => new \HubSpot\Client\Crm\Deals\Model\BatchReadInputSimplePublicObjectId(),
'inputClass' => \HubSpot\Client\Crm\Deals\Model\SimplePublicObjectId::class,
'api' => $this->getNewInstance()->crm()->deals()->batchApi(),
],
'companies' => [
'batchReadRequest' => new \HubSpot\Client\Crm\Companies\Model\BatchReadInputSimplePublicObjectId(),
'inputClass' => \HubSpot\Client\Crm\Companies\Model\SimplePublicObjectId::class,
'api' => $this->getNewInstance()->crm()->companies()->batchApi(),
],
'contacts' => [
'batchReadRequest' => new \HubSpot\Client\Crm\Contacts\Model\BatchReadInputSimplePublicObjectId(),
'inputClass' => \HubSpot\Client\Crm\Contacts\Model\SimplePublicObjectId::class,
'api' => $this->getNewInstance()->crm()->contacts()->batchApi(),
],
];
if (! isset($configurations[$objectType])) {
throw new \InvalidArgumentException("Unsupported object type: {$objectType}");
}
return $configurations[$objectType];
}
private function prepareBatchRequest(array $batchConfig, array $crmIds, array $fields): object
{
$batchReadRequest = $batchConfig['batchReadRequest'];
$inputClass = $batchConfig['inputClass'];
$inputs = array_map(function ($crmId) use ($inputClass) {
$input = new $inputClass();
$input->setId($crmId);
return $input;
}, $crmIds);
$batchReadRequest->setInputs($inputs);
$batchReadRequest->setProperties($fields);
return $batchReadRequest;
}
private function validateApiResponse($response, string $objectType): void
{
if (! $response) {
throw new CrmException("HubSpot API returned null response for {$objectType} batch read");
}
}
private function processApiResults($response): array
{
$results = [];
$responseResults = $response->getResults();
if ($responseResults) {
foreach ($responseResults as $object) {
if ($object && $object->getId()) {
$results[$object->getId()] = [
'id' => $object->getId(),
'properties' => $object->getProperties() ?: [],
];
}
}
}
return $results;
}
private function logBatchResults(string $objectType, array $crmIds, array $results): void
{
$this->log->info("[HubSpot] Batch fetched {$objectType}", [
'requested_count' => count($crmIds),
'returned_count' => count($results),
'crm_ids' => $crmIds,
]);
}
private function handleBatchError(\Throwable $e, string $objectType, array $crmIds): void
{
$errorMessage = $e->getMessage() ?: 'Unknown error';
$errorTrace = $e->getTraceAsString() ?: 'No trace available';
$this->log->error("[HubSpot] Failed to batch fetch {$objectType}", [
'crm_ids' => $crmIds,
'error' => $errorMessage,
'trace' => $errorTrace,
]);
throw new CrmException("Failed to batch fetch {$objectType}: " . $errorMessage);
}
/**
* Batch read multiple opportunities by their CRM IDs
*
* @param array<string> $crmIds Array of HubSpot deal IDs (max 100)
* @param array<string> $fields Array of property names to fetch
*
* @return array<string, array> Array keyed by CRM ID with opportunity data
*/
public function getOpportunitiesByIds(array $crmIds, array $fields): array
{
return $this->batchReadObjects('deals', $crmIds, $fields);
}
/**
* Batch read multiple companies by their CRM IDs
*
* @param array<string> $crmIds Array of HubSpot company IDs (max 100)
* @param array<string> $fields Array of property names to fetch
*
* @return array<string, array> Array keyed by CRM ID with company data
*/
public function getCompaniesByIds(array $crmIds, array $fields): array
{
return $this->batchReadObjects('companies', $crmIds, $fields);
}
/**
* Batch read multiple contacts by their CRM IDs
*
* @param array<string> $crmIds Array of HubSpot contact IDs (max 100)
* @param array<string> $fields Array of property names to fetch
*
* @return array<string, array> Array keyed by CRM ID with contact data
*/
public function getContactsByIds(array $crmIds, array $fields): array
{
return $this->batchReadObjects('contacts', $crmIds, $fields);
}
/**
* @throws CompanyApiException
* @throws CrmException
*/
public function getAccountById(string $crmId, array $fields): array
{
try {
$company = $this->getNewInstance()->crm()->companies()->basicApi()->getById(
$crmId,
implode(',', $fields),
);
} catch (CompanyApiException $e) {
$this->log->info('[Hubspot] Failed to fetch account', [
'crm_id' => $crmId,
'reason' => $e->getMessage(),
]);
throw $e;
}
if (! $company instanceof CompaniesWithAssociations) {
throw new CrmException('Account not found');
}
return [
'id' => $company->getId(),
'properties' => $company->getProperties(),
];
}
/**
* @throws ContactApiException
* @throws CrmException
*/
public function getContactById(string $crmId, array $fields): array
{
try {
$contact = $this->getNewInstance()->crm()->contacts()->basicApi()->getById(
$crmId,
implode(',', $fields)
);
} catch (ContactApiException $e) {
$this->log->info('[Hubspot] Failed to fetch contact', [
'crm_id' => $crmId,
'reason' => $e->getMessage(),
]);
throw $e;
}
if (! $contact instanceof ContactsWithAssociations) {
throw new CrmException('Contact not found');
}
return [
'id' => $contact->getId(),
'properties' => $contact->getProperties(),
];
}
/**
* This is email search request that Hubspot offers as GET (more generous quota)
*/
public function getContactByEmail(string $email, array $fields = []): array
{
try {
$contact = $this->getNewInstance()->crm()->contacts()->basicApi()->getById(
$email,
implode(',', $fields),
null,
false,
'email'
);
return [
'id' => $contact->getId(),
'properties' => $contact->getProperties(),
];
} catch (ContactApiException $e) {
$this->log->info('[Hubspot] Failed to fetch contact', [
'email' => $email,
'reason' => $e->getMessage(),
]);
return [];
}
}
/**
* @throws CrmException
*/
public function fetchProperty(string $objectType, string $propertyId): Property
{
$result = $this->getNewInstance()->crm()->properties()->coreApi()->getByName($objectType, $propertyId);
if (! $result instanceof Property) {
$this->log->error('[Hubspot] Failed to fetch property', [
'object_type' => $objectType,
'property_id' => $propertyId,
'reason' => $result->getMessage(),
]);
throw new CrmException('Failed to fetch property');
}
return $result;
}
/**
* @return array<CrmFieldOption>
*/
public function fetchPropertyOptions(string $objectType, string $propertyId): array
{
/** @var array<CrmFieldOption> */
return $this->fetchProperty($objectType, $propertyId)->getOptions();
}
/**
* @return array<array{id:string, label:string, deleted:bool}>
*/
public function fetchCallDispositions(): array
{
/** @var Response $response */
$response = $this->getInstance()->engagements()->getCallDispositions();
/**
* @var array<array{
* id:string,
* label:string,
* deleted: bool
* }>
*/
return $response->toArray();
}
/**
* @return array<CrmFieldOption>
*/
public function fetchOpportunityPipelineStages(): array
{
$stages = [];
$apiResponse = $this->getNewInstance()->crm()->pipelines()->pipelinesApi()->getAll('deals');
if ($apiResponse instanceof Error) {
$this->log->error('[Hubspot] Failed to fetch opportunity pipelines', [
'reason' => $apiResponse->getMessage(),
]);
return [];
}
foreach ($apiResponse->getResults() as $pipeline) {
$pipelineStages = array_map(
static function (PipelineStage $stage) {
return [
'id' => $stage->getId(),
'label' => $stage->getLabel(),
];
},
$pipeline->getStages()
);
$stages = array_merge($stages, $pipelineStages);
}
return $stages;
}
public function fetchOpportunityPipelines(): array
{
$pipelines = [];
try {
$apiResponse = $this->makeRequest('/crm/v3/pipelines/deals');
} catch (\Exception $e) {
$this->log->info('[Hubspot] Failed to fetch opportunity pipelines', [
'reason' => $e->getMessage(),
]);
return [];
}
$response = $apiResponse->toArray();
foreach ($response['results'] as $pipeline) {
$pipelines[] = [
'id' => $pipeline['id'],
'label' => $pipeline['label'],
];
}
return $pipelines;
}
/**
* @return array<CrmFieldOption>
*/
public function fetchMeetingOutcomeFieldOptions(Field $field): array
{
return $field->getCrmProviderId() === 'meetingOutcome'
? $this->fetchMeetingOutcomeTypes()
: $this->fetchCallActivityTypes();
}
public function fetchMeetingOutcomeTypes(): array
{
return $this->extractMeetingTypeOptions(
'[URL_WITH_CREDENTIALS] Response $response */
$response = $this->getInstance()
->getClient()
->request('GET', $endpoint);
/**
* @var array<array{
* value: string,
* label: string,
* displayOrder: int
* }> $optionData
*/
$optionData = $response->toArray()['options'] ?? [];
$options = [];
foreach ($optionData as $item) {
$options[] = [
'id' => $item['value'],
'value' => $item['value'],
'label' => $item['label'],
'display_order' => $item['displayOrder'],
];
}
return $options;
}
/**
* @return array<CrmFieldOption>
*/
public function fetchDispositionFieldOptions(): array
{
$options = [];
$dispositions = $this->fetchCallDispositions();
foreach ($dispositions as $disposition) {
if ($disposition['deleted'] !== false) {
continue;
}
$option['value'] = $disposition['id'];
$option['id'] = $disposition['id'];
$option['label'] = $disposition['label'];
$options[] = $option;
}
return $options;
}
/**
* @return array<CrmFieldOption>
*/
public function fetchOpportunityFieldOptions(Field $field): array
{
if ($field->isStageField()) {
return $this->fetchOpportunityPipelineStages();
}
if ($field->isPipelineField()) {
return $this->fetchOpportunityPipelines();
}
return $this->fetchPropertyOptions('deals', $field->getCrmProviderId());
}
/**
* @throws BadRequest
* @throws HubspotException
*/
public function makeRequest(string $endpoint, $method = 'GET', $payload = [], ?string $queryString = null)
{
$endpoint = self::BASE_URL . $endpoint;
if ($method === 'GET') {
$response = $this->getInstance()->getClient()?->request(
method: $method,
endpoint: $endpoint,
query_string: $queryString
);
} else {
$response = $this->getInstance()->getClient()->request($method, $endpoint, [
'json' => ($payload),
]);
}
$max = $response->getHeaderLine('X-HubSpot-RateLimit-Max'); // "110"
$remaining = $response->getHeaderLine('X-HubSpot-RateLimit-Remaining'); // "109"
$interval = $response->getHeaderLine('X-HubSpot-RateLimit-Interval-Milliseconds'); // "10000"
$body = json_decode((string) $response->getBody(), true);
\Illuminate\Support\Facades\Log::channel('custom_channel')->info('$max ' . PHP_EOL . print_r($max, true));
\Illuminate\Support\Facades\Log::channel('custom_channel')->info('$remaining ' . PHP_EOL . print_r($remaining, true));
\Illuminate\Support\Facades\Log::channel('custom_channel')->info('$interval ' . PHP_EOL . print_r($interval, true));
\Illuminate\Support\Facades\Log::channel('custom_channel')->info('$body ' . PHP_EOL . print_r($body, true));
return $response;
}
/**
* @throws BadRequest
* @throws HubspotException
*/
public function createMeeting(array $payload): Response
{
$endpoint = '/crm/v3/objects/meetings';
return $this->makeRequest($endpoint, 'POST', $payload);
}
/**
* @throws BadRequest
* @throws HubspotException
*/
public function updateMeeting(string $meetingId, array $payload): Response
{
$endpoint = '/crm/v3/objects/meetings/' . $meetingId;
return $this->makeRequest($endpoint, 'PATCH', $payload);
}
/**
* @throws \Exception
*/
public function createNote(
string $body,
string $ownerId,
int $timestamp,
string $objectId,
NoteObject $noteObject
): ?string {
try {
$noteInput = new SimplePublicObjectInput([
'properties' => [
'hs_note_body' => $body,
'hubspot_owner_id' => $ownerId,
'hs_timestamp' => $timestamp,
],
]);
// Create note
$note = $this->getNewInstance()->crm()->objects()->basicApi()->create('note', $noteInput);
$this->getNewInstance()->crm()->objects()->associationsApi()->create(
'note',
$note->getId(),
$this->getNoteObject($noteObject),
$objectId,
$this->getNoteAssociationType($noteObject),
);
return $note->getId();
} catch (\Exception $e) {
$this->log->error('[Hubspot] Failed to create note', [
'objectId' => $objectId,
'noteObject' => $noteObject->getObjectType(),
'reason' => $e->getMessage(),
]);
\Sentry::captureException($e);
}
return null;
}
public function updateEngagement(string $objectId, array $engagement, array $metadata): void
{
$this->getInstance()->engagements()->update($objectId, $engagement, $metadata);
}
public function getEngagementData(string $engagementId): array
{
$engagement = $this->getInstance()->engagements()->get($engagementId);
return $engagement->toArray();
}
public function createEngagement(array $engagement, array $associations, array $metadata): Response
{
return $this->getInstance()
->engagements()
->create($engagement, $associations, $metadata);
}
public function isUnauthorizedException(\Exception $e): bool
{
// Check for specific HubSpot API exception types first
if ($e instanceof BadRequest) {
// BadRequest can contain 401 status codes
return $e->getCode() === 401;
}
// Check for HTTP client exceptions with status codes
if ($e instanceof \GuzzleHttp\Exception\RequestException && $e->hasResponse()) {
$response = $e->getResponse();
if ($response !== null) {
return $response->getStatusCode() === 401;
}
}
// Check for Guzzle HTTP exceptions
if ($e instanceof \GuzzleHttp\Exception\ClientException) {
return $e->getCode() === 401;
}
// Fallback to string matching as last resort, but be more specific
$message = strtolower($e->getMessage());
return str_contains($message, '401 unauthorized') ||
str_contains($message, 'http 401') ||
str_contains($message, 'status code 401') ||
(preg_match('/\b401\b/', $message) && str_contains($message, 'unauthorized'));
}
/**
* Validates and refreshes the access token if needed before API requests.
* This ensures long-running processes don't fail due to token expiration.
*
* @throws SocialAccountTokenInvalidException
*/
public function ensureValidToken(): void
{
if ($this->oauthAccount === null) {
return;
}
$newToken = $this->tokenManager->ensureValidToken($this->oauthAccount);
if ($newToken !== null) {
$this->accessToken = $newToken;
}
}
public function getConfig()
{
return $this->config;
}
// returns only active (archived=false)
public function getOwners(): array
{
return $this->getNewInstance()->crm()->owners()->getAll();
}
/**
* @param bool $archived
*
* @return array<Owner>|[]
*/
public function getOwnersArchived(bool $archived = true): array
{
$endpoint = '/crm/v3/owners';
$queryParams = [
'archived' => $archived ? 'true' : 'false',
];
$queryString = http_build_query($queryParams);
$owners = [];
try {
$response = $this->makeRequest(endpoint: $endpoint, queryString: $queryString);
$responseData = $response?->toArray();
foreach ($responseData['results'] as $result) {
try {
$owners[] = Owner::create($result);
} catch (Throwable $e) {
$this->log->error('[HubSpot] Failed to process owner data', [
'result' => $result,
'error' => $e->getMessage(),
]);
continue;
}
}
} catch (Throwable $e) {
$this->log->error('HubSpot] Failed to fetch owners', [
'archived' => $archived,
'error' => $e->getMessage(),
]);
return [];
}
return $owners;
}
public function getMeeting(string $engagementId): ObjectWithAssociations
{
return $this->getNewInstance()->crm()->objects()->basicApi()
->getById('meeting', $engagementId, null, 'contact,company,deal');
}
public function deleteEngagement(string $engagementId): void
{
$this->getInstance()->engagements()->delete((int) $engagementId);
}
public function getAssociationsData(array $ids, string $fromObject, string $toObject): array
{
$associationData = [];
$idChunks = array_chunk($ids, self::ASSOCIATIONS_BATCH_SIZE_LIMIT);
foreach ($idChunks as $idChunk) {
try {
$batchInput = new \HubSpot\Client\Crm\Associations\Model\BatchInputPublicObjectId();
$batchInput->setInputs(array_map(function ($id) {
$publicObjectId = new \HubSpot\Client\Crm\Associations\Model\PublicObjectId();
$publicObjectId->setId($id);
return $publicObjectId;
}, $idChunk));
$associatedObjectsData = $this
->getNewInstance()
->crm()
->associations()
->batchApi()
->read($fromObject, $toObject, $batchInput);
if ($associatedObjectsData instanceof \HubSpot\Client\Crm\Associations\Model\BatchResponsePublicAssociationMulti) {
foreach ($associatedObjectsData->getResults() as $association) {
$from = $association->getFrom()->getId();
$toAssociations = $association->getTo();
if (! empty($toAssociations)) {
$associationData[$from] = array_map(function ($item) {
return $item->getId();
}, $toAssociations);
}
}
}
} catch (\Exception $e) {
$this->log->error('[Hubspot] Failed to fetch associations', [
'from_object' => $fromObject,
'to_object' => $toObject,
'reason' => $e->getMessage(),
]);
}
}
return $associationData;
}
/**
* @throws \Exception
*/
private function getNoteAssociationType(NoteObject $noteObject): string
{
return match($noteObject) {
NoteObject::Opportunity => 'note_to_deal',
NoteObject::Lead, NoteObject::Contact => 'note_to_contact', // or 'note_to_lead' if your portal supports it
NoteObject::Account => 'note_to_company',
NoteObject::Call, NoteObject::Event => throw new \Exception('Not supported'),
};
}
/**
* @throws \Exception
*/
private function getNoteObject(NoteObject $noteObject): string
{
return match($noteObject) {
NoteObject::Opportunity => 'deal',
NoteObject::Lead, NoteObject::Contact => 'contact',
NoteObject::Account => 'company',
NoteObject::Call, NoteObject::Event => throw new \Exception('Not supported'),
};
}
public function addAssociations(string $objectType, string $associationType, array $payload): Response
{
$endpoint = "/crm/v4/associations/$objectType/$associationType/batch/create";
return $this->makeRequest($endpoint, 'POST', $payload);
}
public function removeAssociations(string $objectType, string $associationType, array $payload): Response
{
$endpoint = "/crm/v4/associations/$objectType/$associationType/batch/archive";
return $this->makeRequest($endpoint, 'POST', $payload);
}
}
Project
Project
New File or Directory…
Expand Selected
Collapse All
Options
Hide...
|
NULL
|
NULL
|
NULL
|
NULL
|
|
2744
|
111
|
43
|
2026-05-07T11:38:09.493606+00:00
|
/Users/lukas/.screenpipe/data/data/2026-05-07/1778 /Users/lukas/.screenpipe/data/data/2026-05-07/1778153889493_m1.jpg...
|
PhpStorm
|
faVsco.js – Hubspot/Client.php
|
True
|
NULL
|
monitor_1
|
NULL
|
NULL
|
NULL
|
NULL
|
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
Editor for custom.log
Sync Changes
Hide This Notification
Code changed:
Hide
2
60
Previous Highlighted Error
Next Highlighted Error
<?php
declare(strict_types=1);
namespace Jiminny\Services\Crm\Hubspot;
use HubSpot\Client\Crm\Deals\ApiException as DealApiException;
use HubSpot\Client\Crm\Contacts\ApiException as ContactApiException;
use HubSpot\Client\Crm\Companies\ApiException as CompanyApiException;
use HubSpot\Client\Crm\Contacts\Model\SimplePublicObjectWithAssociations as ContactsWithAssociations;
use HubSpot\Client\Crm\Companies\Model\SimplePublicObjectWithAssociations as CompaniesWithAssociations;
use HubSpot\Client\Crm\Deals\Model\SimplePublicObjectWithAssociations as DealWithAssociations;
use HubSpot\Client\Crm\Objects\Model\SimplePublicObjectInput;
use HubSpot\Client\Crm\Objects\Model\SimplePublicObjectWithAssociations as ObjectWithAssociations;
use HubSpot\Client\Crm\Pipelines\Model\Error;
use HubSpot\Client\Crm\Pipelines\Model\PipelineStage;
use HubSpot\Client\Crm\Properties\Model\Property;
use HubSpot\Discovery\Discovery;
use Jiminny\Component\Utility\Service\ProviderRateLimiter;
use Jiminny\Exceptions\CrmException;
use Jiminny\Exceptions\RateLimitException;
use Jiminny\Exceptions\SocialAccountTokenInvalidException;
use Jiminny\Jobs\Crm\NoteObject;
use Jiminny\Models\Crm\Field;
use Jiminny\Services\Crm\BaseClient;
use Jiminny\Services\Crm\Hubspot\DTO\Response\Owner;
use Jiminny\Services\SocialAccountService;
use SevenShores\Hubspot\Exceptions\BadRequest;
use SevenShores\Hubspot\Exceptions\HubspotException;
use SevenShores\Hubspot\Factory;
use SevenShores\Hubspot\Http\Response;
use Jiminny\Services\Crm\Hubspot\Pagination\HubspotPaginationService;
use Throwable;
/**
* @phpstan-type CrmFieldOption array{id:string, label:string, value?:string}
*/
class Client extends BaseClient implements HubspotClientInterface
{
public const string MIN_API_VERSION = '2';
public const string BASE_URL = '[URL_WITH_CREDENTIALS] T
* @param callable(): T $apiCall
* @return T
*
* @throws RateLimitException
*/
private function executeRequest(callable $apiCall)
{
if (! $this->rateLimiter->canMakeRequest($this->config)) {
$retryAfter = $this->rateLimiter->requestAvailableIn($this->config);
$this->log->warning('[Hubspot] Rate limit exceeded, deferring request', [
'team_id' => $this->config->team_id,
'config_id' => $this->config->getId(),
'retry_after' => $retryAfter,
]);
throw new RateLimitException(
'Hubspot rate limit reached for configuration ' . $this->config->getId(),
$retryAfter,
);
}
$this->rateLimiter->incrementRequestCount($this->config);
try {
return $apiCall();
} catch (Throwable $e) {
if ($this->isHubspotRateLimit($e)) {
$retryAfter = $this->parseRetryAfter($e);
$this->log->warning('[Hubspot] Received 429 from API', [
'team_id' => $this->config->team_id,
'config_id' => $this->config->getId(),
'retry_after' => $retryAfter,
'reason' => $e->getMessage(),
]);
throw new RateLimitException('Hubspot returned 429', $retryAfter, $e);
}
throw $e;
}
}
private function isHubspotRateLimit(Throwable $e): bool
{
return method_exists($e, 'getCode') && (int) $e->getCode() === 429;
}
private function parseRetryAfter(Throwable $e): int
{
if (method_exists($e, 'getResponseHeaders')) {
$headers = $e->getResponseHeaders() ?: [];
$value = $headers['Retry-After'] ?? $headers['retry-after'] ?? null;
if (is_array($value)) {
$value = $value[0] ?? null;
}
if (is_numeric($value)) {
return (int) $value;
}
}
return 10;
}
public function getMinimumApiVersion(): string
{
return self::MIN_API_VERSION;
}
public function getInstance(): Factory
{
return new Factory([
'key' => $this->accessToken,
'oauth2' => true,
'base_url' => $this->baseUrl,
]);
}
public function getNewInstance(): Discovery
{
return \HubSpot\Factory::createWithAccessToken($this->accessToken);
}
/**
* Secondly and daily limits for Hubspot API
*
* Product Tier: Free & Starter | Professional & Enterprise | API add-on (any tier)
* Burst: 100/10 seconds | 150/10 seconds | 200/10 seconds
* Daily: 250,000 | 500,000 | 1,000,000
*
* Official documentation states: The search endpoints are rate limited to five requests per second.
* Since with 5 RPS were still hitting secondly rate limits we lowered it to 4
*/
public function getPaginatedData(array $payload, string $type, int $offset = 0): array
{
$total = 0;
$lastId = null;
$rows = [];
foreach ($this->getPaginatedDataGenerator($payload, $type, $offset, $total, $lastId) as $row) {
$rows[] = $row;
}
return ['results' => $rows, 'total' => $total, 'last_record' => $lastId];
}
/**
* @throws HubspotException
* @throws SocialAccountTokenInvalidException
* @throws BadRequest
*/
public function getPaginatedDataGenerator(
array $payload,
string $type,
int $offset = 0,
int &$total = 0,
?string &$lastRecordId = null
): \Generator {
return $this->paginationService->getPaginatedDataGenerator(
$this,
$payload,
$type,
$offset,
$total,
$lastRecordId
);
}
/**
* @throws DealApiException
* @throws CrmException
*/
public function getOpportunityById(string $crmId, array $fields): array
{
try {
$deal = $this->executeRequest(fn () => $this->getNewInstance()->crm()->deals()->basicApi()->getById(
$crmId,
implode(',', $fields),
'companies,contacts'
));
} catch (DealApiException $e) {
$this->log->info('[Hubspot] Failed to fetch opportunity', [
'crm_id' => $crmId,
'reason' => $e->getMessage(),
]);
throw $e;
}
if (! $deal instanceof DealWithAssociations) {
throw new CrmException('Deal not found');
}
return [
'id' => $deal->getId(),
'properties' => $deal->getProperties(),
'associations' => $deal->getAssociations(),
];
}
/**
* Generic batch read method for HubSpot objects
*
* @param string $objectType The object type ('deals', 'companies', 'contacts')
* @param array<string> $crmIds Array of HubSpot object IDs (max 100)
* @param array<string> $fields Array of property names to fetch
*
* @return array<string, array> Array keyed by CRM ID with object data
*/
private function batchReadObjects(string $objectType, array $crmIds, array $fields): array
{
if (empty($crmIds)) {
return [];
}
$this->validateBatchSize($objectType, $crmIds);
$this->ensureValidToken();
try {
$batchConfig = $this->createBatchConfiguration($objectType);
$batchReadRequest = $this->prepareBatchRequest($batchConfig, $crmIds, $fields);
$response = $batchConfig['api']->read($batchReadRequest);
$this->validateApiResponse($response, $objectType);
$results = $this->processApiResults($response);
$this->logBatchResults($objectType, $crmIds, $results);
return $results;
} catch (\Throwable $e) {
$this->handleBatchError($e, $objectType, $crmIds);
}
}
private function validateBatchSize(string $objectType, array $crmIds): void
{
if (count($crmIds) > 100) {
throw new \InvalidArgumentException("Batch size cannot exceed 100 {$objectType}");
}
}
private function createBatchConfiguration(string $objectType): array
{
$configurations = [
'deals' => [
'batchReadRequest' => new \HubSpot\Client\Crm\Deals\Model\BatchReadInputSimplePublicObjectId(),
'inputClass' => \HubSpot\Client\Crm\Deals\Model\SimplePublicObjectId::class,
'api' => $this->getNewInstance()->crm()->deals()->batchApi(),
],
'companies' => [
'batchReadRequest' => new \HubSpot\Client\Crm\Companies\Model\BatchReadInputSimplePublicObjectId(),
'inputClass' => \HubSpot\Client\Crm\Companies\Model\SimplePublicObjectId::class,
'api' => $this->getNewInstance()->crm()->companies()->batchApi(),
],
'contacts' => [
'batchReadRequest' => new \HubSpot\Client\Crm\Contacts\Model\BatchReadInputSimplePublicObjectId(),
'inputClass' => \HubSpot\Client\Crm\Contacts\Model\SimplePublicObjectId::class,
'api' => $this->getNewInstance()->crm()->contacts()->batchApi(),
],
];
if (! isset($configurations[$objectType])) {
throw new \InvalidArgumentException("Unsupported object type: {$objectType}");
}
return $configurations[$objectType];
}
private function prepareBatchRequest(array $batchConfig, array $crmIds, array $fields): object
{
$batchReadRequest = $batchConfig['batchReadRequest'];
$inputClass = $batchConfig['inputClass'];
$inputs = array_map(function ($crmId) use ($inputClass) {
$input = new $inputClass();
$input->setId($crmId);
return $input;
}, $crmIds);
$batchReadRequest->setInputs($inputs);
$batchReadRequest->setProperties($fields);
return $batchReadRequest;
}
private function validateApiResponse($response, string $objectType): void
{
if (! $response) {
throw new CrmException("HubSpot API returned null response for {$objectType} batch read");
}
}
private function processApiResults($response): array
{
$results = [];
$responseResults = $response->getResults();
if ($responseResults) {
foreach ($responseResults as $object) {
if ($object && $object->getId()) {
$results[$object->getId()] = [
'id' => $object->getId(),
'properties' => $object->getProperties() ?: [],
];
}
}
}
return $results;
}
private function logBatchResults(string $objectType, array $crmIds, array $results): void
{
$this->log->info("[HubSpot] Batch fetched {$objectType}", [
'requested_count' => count($crmIds),
'returned_count' => count($results),
'crm_ids' => $crmIds,
]);
}
private function handleBatchError(\Throwable $e, string $objectType, array $crmIds): void
{
$errorMessage = $e->getMessage() ?: 'Unknown error';
$errorTrace = $e->getTraceAsString() ?: 'No trace available';
$this->log->error("[HubSpot] Failed to batch fetch {$objectType}", [
'crm_ids' => $crmIds,
'error' => $errorMessage,
'trace' => $errorTrace,
]);
throw new CrmException("Failed to batch fetch {$objectType}: " . $errorMessage);
}
/**
* Batch read multiple opportunities by their CRM IDs
*
* @param array<string> $crmIds Array of HubSpot deal IDs (max 100)
* @param array<string> $fields Array of property names to fetch
*
* @return array<string, array> Array keyed by CRM ID with opportunity data
*/
public function getOpportunitiesByIds(array $crmIds, array $fields): array
{
return $this->batchReadObjects('deals', $crmIds, $fields);
}
/**
* Batch read multiple companies by their CRM IDs
*
* @param array<string> $crmIds Array of HubSpot company IDs (max 100)
* @param array<string> $fields Array of property names to fetch
*
* @return array<string, array> Array keyed by CRM ID with company data
*/
public function getCompaniesByIds(array $crmIds, array $fields): array
{
return $this->batchReadObjects('companies', $crmIds, $fields);
}
/**
* Batch read multiple contacts by their CRM IDs
*
* @param array<string> $crmIds Array of HubSpot contact IDs (max 100)
* @param array<string> $fields Array of property names to fetch
*
* @return array<string, array> Array keyed by CRM ID with contact data
*/
public function getContactsByIds(array $crmIds, array $fields): array
{
return $this->batchReadObjects('contacts', $crmIds, $fields);
}
/**
* @throws CompanyApiException
* @throws CrmException
*/
public function getAccountById(string $crmId, array $fields): array
{
try {
$company = $this->getNewInstance()->crm()->companies()->basicApi()->getById(
$crmId,
implode(',', $fields),
);
} catch (CompanyApiException $e) {
$this->log->info('[Hubspot] Failed to fetch account', [
'crm_id' => $crmId,
'reason' => $e->getMessage(),
]);
throw $e;
}
if (! $company instanceof CompaniesWithAssociations) {
throw new CrmException('Account not found');
}
return [
'id' => $company->getId(),
'properties' => $company->getProperties(),
];
}
/**
* @throws ContactApiException
* @throws CrmException
*/
public function getContactById(string $crmId, array $fields): array
{
try {
$contact = $this->getNewInstance()->crm()->contacts()->basicApi()->getById(
$crmId,
implode(',', $fields)
);
} catch (ContactApiException $e) {
$this->log->info('[Hubspot] Failed to fetch contact', [
'crm_id' => $crmId,
'reason' => $e->getMessage(),
]);
throw $e;
}
if (! $contact instanceof ContactsWithAssociations) {
throw new CrmException('Contact not found');
}
return [
'id' => $contact->getId(),
'properties' => $contact->getProperties(),
];
}
/**
* This is email search request that Hubspot offers as GET (more generous quota)
*/
public function getContactByEmail(string $email, array $fields = []): array
{
try {
$contact = $this->getNewInstance()->crm()->contacts()->basicApi()->getById(
$email,
implode(',', $fields),
null,
false,
'email'
);
return [
'id' => $contact->getId(),
'properties' => $contact->getProperties(),
];
} catch (ContactApiException $e) {
$this->log->info('[Hubspot] Failed to fetch contact', [
'email' => $email,
'reason' => $e->getMessage(),
]);
return [];
}
}
/**
* @throws CrmException
*/
public function fetchProperty(string $objectType, string $propertyId): Property
{
$result = $this->getNewInstance()->crm()->properties()->coreApi()->getByName($objectType, $propertyId);
if (! $result instanceof Property) {
$this->log->error('[Hubspot] Failed to fetch property', [
'object_type' => $objectType,
'property_id' => $propertyId,
'reason' => $result->getMessage(),
]);
throw new CrmException('Failed to fetch property');
}
return $result;
}
/**
* @return array<CrmFieldOption>
*/
public function fetchPropertyOptions(string $objectType, string $propertyId): array
{
/** @var array<CrmFieldOption> */
return $this->fetchProperty($objectType, $propertyId)->getOptions();
}
/**
* @return array<array{id:string, label:string, deleted:bool}>
*/
public function fetchCallDispositions(): array
{
/** @var Response $response */
$response = $this->getInstance()->engagements()->getCallDispositions();
/**
* @var array<array{
* id:string,
* label:string,
* deleted: bool
* }>
*/
return $response->toArray();
}
/**
* @return array<CrmFieldOption>
*/
public function fetchOpportunityPipelineStages(): array
{
$stages = [];
$apiResponse = $this->getNewInstance()->crm()->pipelines()->pipelinesApi()->getAll('deals');
if ($apiResponse instanceof Error) {
$this->log->error('[Hubspot] Failed to fetch opportunity pipelines', [
'reason' => $apiResponse->getMessage(),
]);
return [];
}
foreach ($apiResponse->getResults() as $pipeline) {
$pipelineStages = array_map(
static function (PipelineStage $stage) {
return [
'id' => $stage->getId(),
'label' => $stage->getLabel(),
];
},
$pipeline->getStages()
);
$stages = array_merge($stages, $pipelineStages);
}
return $stages;
}
public function fetchOpportunityPipelines(): array
{
$pipelines = [];
try {
$apiResponse = $this->makeRequest('/crm/v3/pipelines/deals');
} catch (\Exception $e) {
$this->log->info('[Hubspot] Failed to fetch opportunity pipelines', [
'reason' => $e->getMessage(),
]);
return [];
}
$response = $apiResponse->toArray();
foreach ($response['results'] as $pipeline) {
$pipelines[] = [
'id' => $pipeline['id'],
'label' => $pipeline['label'],
];
}
return $pipelines;
}
/**
* @return array<CrmFieldOption>
*/
public function fetchMeetingOutcomeFieldOptions(Field $field): array
{
return $field->getCrmProviderId() === 'meetingOutcome'
? $this->fetchMeetingOutcomeTypes()
: $this->fetchCallActivityTypes();
}
public function fetchMeetingOutcomeTypes(): array
{
return $this->extractMeetingTypeOptions(
'[URL_WITH_CREDENTIALS] Response $response */
$response = $this->getInstance()
->getClient()
->request('GET', $endpoint);
/**
* @var array<array{
* value: string,
* label: string,
* displayOrder: int
* }> $optionData
*/
$optionData = $response->toArray()['options'] ?? [];
$options = [];
foreach ($optionData as $item) {
$options[] = [
'id' => $item['value'],
'value' => $item['value'],
'label' => $item['label'],
'display_order' => $item['displayOrder'],
];
}
return $options;
}
/**
* @return array<CrmFieldOption>
*/
public function fetchDispositionFieldOptions(): array
{
$options = [];
$dispositions = $this->fetchCallDispositions();
foreach ($dispositions as $disposition) {
if ($disposition['deleted'] !== false) {
continue;
}
$option['value'] = $disposition['id'];
$option['id'] = $disposition['id'];
$option['label'] = $disposition['label'];
$options[] = $option;
}
return $options;
}
/**
* @return array<CrmFieldOption>
*/
public function fetchOpportunityFieldOptions(Field $field): array
{
if ($field->isStageField()) {
return $this->fetchOpportunityPipelineStages();
}
if ($field->isPipelineField()) {
return $this->fetchOpportunityPipelines();
}
return $this->fetchPropertyOptions('deals', $field->getCrmProviderId());
}
/**
* @throws BadRequest
* @throws HubspotException
*/
public function makeRequest(string $endpoint, $method = 'GET', $payload = [], ?string $queryString = null)
{
$endpoint = self::BASE_URL . $endpoint;
if ($method === 'GET') {
$response = $this->getInstance()->getClient()?->request(
method: $method,
endpoint: $endpoint,
query_string: $queryString
);
} else {
$response = $this->getInstance()->getClient()->request($method, $endpoint, [
'json' => ($payload),
]);
}
$max = $response->getHeaderLine('X-HubSpot-RateLimit-Max'); // "110"
$remaining = $response->getHeaderLine('X-HubSpot-RateLimit-Remaining'); // "109"
$interval = $response->getHeaderLine('X-HubSpot-RateLimit-Interval-Milliseconds'); // "10000"
$body = json_decode((string) $response->getBody(), true);
\Illuminate\Support\Facades\Log::channel('custom_channel')->info('$max ' . PHP_EOL . print_r($max, true));
\Illuminate\Support\Facades\Log::channel('custom_channel')->info('$remaining ' . PHP_EOL . print_r($remaining, true));
\Illuminate\Support\Facades\Log::channel('custom_channel')->info('$interval ' . PHP_EOL . print_r($interval, true));
\Illuminate\Support\Facades\Log::channel('custom_channel')->info('$body ' . PHP_EOL . print_r($body, true));
return $response;
}
/**
* @throws BadRequest
* @throws HubspotException
*/
public function createMeeting(array $payload): Response
{
$endpoint = '/crm/v3/objects/meetings';
return $this->makeRequest($endpoint, 'POST', $payload);
}
/**
* @throws BadRequest
* @throws HubspotException
*/
public function updateMeeting(string $meetingId, array $payload): Response
{
$endpoint = '/crm/v3/objects/meetings/' . $meetingId;
return $this->makeRequest($endpoint, 'PATCH', $payload);
}
/**
* @throws \Exception
*/
public function createNote(
string $body,
string $ownerId,
int $timestamp,
string $objectId,
NoteObject $noteObject
): ?string {
try {
$noteInput = new SimplePublicObjectInput([
'properties' => [
'hs_note_body' => $body,
'hubspot_owner_id' => $ownerId,
'hs_timestamp' => $timestamp,
],
]);
// Create note
$note = $this->getNewInstance()->crm()->objects()->basicApi()->create('note', $noteInput);
$this->getNewInstance()->crm()->objects()->associationsApi()->create(
'note',
$note->getId(),
$this->getNoteObject($noteObject),
$objectId,
$this->getNoteAssociationType($noteObject),
);
return $note->getId();
} catch (\Exception $e) {
$this->log->error('[Hubspot] Failed to create note', [
'objectId' => $objectId,
'noteObject' => $noteObject->getObjectType(),
'reason' => $e->getMessage(),
]);
\Sentry::captureException($e);
}
return null;
}
public function updateEngagement(string $objectId, array $engagement, array $metadata): void
{
$this->getInstance()->engagements()->update($objectId, $engagement, $metadata);
}
public function getEngagementData(string $engagementId): array
{
$engagement = $this->getInstance()->engagements()->get($engagementId);
return $engagement->toArray();
}
public function createEngagement(array $engagement, array $associations, array $metadata): Response
{
return $this->getInstance()
->engagements()
->create($engagement, $associations, $metadata);
}
public function isUnauthorizedException(\Exception $e): bool
{
// Check for specific HubSpot API exception types first
if ($e instanceof BadRequest) {
// BadRequest can contain 401 status codes
return $e->getCode() === 401;
}
// Check for HTTP client exceptions with status codes
if ($e instanceof \GuzzleHttp\Exception\RequestException && $e->hasResponse()) {
$response = $e->getResponse();
if ($response !== null) {
return $response->getStatusCode() === 401;
}
}
// Check for Guzzle HTTP exceptions
if ($e instanceof \GuzzleHttp\Exception\ClientException) {
return $e->getCode() === 401;
}
// Fallback to string matching as last resort, but be more specific
$message = strtolower($e->getMessage());
return str_contains($message, '401 unauthorized') ||
str_contains($message, 'http 401') ||
str_contains($message, 'status code 401') ||
(preg_match('/\b401\b/', $message) && str_contains($message, 'unauthorized'));
}
/**
* Validates and refreshes the access token if needed before API requests.
* This ensures long-running processes don't fail due to token expiration.
*
* @throws SocialAccountTokenInvalidException
*/
public function ensureValidToken(): void
{
if ($this->oauthAccount === null) {
return;
}
$newToken = $this->tokenManager->ensureValidToken($this->oauthAccount);
if ($newToken !== null) {
$this->accessToken = $newToken;
}
}
public function getConfig()
{
return $this->config;
}
// returns only active (archived=false)
public function getOwners(): array
{
return $this->getNewInstance()->crm()->owners()->getAll();
}
/**
* @param bool $archived
*
* @return array<Owner>|[]
*/
public function getOwnersArchived(bool $archived = true): array
{
$endpoint = '/crm/v3/owners';
$queryParams = [
'archived' => $archived ? 'true' : 'false',
];
$queryString = http_build_query($queryParams);
$owners = [];
try {
$response = $this->makeRequest(endpoint: $endpoint, queryString: $queryString);
$responseData = $response?->toArray();
foreach ($responseData['results'] as $result) {
try {
$owners[] = Owner::create($result);
} catch (Throwable $e) {
$this->log->error('[HubSpot] Failed to process owner data', [
'result' => $result,
'error' => $e->getMessage(),
]);
continue;
}
}
} catch (Throwable $e) {
$this->log->error('HubSpot] Failed to fetch owners', [
'archived' => $archived,
'error' => $e->getMessage(),
]);
return [];
}
return $owners;
}
public function getMeeting(string $engagementId): ObjectWithAssociations
{
return $this->getNewInstance()->crm()->objects()->basicApi()
->getById('meeting', $engagementId, null, 'contact,company,deal');
}
public function deleteEngagement(string $engagementId): void
{
$this->getInstance()->engagements()->delete((int) $engagementId);
}
public function getAssociationsData(array $ids, string $fromObject, string $toObject): array
{
$associationData = [];
$idChunks = array_chunk($ids, self::ASSOCIATIONS_BATCH_SIZE_LIMIT);
foreach ($idChunks as $idChunk) {
try {
$batchInput = new \HubSpot\Client\Crm\Associations\Model\BatchInputPublicObjectId();
$batchInput->setInputs(array_map(function ($id) {
$publicObjectId = new \HubSpot\Client\Crm\Associations\Model\PublicObjectId();
$publicObjectId->setId($id);
return $publicObjectId;
}, $idChunk));
$associatedObjectsData = $this
->getNewInstance()
->crm()
->associations()
->batchApi()
->read($fromObject, $toObject, $batchInput);
if ($associatedObjectsData instanceof \HubSpot\Client\Crm\Associations\Model\BatchResponsePublicAssociationMulti) {
foreach ($associatedObjectsData->getResults() as $association) {
$from = $association->getFrom()->getId();
$toAssociations = $association->getTo();
if (! empty($toAssociations)) {
$associationData[$from] = array_map(function ($item) {
return $item->getId();
}, $toAssociations);
}
}
}
} catch (\Exception $e) {
$this->log->error('[Hubspot] Failed to fetch associations', [
'from_object' => $fromObject,
'to_object' => $toObject,
'reason' => $e->getMessage(),
]);
}
}
return $associationData;
}
/**
* @throws \Exception
*/
private function getNoteAssociationType(NoteObject $noteObject): string
{
return match($noteObject) {
NoteObject::Opportunity => 'note_to_deal',
NoteObject::Lead, NoteObject::Contact => 'note_to_contact', // or 'note_to_lead' if your portal supports it
NoteObject::Account => 'note_to_company',
NoteObject::Call, NoteObject::Event => throw new \Exception('Not supported'),
};
}
/**
* @throws \Exception
*/
private function getNoteObject(NoteObject $noteObject): string
{
return match($noteObject) {
NoteObject::Opportunity => 'deal',
NoteObject::Lead, NoteObject::Contact => 'contact',
NoteObject::Account => 'company',
NoteObject::Call, NoteObject::Event => throw new \Exception('Not supported'),
};
}
public function addAssociations(string $objectType, string $associationType, array $payload): Response
{
$endpoint = "/crm/v4/associations/$objectType/$associationType/batch/create";
return $this->makeRequest($endpoint, 'POST', $payload);
}
public function removeAssociations(string $objectType, string $associationType, array $payload): Response
{
$endpoint = "/crm/v4/associations/$objectType/$associationType/batch/archive";
return $this->makeRequest($endpoint, 'POST', $payload);
}
}
Project
Project
New File or Directory…
Expand Selected
Collapse All
Options
Hide...
|
[{"role":"AXButton","text" [{"role":"AXButton","text":"Project: faVsco.js, menu","depth":5,"on_screen":true,"help_text":"~/jiminny/app","role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"master, menu","depth":5,"on_screen":true,"help_text":"Git Branch: master","role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Start Listening for PHP Debug Connections","depth":5,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"AskJiminnyReportActivityServiceTest","depth":6,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Run 'AskJiminnyReportActivityServiceTest'","depth":6,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Debug 'AskJiminnyReportActivityServiceTest'","depth":6,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"More Actions","depth":6,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"JetBrains AI","depth":5,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Search Everywhere","depth":5,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"IDE and Project Settings","depth":5,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Sync Changes","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.018055556,"height":0.026666667},"on_screen":false,"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Hide This Notification","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.018055556,"height":0.026666667},"on_screen":false,"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Code changed:","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.088194445,"height":0.027777778},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Hide","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.018055556,"height":0.026666667},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXTextArea","text":"Editor for custom.log","depth":4,"on_screen":true,"role_description":"text entry area","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Sync Changes","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.018055556,"height":0.026666667},"on_screen":false,"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Hide This Notification","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.018055556,"height":0.026666667},"on_screen":false,"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Code changed:","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.088194445,"height":0.027777778},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Hide","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.018055556,"height":0.026666667},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"2","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.016666668,"height":0.02111111},"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"60","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.021527778,"height":0.02111111},"on_screen":false,"role_description":"text"},{"role":"AXButton","text":"Previous Highlighted Error","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.015277778,"height":0.025555555},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Next Highlighted Error","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.014583333,"height":0.025555555},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXTextArea","text":"<?php\n\ndeclare(strict_types=1);\n\nnamespace Jiminny\\Services\\Crm\\Hubspot;\n\nuse HubSpot\\Client\\Crm\\Deals\\ApiException as DealApiException;\nuse HubSpot\\Client\\Crm\\Contacts\\ApiException as ContactApiException;\nuse HubSpot\\Client\\Crm\\Companies\\ApiException as CompanyApiException;\nuse HubSpot\\Client\\Crm\\Contacts\\Model\\SimplePublicObjectWithAssociations as ContactsWithAssociations;\nuse HubSpot\\Client\\Crm\\Companies\\Model\\SimplePublicObjectWithAssociations as CompaniesWithAssociations;\nuse HubSpot\\Client\\Crm\\Deals\\Model\\SimplePublicObjectWithAssociations as DealWithAssociations;\nuse HubSpot\\Client\\Crm\\Objects\\Model\\SimplePublicObjectInput;\nuse HubSpot\\Client\\Crm\\Objects\\Model\\SimplePublicObjectWithAssociations as ObjectWithAssociations;\nuse HubSpot\\Client\\Crm\\Pipelines\\Model\\Error;\nuse HubSpot\\Client\\Crm\\Pipelines\\Model\\PipelineStage;\nuse HubSpot\\Client\\Crm\\Properties\\Model\\Property;\nuse HubSpot\\Discovery\\Discovery;\nuse Jiminny\\Component\\Utility\\Service\\ProviderRateLimiter;\nuse Jiminny\\Exceptions\\CrmException;\nuse Jiminny\\Exceptions\\RateLimitException;\nuse Jiminny\\Exceptions\\SocialAccountTokenInvalidException;\nuse Jiminny\\Jobs\\Crm\\NoteObject;\nuse Jiminny\\Models\\Crm\\Field;\nuse Jiminny\\Services\\Crm\\BaseClient;\nuse Jiminny\\Services\\Crm\\Hubspot\\DTO\\Response\\Owner;\nuse Jiminny\\Services\\SocialAccountService;\nuse SevenShores\\Hubspot\\Exceptions\\BadRequest;\nuse SevenShores\\Hubspot\\Exceptions\\HubspotException;\nuse SevenShores\\Hubspot\\Factory;\nuse SevenShores\\Hubspot\\Http\\Response;\nuse Jiminny\\Services\\Crm\\Hubspot\\Pagination\\HubspotPaginationService;\nuse Throwable;\n\n/**\n * @phpstan-type CrmFieldOption array{id:string, label:string, value?:string}\n */\nclass Client extends BaseClient implements HubspotClientInterface\n{\n public const string MIN_API_VERSION = '2';\n\n public const string BASE_URL = 'https://api.hubapi.com';\n\n public const int ASSOCIATIONS_BATCH_SIZE_LIMIT = 1000;\n\n private HubspotPaginationService $paginationService;\n private HubspotTokenManager $tokenManager;\n private ProviderRateLimiter $rateLimiter;\n\n public function __construct(\n SocialAccountService $socialAccountService,\n HubspotPaginationService $paginationService,\n HubspotTokenManager $tokenManager,\n ProviderRateLimiter $rateLimiter,\n ) {\n parent::__construct($socialAccountService);\n $this->paginationService = $paginationService;\n $this->tokenManager = $tokenManager;\n $this->rateLimiter = $rateLimiter;\n\n $this->setBaseUrl(self::BASE_URL);\n $this->setVersion(self::MIN_API_VERSION);\n }\n\n /**\n * Single entry point for every HubSpot API call. Enforces the per-portal\n * rate limit configured in the rate_limits table (morphed to the current\n * Configuration) and reacts to a real 429 from HubSpot by translating it\n * into a RateLimitException carrying Retry-After.\n *\n * Wrap any outbound HubSpot call (SDK or raw HTTP) like:\n *\n * $this->executeRequest(fn () => $this->getNewInstance()->crm()->...);\n *\n * @template T\n * @param callable(): T $apiCall\n * @return T\n *\n * @throws RateLimitException\n */\n private function executeRequest(callable $apiCall)\n {\n if (! $this->rateLimiter->canMakeRequest($this->config)) {\n $retryAfter = $this->rateLimiter->requestAvailableIn($this->config);\n\n $this->log->warning('[Hubspot] Rate limit exceeded, deferring request', [\n 'team_id' => $this->config->team_id,\n 'config_id' => $this->config->getId(),\n 'retry_after' => $retryAfter,\n ]);\n\n throw new RateLimitException(\n 'Hubspot rate limit reached for configuration ' . $this->config->getId(),\n $retryAfter,\n );\n }\n\n $this->rateLimiter->incrementRequestCount($this->config);\n\n try {\n return $apiCall();\n } catch (Throwable $e) {\n if ($this->isHubspotRateLimit($e)) {\n $retryAfter = $this->parseRetryAfter($e);\n\n $this->log->warning('[Hubspot] Received 429 from API', [\n 'team_id' => $this->config->team_id,\n 'config_id' => $this->config->getId(),\n 'retry_after' => $retryAfter,\n 'reason' => $e->getMessage(),\n ]);\n\n throw new RateLimitException('Hubspot returned 429', $retryAfter, $e);\n }\n\n throw $e;\n }\n }\n\n private function isHubspotRateLimit(Throwable $e): bool\n {\n return method_exists($e, 'getCode') && (int) $e->getCode() === 429;\n }\n\n private function parseRetryAfter(Throwable $e): int\n {\n if (method_exists($e, 'getResponseHeaders')) {\n $headers = $e->getResponseHeaders() ?: [];\n $value = $headers['Retry-After'] ?? $headers['retry-after'] ?? null;\n if (is_array($value)) {\n $value = $value[0] ?? null;\n }\n if (is_numeric($value)) {\n return (int) $value;\n }\n }\n\n return 10;\n }\n\n public function getMinimumApiVersion(): string\n {\n return self::MIN_API_VERSION;\n }\n\n public function getInstance(): Factory\n {\n return new Factory([\n 'key' => $this->accessToken,\n 'oauth2' => true,\n 'base_url' => $this->baseUrl,\n ]);\n }\n\n public function getNewInstance(): Discovery\n {\n return \\HubSpot\\Factory::createWithAccessToken($this->accessToken);\n }\n\n /**\n * Secondly and daily limits for Hubspot API\n *\n * Product Tier: Free & Starter | Professional & Enterprise | API add-on (any tier)\n * Burst: 100/10 seconds | 150/10 seconds | 200/10 seconds\n * Daily: 250,000 | 500,000 | 1,000,000\n *\n * Official documentation states: The search endpoints are rate limited to five requests per second.\n * Since with 5 RPS were still hitting secondly rate limits we lowered it to 4\n */\n public function getPaginatedData(array $payload, string $type, int $offset = 0): array\n {\n $total = 0;\n $lastId = null;\n $rows = [];\n foreach ($this->getPaginatedDataGenerator($payload, $type, $offset, $total, $lastId) as $row) {\n $rows[] = $row;\n }\n\n return ['results' => $rows, 'total' => $total, 'last_record' => $lastId];\n }\n\n /**\n * @throws HubspotException\n * @throws SocialAccountTokenInvalidException\n * @throws BadRequest\n */\n public function getPaginatedDataGenerator(\n array $payload,\n string $type,\n int $offset = 0,\n int &$total = 0,\n ?string &$lastRecordId = null\n ): \\Generator {\n return $this->paginationService->getPaginatedDataGenerator(\n $this,\n $payload,\n $type,\n $offset,\n $total,\n $lastRecordId\n );\n }\n\n /**\n * @throws DealApiException\n * @throws CrmException\n */\n public function getOpportunityById(string $crmId, array $fields): array\n {\n try {\n $deal = $this->executeRequest(fn () => $this->getNewInstance()->crm()->deals()->basicApi()->getById(\n $crmId,\n implode(',', $fields),\n 'companies,contacts'\n ));\n } catch (DealApiException $e) {\n $this->log->info('[Hubspot] Failed to fetch opportunity', [\n 'crm_id' => $crmId,\n 'reason' => $e->getMessage(),\n ]);\n\n throw $e;\n }\n\n if (! $deal instanceof DealWithAssociations) {\n throw new CrmException('Deal not found');\n }\n\n return [\n 'id' => $deal->getId(),\n 'properties' => $deal->getProperties(),\n 'associations' => $deal->getAssociations(),\n ];\n }\n\n /**\n * Generic batch read method for HubSpot objects\n *\n * @param string $objectType The object type ('deals', 'companies', 'contacts')\n * @param array<string> $crmIds Array of HubSpot object IDs (max 100)\n * @param array<string> $fields Array of property names to fetch\n *\n * @return array<string, array> Array keyed by CRM ID with object data\n */\n private function batchReadObjects(string $objectType, array $crmIds, array $fields): array\n {\n if (empty($crmIds)) {\n return [];\n }\n\n $this->validateBatchSize($objectType, $crmIds);\n $this->ensureValidToken();\n\n try {\n $batchConfig = $this->createBatchConfiguration($objectType);\n $batchReadRequest = $this->prepareBatchRequest($batchConfig, $crmIds, $fields);\n $response = $batchConfig['api']->read($batchReadRequest);\n\n $this->validateApiResponse($response, $objectType);\n\n $results = $this->processApiResults($response);\n $this->logBatchResults($objectType, $crmIds, $results);\n\n return $results;\n } catch (\\Throwable $e) {\n $this->handleBatchError($e, $objectType, $crmIds);\n }\n }\n\n private function validateBatchSize(string $objectType, array $crmIds): void\n {\n if (count($crmIds) > 100) {\n throw new \\InvalidArgumentException(\"Batch size cannot exceed 100 {$objectType}\");\n }\n }\n\n private function createBatchConfiguration(string $objectType): array\n {\n $configurations = [\n 'deals' => [\n 'batchReadRequest' => new \\HubSpot\\Client\\Crm\\Deals\\Model\\BatchReadInputSimplePublicObjectId(),\n 'inputClass' => \\HubSpot\\Client\\Crm\\Deals\\Model\\SimplePublicObjectId::class,\n 'api' => $this->getNewInstance()->crm()->deals()->batchApi(),\n ],\n 'companies' => [\n 'batchReadRequest' => new \\HubSpot\\Client\\Crm\\Companies\\Model\\BatchReadInputSimplePublicObjectId(),\n 'inputClass' => \\HubSpot\\Client\\Crm\\Companies\\Model\\SimplePublicObjectId::class,\n 'api' => $this->getNewInstance()->crm()->companies()->batchApi(),\n ],\n 'contacts' => [\n 'batchReadRequest' => new \\HubSpot\\Client\\Crm\\Contacts\\Model\\BatchReadInputSimplePublicObjectId(),\n 'inputClass' => \\HubSpot\\Client\\Crm\\Contacts\\Model\\SimplePublicObjectId::class,\n 'api' => $this->getNewInstance()->crm()->contacts()->batchApi(),\n ],\n ];\n\n if (! isset($configurations[$objectType])) {\n throw new \\InvalidArgumentException(\"Unsupported object type: {$objectType}\");\n }\n\n return $configurations[$objectType];\n }\n\n private function prepareBatchRequest(array $batchConfig, array $crmIds, array $fields): object\n {\n $batchReadRequest = $batchConfig['batchReadRequest'];\n $inputClass = $batchConfig['inputClass'];\n\n $inputs = array_map(function ($crmId) use ($inputClass) {\n $input = new $inputClass();\n $input->setId($crmId);\n\n return $input;\n }, $crmIds);\n\n $batchReadRequest->setInputs($inputs);\n $batchReadRequest->setProperties($fields);\n\n return $batchReadRequest;\n }\n\n private function validateApiResponse($response, string $objectType): void\n {\n if (! $response) {\n throw new CrmException(\"HubSpot API returned null response for {$objectType} batch read\");\n }\n }\n\n private function processApiResults($response): array\n {\n $results = [];\n $responseResults = $response->getResults();\n\n if ($responseResults) {\n foreach ($responseResults as $object) {\n if ($object && $object->getId()) {\n $results[$object->getId()] = [\n 'id' => $object->getId(),\n 'properties' => $object->getProperties() ?: [],\n ];\n }\n }\n }\n\n return $results;\n }\n\n private function logBatchResults(string $objectType, array $crmIds, array $results): void\n {\n $this->log->info(\"[HubSpot] Batch fetched {$objectType}\", [\n 'requested_count' => count($crmIds),\n 'returned_count' => count($results),\n 'crm_ids' => $crmIds,\n ]);\n }\n\n private function handleBatchError(\\Throwable $e, string $objectType, array $crmIds): void\n {\n $errorMessage = $e->getMessage() ?: 'Unknown error';\n $errorTrace = $e->getTraceAsString() ?: 'No trace available';\n\n $this->log->error(\"[HubSpot] Failed to batch fetch {$objectType}\", [\n 'crm_ids' => $crmIds,\n 'error' => $errorMessage,\n 'trace' => $errorTrace,\n ]);\n\n throw new CrmException(\"Failed to batch fetch {$objectType}: \" . $errorMessage);\n }\n\n /**\n * Batch read multiple opportunities by their CRM IDs\n *\n * @param array<string> $crmIds Array of HubSpot deal IDs (max 100)\n * @param array<string> $fields Array of property names to fetch\n *\n * @return array<string, array> Array keyed by CRM ID with opportunity data\n */\n public function getOpportunitiesByIds(array $crmIds, array $fields): array\n {\n return $this->batchReadObjects('deals', $crmIds, $fields);\n }\n\n /**\n * Batch read multiple companies by their CRM IDs\n *\n * @param array<string> $crmIds Array of HubSpot company IDs (max 100)\n * @param array<string> $fields Array of property names to fetch\n *\n * @return array<string, array> Array keyed by CRM ID with company data\n */\n public function getCompaniesByIds(array $crmIds, array $fields): array\n {\n return $this->batchReadObjects('companies', $crmIds, $fields);\n }\n\n /**\n * Batch read multiple contacts by their CRM IDs\n *\n * @param array<string> $crmIds Array of HubSpot contact IDs (max 100)\n * @param array<string> $fields Array of property names to fetch\n *\n * @return array<string, array> Array keyed by CRM ID with contact data\n */\n public function getContactsByIds(array $crmIds, array $fields): array\n {\n return $this->batchReadObjects('contacts', $crmIds, $fields);\n }\n\n /**\n * @throws CompanyApiException\n * @throws CrmException\n */\n public function getAccountById(string $crmId, array $fields): array\n {\n try {\n $company = $this->getNewInstance()->crm()->companies()->basicApi()->getById(\n $crmId,\n implode(',', $fields),\n );\n } catch (CompanyApiException $e) {\n $this->log->info('[Hubspot] Failed to fetch account', [\n 'crm_id' => $crmId,\n 'reason' => $e->getMessage(),\n ]);\n\n throw $e;\n }\n\n if (! $company instanceof CompaniesWithAssociations) {\n throw new CrmException('Account not found');\n }\n\n return [\n 'id' => $company->getId(),\n 'properties' => $company->getProperties(),\n ];\n }\n\n /**\n * @throws ContactApiException\n * @throws CrmException\n */\n public function getContactById(string $crmId, array $fields): array\n {\n try {\n $contact = $this->getNewInstance()->crm()->contacts()->basicApi()->getById(\n $crmId,\n implode(',', $fields)\n );\n } catch (ContactApiException $e) {\n $this->log->info('[Hubspot] Failed to fetch contact', [\n 'crm_id' => $crmId,\n 'reason' => $e->getMessage(),\n ]);\n\n throw $e;\n }\n\n if (! $contact instanceof ContactsWithAssociations) {\n throw new CrmException('Contact not found');\n }\n\n return [\n 'id' => $contact->getId(),\n 'properties' => $contact->getProperties(),\n ];\n }\n\n /**\n * This is email search request that Hubspot offers as GET (more generous quota)\n */\n public function getContactByEmail(string $email, array $fields = []): array\n {\n try {\n $contact = $this->getNewInstance()->crm()->contacts()->basicApi()->getById(\n $email,\n implode(',', $fields),\n null,\n false,\n 'email'\n );\n\n return [\n 'id' => $contact->getId(),\n 'properties' => $contact->getProperties(),\n ];\n } catch (ContactApiException $e) {\n $this->log->info('[Hubspot] Failed to fetch contact', [\n 'email' => $email,\n 'reason' => $e->getMessage(),\n ]);\n\n return [];\n }\n }\n\n /**\n * @throws CrmException\n */\n public function fetchProperty(string $objectType, string $propertyId): Property\n {\n $result = $this->getNewInstance()->crm()->properties()->coreApi()->getByName($objectType, $propertyId);\n\n if (! $result instanceof Property) {\n $this->log->error('[Hubspot] Failed to fetch property', [\n 'object_type' => $objectType,\n 'property_id' => $propertyId,\n 'reason' => $result->getMessage(),\n ]);\n\n throw new CrmException('Failed to fetch property');\n }\n\n return $result;\n }\n\n /**\n * @return array<CrmFieldOption>\n */\n public function fetchPropertyOptions(string $objectType, string $propertyId): array\n {\n /** @var array<CrmFieldOption> */\n return $this->fetchProperty($objectType, $propertyId)->getOptions();\n }\n\n /**\n * @return array<array{id:string, label:string, deleted:bool}>\n */\n public function fetchCallDispositions(): array\n {\n /** @var Response $response */\n $response = $this->getInstance()->engagements()->getCallDispositions();\n\n /**\n * @var array<array{\n * id:string,\n * label:string,\n * deleted: bool\n * }>\n */\n return $response->toArray();\n }\n\n /**\n * @return array<CrmFieldOption>\n */\n public function fetchOpportunityPipelineStages(): array\n {\n $stages = [];\n $apiResponse = $this->getNewInstance()->crm()->pipelines()->pipelinesApi()->getAll('deals');\n\n if ($apiResponse instanceof Error) {\n $this->log->error('[Hubspot] Failed to fetch opportunity pipelines', [\n 'reason' => $apiResponse->getMessage(),\n ]);\n\n return [];\n }\n\n foreach ($apiResponse->getResults() as $pipeline) {\n $pipelineStages = array_map(\n static function (PipelineStage $stage) {\n return [\n 'id' => $stage->getId(),\n 'label' => $stage->getLabel(),\n ];\n },\n $pipeline->getStages()\n );\n\n $stages = array_merge($stages, $pipelineStages);\n }\n\n return $stages;\n }\n\n public function fetchOpportunityPipelines(): array\n {\n $pipelines = [];\n\n try {\n $apiResponse = $this->makeRequest('/crm/v3/pipelines/deals');\n } catch (\\Exception $e) {\n $this->log->info('[Hubspot] Failed to fetch opportunity pipelines', [\n 'reason' => $e->getMessage(),\n ]);\n\n return [];\n }\n\n $response = $apiResponse->toArray();\n\n foreach ($response['results'] as $pipeline) {\n $pipelines[] = [\n 'id' => $pipeline['id'],\n 'label' => $pipeline['label'],\n ];\n }\n\n return $pipelines;\n }\n\n /**\n * @return array<CrmFieldOption>\n */\n public function fetchMeetingOutcomeFieldOptions(Field $field): array\n {\n return $field->getCrmProviderId() === 'meetingOutcome'\n ? $this->fetchMeetingOutcomeTypes()\n : $this->fetchCallActivityTypes();\n }\n\n public function fetchMeetingOutcomeTypes(): array\n {\n return $this->extractMeetingTypeOptions(\n 'https://api.hubapi.com/crm/v3/properties/meeting/hs_meeting_outcome'\n );\n }\n\n public function fetchCallActivityTypes(): array\n {\n return $this->extractMeetingTypeOptions(\n 'https://api.hubapi.com/crm/v3/properties/call/hs_activity_type'\n );\n }\n\n private function extractMeetingTypeOptions(string $endpoint): array\n {\n /** @var Response $response */\n $response = $this->getInstance()\n ->getClient()\n ->request('GET', $endpoint);\n\n /**\n * @var array<array{\n * value: string,\n * label: string,\n * displayOrder: int\n * }> $optionData\n */\n $optionData = $response->toArray()['options'] ?? [];\n\n $options = [];\n foreach ($optionData as $item) {\n $options[] = [\n 'id' => $item['value'],\n 'value' => $item['value'],\n 'label' => $item['label'],\n 'display_order' => $item['displayOrder'],\n ];\n }\n\n return $options;\n }\n\n /**\n * @return array<CrmFieldOption>\n */\n public function fetchDispositionFieldOptions(): array\n {\n $options = [];\n\n $dispositions = $this->fetchCallDispositions();\n\n foreach ($dispositions as $disposition) {\n if ($disposition['deleted'] !== false) {\n continue;\n }\n\n $option['value'] = $disposition['id'];\n $option['id'] = $disposition['id'];\n $option['label'] = $disposition['label'];\n\n $options[] = $option;\n }\n\n return $options;\n }\n\n /**\n * @return array<CrmFieldOption>\n */\n public function fetchOpportunityFieldOptions(Field $field): array\n {\n if ($field->isStageField()) {\n return $this->fetchOpportunityPipelineStages();\n }\n\n if ($field->isPipelineField()) {\n return $this->fetchOpportunityPipelines();\n }\n\n return $this->fetchPropertyOptions('deals', $field->getCrmProviderId());\n }\n\n /**\n * @throws BadRequest\n * @throws HubspotException\n */\n public function makeRequest(string $endpoint, $method = 'GET', $payload = [], ?string $queryString = null)\n {\n $endpoint = self::BASE_URL . $endpoint;\n\n if ($method === 'GET') {\n $response = $this->getInstance()->getClient()?->request(\n method: $method,\n endpoint: $endpoint,\n query_string: $queryString\n );\n } else {\n $response = $this->getInstance()->getClient()->request($method, $endpoint, [\n 'json' => ($payload),\n ]);\n }\n\n $max = $response->getHeaderLine('X-HubSpot-RateLimit-Max'); // \"110\"\n $remaining = $response->getHeaderLine('X-HubSpot-RateLimit-Remaining'); // \"109\"\n $interval = $response->getHeaderLine('X-HubSpot-RateLimit-Interval-Milliseconds'); // \"10000\"\n $body = json_decode((string) $response->getBody(), true);\n\n \\Illuminate\\Support\\Facades\\Log::channel('custom_channel')->info('$max ' . PHP_EOL . print_r($max, true));\n \\Illuminate\\Support\\Facades\\Log::channel('custom_channel')->info('$remaining ' . PHP_EOL . print_r($remaining, true));\n \\Illuminate\\Support\\Facades\\Log::channel('custom_channel')->info('$interval ' . PHP_EOL . print_r($interval, true));\n \\Illuminate\\Support\\Facades\\Log::channel('custom_channel')->info('$body ' . PHP_EOL . print_r($body, true));\n\n return $response;\n }\n\n /**\n * @throws BadRequest\n * @throws HubspotException\n */\n public function createMeeting(array $payload): Response\n {\n $endpoint = '/crm/v3/objects/meetings';\n\n return $this->makeRequest($endpoint, 'POST', $payload);\n }\n\n /**\n * @throws BadRequest\n * @throws HubspotException\n */\n public function updateMeeting(string $meetingId, array $payload): Response\n {\n $endpoint = '/crm/v3/objects/meetings/' . $meetingId;\n\n return $this->makeRequest($endpoint, 'PATCH', $payload);\n }\n\n /**\n * @throws \\Exception\n */\n public function createNote(\n string $body,\n string $ownerId,\n int $timestamp,\n string $objectId,\n NoteObject $noteObject\n ): ?string {\n try {\n $noteInput = new SimplePublicObjectInput([\n 'properties' => [\n 'hs_note_body' => $body,\n 'hubspot_owner_id' => $ownerId,\n 'hs_timestamp' => $timestamp,\n ],\n ]);\n\n // Create note\n $note = $this->getNewInstance()->crm()->objects()->basicApi()->create('note', $noteInput);\n\n $this->getNewInstance()->crm()->objects()->associationsApi()->create(\n 'note',\n $note->getId(),\n $this->getNoteObject($noteObject),\n $objectId,\n $this->getNoteAssociationType($noteObject),\n );\n\n return $note->getId();\n } catch (\\Exception $e) {\n $this->log->error('[Hubspot] Failed to create note', [\n 'objectId' => $objectId,\n 'noteObject' => $noteObject->getObjectType(),\n 'reason' => $e->getMessage(),\n ]);\n\n \\Sentry::captureException($e);\n }\n\n return null;\n }\n\n public function updateEngagement(string $objectId, array $engagement, array $metadata): void\n {\n $this->getInstance()->engagements()->update($objectId, $engagement, $metadata);\n }\n\n public function getEngagementData(string $engagementId): array\n {\n $engagement = $this->getInstance()->engagements()->get($engagementId);\n\n return $engagement->toArray();\n }\n\n public function createEngagement(array $engagement, array $associations, array $metadata): Response\n {\n return $this->getInstance()\n ->engagements()\n ->create($engagement, $associations, $metadata);\n }\n\n public function isUnauthorizedException(\\Exception $e): bool\n {\n // Check for specific HubSpot API exception types first\n if ($e instanceof BadRequest) {\n // BadRequest can contain 401 status codes\n return $e->getCode() === 401;\n }\n\n // Check for HTTP client exceptions with status codes\n if ($e instanceof \\GuzzleHttp\\Exception\\RequestException && $e->hasResponse()) {\n $response = $e->getResponse();\n if ($response !== null) {\n return $response->getStatusCode() === 401;\n }\n }\n\n // Check for Guzzle HTTP exceptions\n if ($e instanceof \\GuzzleHttp\\Exception\\ClientException) {\n return $e->getCode() === 401;\n }\n\n // Fallback to string matching as last resort, but be more specific\n $message = strtolower($e->getMessage());\n\n return str_contains($message, '401 unauthorized') ||\n str_contains($message, 'http 401') ||\n str_contains($message, 'status code 401') ||\n (preg_match('/\\b401\\b/', $message) && str_contains($message, 'unauthorized'));\n }\n\n /**\n * Validates and refreshes the access token if needed before API requests.\n * This ensures long-running processes don't fail due to token expiration.\n *\n * @throws SocialAccountTokenInvalidException\n */\n public function ensureValidToken(): void\n {\n if ($this->oauthAccount === null) {\n return;\n }\n\n $newToken = $this->tokenManager->ensureValidToken($this->oauthAccount);\n if ($newToken !== null) {\n $this->accessToken = $newToken;\n }\n }\n\n public function getConfig()\n {\n return $this->config;\n }\n\n // returns only active (archived=false)\n public function getOwners(): array\n {\n return $this->getNewInstance()->crm()->owners()->getAll();\n }\n\n /**\n * @param bool $archived\n *\n * @return array<Owner>|[]\n */\n public function getOwnersArchived(bool $archived = true): array\n {\n $endpoint = '/crm/v3/owners';\n $queryParams = [\n 'archived' => $archived ? 'true' : 'false',\n ];\n $queryString = http_build_query($queryParams);\n\n $owners = [];\n\n try {\n $response = $this->makeRequest(endpoint: $endpoint, queryString: $queryString);\n $responseData = $response?->toArray();\n\n foreach ($responseData['results'] as $result) {\n try {\n $owners[] = Owner::create($result);\n } catch (Throwable $e) {\n $this->log->error('[HubSpot] Failed to process owner data', [\n 'result' => $result,\n 'error' => $e->getMessage(),\n ]);\n\n continue;\n }\n }\n } catch (Throwable $e) {\n $this->log->error('HubSpot] Failed to fetch owners', [\n 'archived' => $archived,\n 'error' => $e->getMessage(),\n ]);\n\n return [];\n }\n\n return $owners;\n }\n\n public function getMeeting(string $engagementId): ObjectWithAssociations\n {\n return $this->getNewInstance()->crm()->objects()->basicApi()\n ->getById('meeting', $engagementId, null, 'contact,company,deal');\n }\n\n public function deleteEngagement(string $engagementId): void\n {\n $this->getInstance()->engagements()->delete((int) $engagementId);\n }\n\n public function getAssociationsData(array $ids, string $fromObject, string $toObject): array\n {\n $associationData = [];\n $idChunks = array_chunk($ids, self::ASSOCIATIONS_BATCH_SIZE_LIMIT);\n\n foreach ($idChunks as $idChunk) {\n try {\n $batchInput = new \\HubSpot\\Client\\Crm\\Associations\\Model\\BatchInputPublicObjectId();\n $batchInput->setInputs(array_map(function ($id) {\n $publicObjectId = new \\HubSpot\\Client\\Crm\\Associations\\Model\\PublicObjectId();\n $publicObjectId->setId($id);\n\n return $publicObjectId;\n }, $idChunk));\n\n $associatedObjectsData = $this\n ->getNewInstance()\n ->crm()\n ->associations()\n ->batchApi()\n ->read($fromObject, $toObject, $batchInput);\n\n if ($associatedObjectsData instanceof \\HubSpot\\Client\\Crm\\Associations\\Model\\BatchResponsePublicAssociationMulti) {\n foreach ($associatedObjectsData->getResults() as $association) {\n $from = $association->getFrom()->getId();\n $toAssociations = $association->getTo();\n\n if (! empty($toAssociations)) {\n $associationData[$from] = array_map(function ($item) {\n return $item->getId();\n }, $toAssociations);\n }\n }\n }\n } catch (\\Exception $e) {\n $this->log->error('[Hubspot] Failed to fetch associations', [\n 'from_object' => $fromObject,\n 'to_object' => $toObject,\n 'reason' => $e->getMessage(),\n ]);\n }\n }\n\n return $associationData;\n }\n\n /**\n * @throws \\Exception\n */\n private function getNoteAssociationType(NoteObject $noteObject): string\n {\n return match($noteObject) {\n NoteObject::Opportunity => 'note_to_deal',\n NoteObject::Lead, NoteObject::Contact => 'note_to_contact', // or 'note_to_lead' if your portal supports it\n NoteObject::Account => 'note_to_company',\n NoteObject::Call, NoteObject::Event => throw new \\Exception('Not supported'),\n };\n }\n\n /**\n * @throws \\Exception\n */\n private function getNoteObject(NoteObject $noteObject): string\n {\n return match($noteObject) {\n NoteObject::Opportunity => 'deal',\n NoteObject::Lead, NoteObject::Contact => 'contact',\n NoteObject::Account => 'company',\n NoteObject::Call, NoteObject::Event => throw new \\Exception('Not supported'),\n };\n }\n\n public function addAssociations(string $objectType, string $associationType, array $payload): Response\n {\n $endpoint = \"/crm/v4/associations/$objectType/$associationType/batch/create\";\n\n return $this->makeRequest($endpoint, 'POST', $payload);\n }\n\n public function removeAssociations(string $objectType, string $associationType, array $payload): Response\n {\n $endpoint = \"/crm/v4/associations/$objectType/$associationType/batch/archive\";\n\n return $this->makeRequest($endpoint, 'POST', $payload);\n }\n}","depth":4,"on_screen":true,"value":"<?php\n\ndeclare(strict_types=1);\n\nnamespace Jiminny\\Services\\Crm\\Hubspot;\n\nuse HubSpot\\Client\\Crm\\Deals\\ApiException as DealApiException;\nuse HubSpot\\Client\\Crm\\Contacts\\ApiException as ContactApiException;\nuse HubSpot\\Client\\Crm\\Companies\\ApiException as CompanyApiException;\nuse HubSpot\\Client\\Crm\\Contacts\\Model\\SimplePublicObjectWithAssociations as ContactsWithAssociations;\nuse HubSpot\\Client\\Crm\\Companies\\Model\\SimplePublicObjectWithAssociations as CompaniesWithAssociations;\nuse HubSpot\\Client\\Crm\\Deals\\Model\\SimplePublicObjectWithAssociations as DealWithAssociations;\nuse HubSpot\\Client\\Crm\\Objects\\Model\\SimplePublicObjectInput;\nuse HubSpot\\Client\\Crm\\Objects\\Model\\SimplePublicObjectWithAssociations as ObjectWithAssociations;\nuse HubSpot\\Client\\Crm\\Pipelines\\Model\\Error;\nuse HubSpot\\Client\\Crm\\Pipelines\\Model\\PipelineStage;\nuse HubSpot\\Client\\Crm\\Properties\\Model\\Property;\nuse HubSpot\\Discovery\\Discovery;\nuse Jiminny\\Component\\Utility\\Service\\ProviderRateLimiter;\nuse Jiminny\\Exceptions\\CrmException;\nuse Jiminny\\Exceptions\\RateLimitException;\nuse Jiminny\\Exceptions\\SocialAccountTokenInvalidException;\nuse Jiminny\\Jobs\\Crm\\NoteObject;\nuse Jiminny\\Models\\Crm\\Field;\nuse Jiminny\\Services\\Crm\\BaseClient;\nuse Jiminny\\Services\\Crm\\Hubspot\\DTO\\Response\\Owner;\nuse Jiminny\\Services\\SocialAccountService;\nuse SevenShores\\Hubspot\\Exceptions\\BadRequest;\nuse SevenShores\\Hubspot\\Exceptions\\HubspotException;\nuse SevenShores\\Hubspot\\Factory;\nuse SevenShores\\Hubspot\\Http\\Response;\nuse Jiminny\\Services\\Crm\\Hubspot\\Pagination\\HubspotPaginationService;\nuse Throwable;\n\n/**\n * @phpstan-type CrmFieldOption array{id:string, label:string, value?:string}\n */\nclass Client extends BaseClient implements HubspotClientInterface\n{\n public const string MIN_API_VERSION = '2';\n\n public const string BASE_URL = 'https://api.hubapi.com';\n\n public const int ASSOCIATIONS_BATCH_SIZE_LIMIT = 1000;\n\n private HubspotPaginationService $paginationService;\n private HubspotTokenManager $tokenManager;\n private ProviderRateLimiter $rateLimiter;\n\n public function __construct(\n SocialAccountService $socialAccountService,\n HubspotPaginationService $paginationService,\n HubspotTokenManager $tokenManager,\n ProviderRateLimiter $rateLimiter,\n ) {\n parent::__construct($socialAccountService);\n $this->paginationService = $paginationService;\n $this->tokenManager = $tokenManager;\n $this->rateLimiter = $rateLimiter;\n\n $this->setBaseUrl(self::BASE_URL);\n $this->setVersion(self::MIN_API_VERSION);\n }\n\n /**\n * Single entry point for every HubSpot API call. Enforces the per-portal\n * rate limit configured in the rate_limits table (morphed to the current\n * Configuration) and reacts to a real 429 from HubSpot by translating it\n * into a RateLimitException carrying Retry-After.\n *\n * Wrap any outbound HubSpot call (SDK or raw HTTP) like:\n *\n * $this->executeRequest(fn () => $this->getNewInstance()->crm()->...);\n *\n * @template T\n * @param callable(): T $apiCall\n * @return T\n *\n * @throws RateLimitException\n */\n private function executeRequest(callable $apiCall)\n {\n if (! $this->rateLimiter->canMakeRequest($this->config)) {\n $retryAfter = $this->rateLimiter->requestAvailableIn($this->config);\n\n $this->log->warning('[Hubspot] Rate limit exceeded, deferring request', [\n 'team_id' => $this->config->team_id,\n 'config_id' => $this->config->getId(),\n 'retry_after' => $retryAfter,\n ]);\n\n throw new RateLimitException(\n 'Hubspot rate limit reached for configuration ' . $this->config->getId(),\n $retryAfter,\n );\n }\n\n $this->rateLimiter->incrementRequestCount($this->config);\n\n try {\n return $apiCall();\n } catch (Throwable $e) {\n if ($this->isHubspotRateLimit($e)) {\n $retryAfter = $this->parseRetryAfter($e);\n\n $this->log->warning('[Hubspot] Received 429 from API', [\n 'team_id' => $this->config->team_id,\n 'config_id' => $this->config->getId(),\n 'retry_after' => $retryAfter,\n 'reason' => $e->getMessage(),\n ]);\n\n throw new RateLimitException('Hubspot returned 429', $retryAfter, $e);\n }\n\n throw $e;\n }\n }\n\n private function isHubspotRateLimit(Throwable $e): bool\n {\n return method_exists($e, 'getCode') && (int) $e->getCode() === 429;\n }\n\n private function parseRetryAfter(Throwable $e): int\n {\n if (method_exists($e, 'getResponseHeaders')) {\n $headers = $e->getResponseHeaders() ?: [];\n $value = $headers['Retry-After'] ?? $headers['retry-after'] ?? null;\n if (is_array($value)) {\n $value = $value[0] ?? null;\n }\n if (is_numeric($value)) {\n return (int) $value;\n }\n }\n\n return 10;\n }\n\n public function getMinimumApiVersion(): string\n {\n return self::MIN_API_VERSION;\n }\n\n public function getInstance(): Factory\n {\n return new Factory([\n 'key' => $this->accessToken,\n 'oauth2' => true,\n 'base_url' => $this->baseUrl,\n ]);\n }\n\n public function getNewInstance(): Discovery\n {\n return \\HubSpot\\Factory::createWithAccessToken($this->accessToken);\n }\n\n /**\n * Secondly and daily limits for Hubspot API\n *\n * Product Tier: Free & Starter | Professional & Enterprise | API add-on (any tier)\n * Burst: 100/10 seconds | 150/10 seconds | 200/10 seconds\n * Daily: 250,000 | 500,000 | 1,000,000\n *\n * Official documentation states: The search endpoints are rate limited to five requests per second.\n * Since with 5 RPS were still hitting secondly rate limits we lowered it to 4\n */\n public function getPaginatedData(array $payload, string $type, int $offset = 0): array\n {\n $total = 0;\n $lastId = null;\n $rows = [];\n foreach ($this->getPaginatedDataGenerator($payload, $type, $offset, $total, $lastId) as $row) {\n $rows[] = $row;\n }\n\n return ['results' => $rows, 'total' => $total, 'last_record' => $lastId];\n }\n\n /**\n * @throws HubspotException\n * @throws SocialAccountTokenInvalidException\n * @throws BadRequest\n */\n public function getPaginatedDataGenerator(\n array $payload,\n string $type,\n int $offset = 0,\n int &$total = 0,\n ?string &$lastRecordId = null\n ): \\Generator {\n return $this->paginationService->getPaginatedDataGenerator(\n $this,\n $payload,\n $type,\n $offset,\n $total,\n $lastRecordId\n );\n }\n\n /**\n * @throws DealApiException\n * @throws CrmException\n */\n public function getOpportunityById(string $crmId, array $fields): array\n {\n try {\n $deal = $this->executeRequest(fn () => $this->getNewInstance()->crm()->deals()->basicApi()->getById(\n $crmId,\n implode(',', $fields),\n 'companies,contacts'\n ));\n } catch (DealApiException $e) {\n $this->log->info('[Hubspot] Failed to fetch opportunity', [\n 'crm_id' => $crmId,\n 'reason' => $e->getMessage(),\n ]);\n\n throw $e;\n }\n\n if (! $deal instanceof DealWithAssociations) {\n throw new CrmException('Deal not found');\n }\n\n return [\n 'id' => $deal->getId(),\n 'properties' => $deal->getProperties(),\n 'associations' => $deal->getAssociations(),\n ];\n }\n\n /**\n * Generic batch read method for HubSpot objects\n *\n * @param string $objectType The object type ('deals', 'companies', 'contacts')\n * @param array<string> $crmIds Array of HubSpot object IDs (max 100)\n * @param array<string> $fields Array of property names to fetch\n *\n * @return array<string, array> Array keyed by CRM ID with object data\n */\n private function batchReadObjects(string $objectType, array $crmIds, array $fields): array\n {\n if (empty($crmIds)) {\n return [];\n }\n\n $this->validateBatchSize($objectType, $crmIds);\n $this->ensureValidToken();\n\n try {\n $batchConfig = $this->createBatchConfiguration($objectType);\n $batchReadRequest = $this->prepareBatchRequest($batchConfig, $crmIds, $fields);\n $response = $batchConfig['api']->read($batchReadRequest);\n\n $this->validateApiResponse($response, $objectType);\n\n $results = $this->processApiResults($response);\n $this->logBatchResults($objectType, $crmIds, $results);\n\n return $results;\n } catch (\\Throwable $e) {\n $this->handleBatchError($e, $objectType, $crmIds);\n }\n }\n\n private function validateBatchSize(string $objectType, array $crmIds): void\n {\n if (count($crmIds) > 100) {\n throw new \\InvalidArgumentException(\"Batch size cannot exceed 100 {$objectType}\");\n }\n }\n\n private function createBatchConfiguration(string $objectType): array\n {\n $configurations = [\n 'deals' => [\n 'batchReadRequest' => new \\HubSpot\\Client\\Crm\\Deals\\Model\\BatchReadInputSimplePublicObjectId(),\n 'inputClass' => \\HubSpot\\Client\\Crm\\Deals\\Model\\SimplePublicObjectId::class,\n 'api' => $this->getNewInstance()->crm()->deals()->batchApi(),\n ],\n 'companies' => [\n 'batchReadRequest' => new \\HubSpot\\Client\\Crm\\Companies\\Model\\BatchReadInputSimplePublicObjectId(),\n 'inputClass' => \\HubSpot\\Client\\Crm\\Companies\\Model\\SimplePublicObjectId::class,\n 'api' => $this->getNewInstance()->crm()->companies()->batchApi(),\n ],\n 'contacts' => [\n 'batchReadRequest' => new \\HubSpot\\Client\\Crm\\Contacts\\Model\\BatchReadInputSimplePublicObjectId(),\n 'inputClass' => \\HubSpot\\Client\\Crm\\Contacts\\Model\\SimplePublicObjectId::class,\n 'api' => $this->getNewInstance()->crm()->contacts()->batchApi(),\n ],\n ];\n\n if (! isset($configurations[$objectType])) {\n throw new \\InvalidArgumentException(\"Unsupported object type: {$objectType}\");\n }\n\n return $configurations[$objectType];\n }\n\n private function prepareBatchRequest(array $batchConfig, array $crmIds, array $fields): object\n {\n $batchReadRequest = $batchConfig['batchReadRequest'];\n $inputClass = $batchConfig['inputClass'];\n\n $inputs = array_map(function ($crmId) use ($inputClass) {\n $input = new $inputClass();\n $input->setId($crmId);\n\n return $input;\n }, $crmIds);\n\n $batchReadRequest->setInputs($inputs);\n $batchReadRequest->setProperties($fields);\n\n return $batchReadRequest;\n }\n\n private function validateApiResponse($response, string $objectType): void\n {\n if (! $response) {\n throw new CrmException(\"HubSpot API returned null response for {$objectType} batch read\");\n }\n }\n\n private function processApiResults($response): array\n {\n $results = [];\n $responseResults = $response->getResults();\n\n if ($responseResults) {\n foreach ($responseResults as $object) {\n if ($object && $object->getId()) {\n $results[$object->getId()] = [\n 'id' => $object->getId(),\n 'properties' => $object->getProperties() ?: [],\n ];\n }\n }\n }\n\n return $results;\n }\n\n private function logBatchResults(string $objectType, array $crmIds, array $results): void\n {\n $this->log->info(\"[HubSpot] Batch fetched {$objectType}\", [\n 'requested_count' => count($crmIds),\n 'returned_count' => count($results),\n 'crm_ids' => $crmIds,\n ]);\n }\n\n private function handleBatchError(\\Throwable $e, string $objectType, array $crmIds): void\n {\n $errorMessage = $e->getMessage() ?: 'Unknown error';\n $errorTrace = $e->getTraceAsString() ?: 'No trace available';\n\n $this->log->error(\"[HubSpot] Failed to batch fetch {$objectType}\", [\n 'crm_ids' => $crmIds,\n 'error' => $errorMessage,\n 'trace' => $errorTrace,\n ]);\n\n throw new CrmException(\"Failed to batch fetch {$objectType}: \" . $errorMessage);\n }\n\n /**\n * Batch read multiple opportunities by their CRM IDs\n *\n * @param array<string> $crmIds Array of HubSpot deal IDs (max 100)\n * @param array<string> $fields Array of property names to fetch\n *\n * @return array<string, array> Array keyed by CRM ID with opportunity data\n */\n public function getOpportunitiesByIds(array $crmIds, array $fields): array\n {\n return $this->batchReadObjects('deals', $crmIds, $fields);\n }\n\n /**\n * Batch read multiple companies by their CRM IDs\n *\n * @param array<string> $crmIds Array of HubSpot company IDs (max 100)\n * @param array<string> $fields Array of property names to fetch\n *\n * @return array<string, array> Array keyed by CRM ID with company data\n */\n public function getCompaniesByIds(array $crmIds, array $fields): array\n {\n return $this->batchReadObjects('companies', $crmIds, $fields);\n }\n\n /**\n * Batch read multiple contacts by their CRM IDs\n *\n * @param array<string> $crmIds Array of HubSpot contact IDs (max 100)\n * @param array<string> $fields Array of property names to fetch\n *\n * @return array<string, array> Array keyed by CRM ID with contact data\n */\n public function getContactsByIds(array $crmIds, array $fields): array\n {\n return $this->batchReadObjects('contacts', $crmIds, $fields);\n }\n\n /**\n * @throws CompanyApiException\n * @throws CrmException\n */\n public function getAccountById(string $crmId, array $fields): array\n {\n try {\n $company = $this->getNewInstance()->crm()->companies()->basicApi()->getById(\n $crmId,\n implode(',', $fields),\n );\n } catch (CompanyApiException $e) {\n $this->log->info('[Hubspot] Failed to fetch account', [\n 'crm_id' => $crmId,\n 'reason' => $e->getMessage(),\n ]);\n\n throw $e;\n }\n\n if (! $company instanceof CompaniesWithAssociations) {\n throw new CrmException('Account not found');\n }\n\n return [\n 'id' => $company->getId(),\n 'properties' => $company->getProperties(),\n ];\n }\n\n /**\n * @throws ContactApiException\n * @throws CrmException\n */\n public function getContactById(string $crmId, array $fields): array\n {\n try {\n $contact = $this->getNewInstance()->crm()->contacts()->basicApi()->getById(\n $crmId,\n implode(',', $fields)\n );\n } catch (ContactApiException $e) {\n $this->log->info('[Hubspot] Failed to fetch contact', [\n 'crm_id' => $crmId,\n 'reason' => $e->getMessage(),\n ]);\n\n throw $e;\n }\n\n if (! $contact instanceof ContactsWithAssociations) {\n throw new CrmException('Contact not found');\n }\n\n return [\n 'id' => $contact->getId(),\n 'properties' => $contact->getProperties(),\n ];\n }\n\n /**\n * This is email search request that Hubspot offers as GET (more generous quota)\n */\n public function getContactByEmail(string $email, array $fields = []): array\n {\n try {\n $contact = $this->getNewInstance()->crm()->contacts()->basicApi()->getById(\n $email,\n implode(',', $fields),\n null,\n false,\n 'email'\n );\n\n return [\n 'id' => $contact->getId(),\n 'properties' => $contact->getProperties(),\n ];\n } catch (ContactApiException $e) {\n $this->log->info('[Hubspot] Failed to fetch contact', [\n 'email' => $email,\n 'reason' => $e->getMessage(),\n ]);\n\n return [];\n }\n }\n\n /**\n * @throws CrmException\n */\n public function fetchProperty(string $objectType, string $propertyId): Property\n {\n $result = $this->getNewInstance()->crm()->properties()->coreApi()->getByName($objectType, $propertyId);\n\n if (! $result instanceof Property) {\n $this->log->error('[Hubspot] Failed to fetch property', [\n 'object_type' => $objectType,\n 'property_id' => $propertyId,\n 'reason' => $result->getMessage(),\n ]);\n\n throw new CrmException('Failed to fetch property');\n }\n\n return $result;\n }\n\n /**\n * @return array<CrmFieldOption>\n */\n public function fetchPropertyOptions(string $objectType, string $propertyId): array\n {\n /** @var array<CrmFieldOption> */\n return $this->fetchProperty($objectType, $propertyId)->getOptions();\n }\n\n /**\n * @return array<array{id:string, label:string, deleted:bool}>\n */\n public function fetchCallDispositions(): array\n {\n /** @var Response $response */\n $response = $this->getInstance()->engagements()->getCallDispositions();\n\n /**\n * @var array<array{\n * id:string,\n * label:string,\n * deleted: bool\n * }>\n */\n return $response->toArray();\n }\n\n /**\n * @return array<CrmFieldOption>\n */\n public function fetchOpportunityPipelineStages(): array\n {\n $stages = [];\n $apiResponse = $this->getNewInstance()->crm()->pipelines()->pipelinesApi()->getAll('deals');\n\n if ($apiResponse instanceof Error) {\n $this->log->error('[Hubspot] Failed to fetch opportunity pipelines', [\n 'reason' => $apiResponse->getMessage(),\n ]);\n\n return [];\n }\n\n foreach ($apiResponse->getResults() as $pipeline) {\n $pipelineStages = array_map(\n static function (PipelineStage $stage) {\n return [\n 'id' => $stage->getId(),\n 'label' => $stage->getLabel(),\n ];\n },\n $pipeline->getStages()\n );\n\n $stages = array_merge($stages, $pipelineStages);\n }\n\n return $stages;\n }\n\n public function fetchOpportunityPipelines(): array\n {\n $pipelines = [];\n\n try {\n $apiResponse = $this->makeRequest('/crm/v3/pipelines/deals');\n } catch (\\Exception $e) {\n $this->log->info('[Hubspot] Failed to fetch opportunity pipelines', [\n 'reason' => $e->getMessage(),\n ]);\n\n return [];\n }\n\n $response = $apiResponse->toArray();\n\n foreach ($response['results'] as $pipeline) {\n $pipelines[] = [\n 'id' => $pipeline['id'],\n 'label' => $pipeline['label'],\n ];\n }\n\n return $pipelines;\n }\n\n /**\n * @return array<CrmFieldOption>\n */\n public function fetchMeetingOutcomeFieldOptions(Field $field): array\n {\n return $field->getCrmProviderId() === 'meetingOutcome'\n ? $this->fetchMeetingOutcomeTypes()\n : $this->fetchCallActivityTypes();\n }\n\n public function fetchMeetingOutcomeTypes(): array\n {\n return $this->extractMeetingTypeOptions(\n 'https://api.hubapi.com/crm/v3/properties/meeting/hs_meeting_outcome'\n );\n }\n\n public function fetchCallActivityTypes(): array\n {\n return $this->extractMeetingTypeOptions(\n 'https://api.hubapi.com/crm/v3/properties/call/hs_activity_type'\n );\n }\n\n private function extractMeetingTypeOptions(string $endpoint): array\n {\n /** @var Response $response */\n $response = $this->getInstance()\n ->getClient()\n ->request('GET', $endpoint);\n\n /**\n * @var array<array{\n * value: string,\n * label: string,\n * displayOrder: int\n * }> $optionData\n */\n $optionData = $response->toArray()['options'] ?? [];\n\n $options = [];\n foreach ($optionData as $item) {\n $options[] = [\n 'id' => $item['value'],\n 'value' => $item['value'],\n 'label' => $item['label'],\n 'display_order' => $item['displayOrder'],\n ];\n }\n\n return $options;\n }\n\n /**\n * @return array<CrmFieldOption>\n */\n public function fetchDispositionFieldOptions(): array\n {\n $options = [];\n\n $dispositions = $this->fetchCallDispositions();\n\n foreach ($dispositions as $disposition) {\n if ($disposition['deleted'] !== false) {\n continue;\n }\n\n $option['value'] = $disposition['id'];\n $option['id'] = $disposition['id'];\n $option['label'] = $disposition['label'];\n\n $options[] = $option;\n }\n\n return $options;\n }\n\n /**\n * @return array<CrmFieldOption>\n */\n public function fetchOpportunityFieldOptions(Field $field): array\n {\n if ($field->isStageField()) {\n return $this->fetchOpportunityPipelineStages();\n }\n\n if ($field->isPipelineField()) {\n return $this->fetchOpportunityPipelines();\n }\n\n return $this->fetchPropertyOptions('deals', $field->getCrmProviderId());\n }\n\n /**\n * @throws BadRequest\n * @throws HubspotException\n */\n public function makeRequest(string $endpoint, $method = 'GET', $payload = [], ?string $queryString = null)\n {\n $endpoint = self::BASE_URL . $endpoint;\n\n if ($method === 'GET') {\n $response = $this->getInstance()->getClient()?->request(\n method: $method,\n endpoint: $endpoint,\n query_string: $queryString\n );\n } else {\n $response = $this->getInstance()->getClient()->request($method, $endpoint, [\n 'json' => ($payload),\n ]);\n }\n\n $max = $response->getHeaderLine('X-HubSpot-RateLimit-Max'); // \"110\"\n $remaining = $response->getHeaderLine('X-HubSpot-RateLimit-Remaining'); // \"109\"\n $interval = $response->getHeaderLine('X-HubSpot-RateLimit-Interval-Milliseconds'); // \"10000\"\n $body = json_decode((string) $response->getBody(), true);\n\n \\Illuminate\\Support\\Facades\\Log::channel('custom_channel')->info('$max ' . PHP_EOL . print_r($max, true));\n \\Illuminate\\Support\\Facades\\Log::channel('custom_channel')->info('$remaining ' . PHP_EOL . print_r($remaining, true));\n \\Illuminate\\Support\\Facades\\Log::channel('custom_channel')->info('$interval ' . PHP_EOL . print_r($interval, true));\n \\Illuminate\\Support\\Facades\\Log::channel('custom_channel')->info('$body ' . PHP_EOL . print_r($body, true));\n\n return $response;\n }\n\n /**\n * @throws BadRequest\n * @throws HubspotException\n */\n public function createMeeting(array $payload): Response\n {\n $endpoint = '/crm/v3/objects/meetings';\n\n return $this->makeRequest($endpoint, 'POST', $payload);\n }\n\n /**\n * @throws BadRequest\n * @throws HubspotException\n */\n public function updateMeeting(string $meetingId, array $payload): Response\n {\n $endpoint = '/crm/v3/objects/meetings/' . $meetingId;\n\n return $this->makeRequest($endpoint, 'PATCH', $payload);\n }\n\n /**\n * @throws \\Exception\n */\n public function createNote(\n string $body,\n string $ownerId,\n int $timestamp,\n string $objectId,\n NoteObject $noteObject\n ): ?string {\n try {\n $noteInput = new SimplePublicObjectInput([\n 'properties' => [\n 'hs_note_body' => $body,\n 'hubspot_owner_id' => $ownerId,\n 'hs_timestamp' => $timestamp,\n ],\n ]);\n\n // Create note\n $note = $this->getNewInstance()->crm()->objects()->basicApi()->create('note', $noteInput);\n\n $this->getNewInstance()->crm()->objects()->associationsApi()->create(\n 'note',\n $note->getId(),\n $this->getNoteObject($noteObject),\n $objectId,\n $this->getNoteAssociationType($noteObject),\n );\n\n return $note->getId();\n } catch (\\Exception $e) {\n $this->log->error('[Hubspot] Failed to create note', [\n 'objectId' => $objectId,\n 'noteObject' => $noteObject->getObjectType(),\n 'reason' => $e->getMessage(),\n ]);\n\n \\Sentry::captureException($e);\n }\n\n return null;\n }\n\n public function updateEngagement(string $objectId, array $engagement, array $metadata): void\n {\n $this->getInstance()->engagements()->update($objectId, $engagement, $metadata);\n }\n\n public function getEngagementData(string $engagementId): array\n {\n $engagement = $this->getInstance()->engagements()->get($engagementId);\n\n return $engagement->toArray();\n }\n\n public function createEngagement(array $engagement, array $associations, array $metadata): Response\n {\n return $this->getInstance()\n ->engagements()\n ->create($engagement, $associations, $metadata);\n }\n\n public function isUnauthorizedException(\\Exception $e): bool\n {\n // Check for specific HubSpot API exception types first\n if ($e instanceof BadRequest) {\n // BadRequest can contain 401 status codes\n return $e->getCode() === 401;\n }\n\n // Check for HTTP client exceptions with status codes\n if ($e instanceof \\GuzzleHttp\\Exception\\RequestException && $e->hasResponse()) {\n $response = $e->getResponse();\n if ($response !== null) {\n return $response->getStatusCode() === 401;\n }\n }\n\n // Check for Guzzle HTTP exceptions\n if ($e instanceof \\GuzzleHttp\\Exception\\ClientException) {\n return $e->getCode() === 401;\n }\n\n // Fallback to string matching as last resort, but be more specific\n $message = strtolower($e->getMessage());\n\n return str_contains($message, '401 unauthorized') ||\n str_contains($message, 'http 401') ||\n str_contains($message, 'status code 401') ||\n (preg_match('/\\b401\\b/', $message) && str_contains($message, 'unauthorized'));\n }\n\n /**\n * Validates and refreshes the access token if needed before API requests.\n * This ensures long-running processes don't fail due to token expiration.\n *\n * @throws SocialAccountTokenInvalidException\n */\n public function ensureValidToken(): void\n {\n if ($this->oauthAccount === null) {\n return;\n }\n\n $newToken = $this->tokenManager->ensureValidToken($this->oauthAccount);\n if ($newToken !== null) {\n $this->accessToken = $newToken;\n }\n }\n\n public function getConfig()\n {\n return $this->config;\n }\n\n // returns only active (archived=false)\n public function getOwners(): array\n {\n return $this->getNewInstance()->crm()->owners()->getAll();\n }\n\n /**\n * @param bool $archived\n *\n * @return array<Owner>|[]\n */\n public function getOwnersArchived(bool $archived = true): array\n {\n $endpoint = '/crm/v3/owners';\n $queryParams = [\n 'archived' => $archived ? 'true' : 'false',\n ];\n $queryString = http_build_query($queryParams);\n\n $owners = [];\n\n try {\n $response = $this->makeRequest(endpoint: $endpoint, queryString: $queryString);\n $responseData = $response?->toArray();\n\n foreach ($responseData['results'] as $result) {\n try {\n $owners[] = Owner::create($result);\n } catch (Throwable $e) {\n $this->log->error('[HubSpot] Failed to process owner data', [\n 'result' => $result,\n 'error' => $e->getMessage(),\n ]);\n\n continue;\n }\n }\n } catch (Throwable $e) {\n $this->log->error('HubSpot] Failed to fetch owners', [\n 'archived' => $archived,\n 'error' => $e->getMessage(),\n ]);\n\n return [];\n }\n\n return $owners;\n }\n\n public function getMeeting(string $engagementId): ObjectWithAssociations\n {\n return $this->getNewInstance()->crm()->objects()->basicApi()\n ->getById('meeting', $engagementId, null, 'contact,company,deal');\n }\n\n public function deleteEngagement(string $engagementId): void\n {\n $this->getInstance()->engagements()->delete((int) $engagementId);\n }\n\n public function getAssociationsData(array $ids, string $fromObject, string $toObject): array\n {\n $associationData = [];\n $idChunks = array_chunk($ids, self::ASSOCIATIONS_BATCH_SIZE_LIMIT);\n\n foreach ($idChunks as $idChunk) {\n try {\n $batchInput = new \\HubSpot\\Client\\Crm\\Associations\\Model\\BatchInputPublicObjectId();\n $batchInput->setInputs(array_map(function ($id) {\n $publicObjectId = new \\HubSpot\\Client\\Crm\\Associations\\Model\\PublicObjectId();\n $publicObjectId->setId($id);\n\n return $publicObjectId;\n }, $idChunk));\n\n $associatedObjectsData = $this\n ->getNewInstance()\n ->crm()\n ->associations()\n ->batchApi()\n ->read($fromObject, $toObject, $batchInput);\n\n if ($associatedObjectsData instanceof \\HubSpot\\Client\\Crm\\Associations\\Model\\BatchResponsePublicAssociationMulti) {\n foreach ($associatedObjectsData->getResults() as $association) {\n $from = $association->getFrom()->getId();\n $toAssociations = $association->getTo();\n\n if (! empty($toAssociations)) {\n $associationData[$from] = array_map(function ($item) {\n return $item->getId();\n }, $toAssociations);\n }\n }\n }\n } catch (\\Exception $e) {\n $this->log->error('[Hubspot] Failed to fetch associations', [\n 'from_object' => $fromObject,\n 'to_object' => $toObject,\n 'reason' => $e->getMessage(),\n ]);\n }\n }\n\n return $associationData;\n }\n\n /**\n * @throws \\Exception\n */\n private function getNoteAssociationType(NoteObject $noteObject): string\n {\n return match($noteObject) {\n NoteObject::Opportunity => 'note_to_deal',\n NoteObject::Lead, NoteObject::Contact => 'note_to_contact', // or 'note_to_lead' if your portal supports it\n NoteObject::Account => 'note_to_company',\n NoteObject::Call, NoteObject::Event => throw new \\Exception('Not supported'),\n };\n }\n\n /**\n * @throws \\Exception\n */\n private function getNoteObject(NoteObject $noteObject): string\n {\n return match($noteObject) {\n NoteObject::Opportunity => 'deal',\n NoteObject::Lead, NoteObject::Contact => 'contact',\n NoteObject::Account => 'company',\n NoteObject::Call, NoteObject::Event => throw new \\Exception('Not supported'),\n };\n }\n\n public function addAssociations(string $objectType, string $associationType, array $payload): Response\n {\n $endpoint = \"/crm/v4/associations/$objectType/$associationType/batch/create\";\n\n return $this->makeRequest($endpoint, 'POST', $payload);\n }\n\n public function removeAssociations(string $objectType, string $associationType, array $payload): Response\n {\n $endpoint = \"/crm/v4/associations/$objectType/$associationType/batch/archive\";\n\n return $this->makeRequest($endpoint, 'POST', $payload);\n }\n}","role_description":"text entry area","is_enabled":true,"is_focused":true,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"Project","depth":3,"on_screen":false,"role_description":"text"},{"role":"AXButton","text":"Project","depth":3,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"New File or Directory…","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.018055556,"height":0.026666667},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Expand Selected","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.018055556,"height":0.026666667},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Collapse All","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.018055556,"height":0.026666667},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Options","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.018055556,"height":0.026666667},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Hide","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.018055556,"height":0.026666667},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false}]...
|
-188086479456983350
|
6378757184196315236
|
click
|
accessibility
|
NULL
|
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
Editor for custom.log
Sync Changes
Hide This Notification
Code changed:
Hide
2
60
Previous Highlighted Error
Next Highlighted Error
<?php
declare(strict_types=1);
namespace Jiminny\Services\Crm\Hubspot;
use HubSpot\Client\Crm\Deals\ApiException as DealApiException;
use HubSpot\Client\Crm\Contacts\ApiException as ContactApiException;
use HubSpot\Client\Crm\Companies\ApiException as CompanyApiException;
use HubSpot\Client\Crm\Contacts\Model\SimplePublicObjectWithAssociations as ContactsWithAssociations;
use HubSpot\Client\Crm\Companies\Model\SimplePublicObjectWithAssociations as CompaniesWithAssociations;
use HubSpot\Client\Crm\Deals\Model\SimplePublicObjectWithAssociations as DealWithAssociations;
use HubSpot\Client\Crm\Objects\Model\SimplePublicObjectInput;
use HubSpot\Client\Crm\Objects\Model\SimplePublicObjectWithAssociations as ObjectWithAssociations;
use HubSpot\Client\Crm\Pipelines\Model\Error;
use HubSpot\Client\Crm\Pipelines\Model\PipelineStage;
use HubSpot\Client\Crm\Properties\Model\Property;
use HubSpot\Discovery\Discovery;
use Jiminny\Component\Utility\Service\ProviderRateLimiter;
use Jiminny\Exceptions\CrmException;
use Jiminny\Exceptions\RateLimitException;
use Jiminny\Exceptions\SocialAccountTokenInvalidException;
use Jiminny\Jobs\Crm\NoteObject;
use Jiminny\Models\Crm\Field;
use Jiminny\Services\Crm\BaseClient;
use Jiminny\Services\Crm\Hubspot\DTO\Response\Owner;
use Jiminny\Services\SocialAccountService;
use SevenShores\Hubspot\Exceptions\BadRequest;
use SevenShores\Hubspot\Exceptions\HubspotException;
use SevenShores\Hubspot\Factory;
use SevenShores\Hubspot\Http\Response;
use Jiminny\Services\Crm\Hubspot\Pagination\HubspotPaginationService;
use Throwable;
/**
* @phpstan-type CrmFieldOption array{id:string, label:string, value?:string}
*/
class Client extends BaseClient implements HubspotClientInterface
{
public const string MIN_API_VERSION = '2';
public const string BASE_URL = '[URL_WITH_CREDENTIALS] T
* @param callable(): T $apiCall
* @return T
*
* @throws RateLimitException
*/
private function executeRequest(callable $apiCall)
{
if (! $this->rateLimiter->canMakeRequest($this->config)) {
$retryAfter = $this->rateLimiter->requestAvailableIn($this->config);
$this->log->warning('[Hubspot] Rate limit exceeded, deferring request', [
'team_id' => $this->config->team_id,
'config_id' => $this->config->getId(),
'retry_after' => $retryAfter,
]);
throw new RateLimitException(
'Hubspot rate limit reached for configuration ' . $this->config->getId(),
$retryAfter,
);
}
$this->rateLimiter->incrementRequestCount($this->config);
try {
return $apiCall();
} catch (Throwable $e) {
if ($this->isHubspotRateLimit($e)) {
$retryAfter = $this->parseRetryAfter($e);
$this->log->warning('[Hubspot] Received 429 from API', [
'team_id' => $this->config->team_id,
'config_id' => $this->config->getId(),
'retry_after' => $retryAfter,
'reason' => $e->getMessage(),
]);
throw new RateLimitException('Hubspot returned 429', $retryAfter, $e);
}
throw $e;
}
}
private function isHubspotRateLimit(Throwable $e): bool
{
return method_exists($e, 'getCode') && (int) $e->getCode() === 429;
}
private function parseRetryAfter(Throwable $e): int
{
if (method_exists($e, 'getResponseHeaders')) {
$headers = $e->getResponseHeaders() ?: [];
$value = $headers['Retry-After'] ?? $headers['retry-after'] ?? null;
if (is_array($value)) {
$value = $value[0] ?? null;
}
if (is_numeric($value)) {
return (int) $value;
}
}
return 10;
}
public function getMinimumApiVersion(): string
{
return self::MIN_API_VERSION;
}
public function getInstance(): Factory
{
return new Factory([
'key' => $this->accessToken,
'oauth2' => true,
'base_url' => $this->baseUrl,
]);
}
public function getNewInstance(): Discovery
{
return \HubSpot\Factory::createWithAccessToken($this->accessToken);
}
/**
* Secondly and daily limits for Hubspot API
*
* Product Tier: Free & Starter | Professional & Enterprise | API add-on (any tier)
* Burst: 100/10 seconds | 150/10 seconds | 200/10 seconds
* Daily: 250,000 | 500,000 | 1,000,000
*
* Official documentation states: The search endpoints are rate limited to five requests per second.
* Since with 5 RPS were still hitting secondly rate limits we lowered it to 4
*/
public function getPaginatedData(array $payload, string $type, int $offset = 0): array
{
$total = 0;
$lastId = null;
$rows = [];
foreach ($this->getPaginatedDataGenerator($payload, $type, $offset, $total, $lastId) as $row) {
$rows[] = $row;
}
return ['results' => $rows, 'total' => $total, 'last_record' => $lastId];
}
/**
* @throws HubspotException
* @throws SocialAccountTokenInvalidException
* @throws BadRequest
*/
public function getPaginatedDataGenerator(
array $payload,
string $type,
int $offset = 0,
int &$total = 0,
?string &$lastRecordId = null
): \Generator {
return $this->paginationService->getPaginatedDataGenerator(
$this,
$payload,
$type,
$offset,
$total,
$lastRecordId
);
}
/**
* @throws DealApiException
* @throws CrmException
*/
public function getOpportunityById(string $crmId, array $fields): array
{
try {
$deal = $this->executeRequest(fn () => $this->getNewInstance()->crm()->deals()->basicApi()->getById(
$crmId,
implode(',', $fields),
'companies,contacts'
));
} catch (DealApiException $e) {
$this->log->info('[Hubspot] Failed to fetch opportunity', [
'crm_id' => $crmId,
'reason' => $e->getMessage(),
]);
throw $e;
}
if (! $deal instanceof DealWithAssociations) {
throw new CrmException('Deal not found');
}
return [
'id' => $deal->getId(),
'properties' => $deal->getProperties(),
'associations' => $deal->getAssociations(),
];
}
/**
* Generic batch read method for HubSpot objects
*
* @param string $objectType The object type ('deals', 'companies', 'contacts')
* @param array<string> $crmIds Array of HubSpot object IDs (max 100)
* @param array<string> $fields Array of property names to fetch
*
* @return array<string, array> Array keyed by CRM ID with object data
*/
private function batchReadObjects(string $objectType, array $crmIds, array $fields): array
{
if (empty($crmIds)) {
return [];
}
$this->validateBatchSize($objectType, $crmIds);
$this->ensureValidToken();
try {
$batchConfig = $this->createBatchConfiguration($objectType);
$batchReadRequest = $this->prepareBatchRequest($batchConfig, $crmIds, $fields);
$response = $batchConfig['api']->read($batchReadRequest);
$this->validateApiResponse($response, $objectType);
$results = $this->processApiResults($response);
$this->logBatchResults($objectType, $crmIds, $results);
return $results;
} catch (\Throwable $e) {
$this->handleBatchError($e, $objectType, $crmIds);
}
}
private function validateBatchSize(string $objectType, array $crmIds): void
{
if (count($crmIds) > 100) {
throw new \InvalidArgumentException("Batch size cannot exceed 100 {$objectType}");
}
}
private function createBatchConfiguration(string $objectType): array
{
$configurations = [
'deals' => [
'batchReadRequest' => new \HubSpot\Client\Crm\Deals\Model\BatchReadInputSimplePublicObjectId(),
'inputClass' => \HubSpot\Client\Crm\Deals\Model\SimplePublicObjectId::class,
'api' => $this->getNewInstance()->crm()->deals()->batchApi(),
],
'companies' => [
'batchReadRequest' => new \HubSpot\Client\Crm\Companies\Model\BatchReadInputSimplePublicObjectId(),
'inputClass' => \HubSpot\Client\Crm\Companies\Model\SimplePublicObjectId::class,
'api' => $this->getNewInstance()->crm()->companies()->batchApi(),
],
'contacts' => [
'batchReadRequest' => new \HubSpot\Client\Crm\Contacts\Model\BatchReadInputSimplePublicObjectId(),
'inputClass' => \HubSpot\Client\Crm\Contacts\Model\SimplePublicObjectId::class,
'api' => $this->getNewInstance()->crm()->contacts()->batchApi(),
],
];
if (! isset($configurations[$objectType])) {
throw new \InvalidArgumentException("Unsupported object type: {$objectType}");
}
return $configurations[$objectType];
}
private function prepareBatchRequest(array $batchConfig, array $crmIds, array $fields): object
{
$batchReadRequest = $batchConfig['batchReadRequest'];
$inputClass = $batchConfig['inputClass'];
$inputs = array_map(function ($crmId) use ($inputClass) {
$input = new $inputClass();
$input->setId($crmId);
return $input;
}, $crmIds);
$batchReadRequest->setInputs($inputs);
$batchReadRequest->setProperties($fields);
return $batchReadRequest;
}
private function validateApiResponse($response, string $objectType): void
{
if (! $response) {
throw new CrmException("HubSpot API returned null response for {$objectType} batch read");
}
}
private function processApiResults($response): array
{
$results = [];
$responseResults = $response->getResults();
if ($responseResults) {
foreach ($responseResults as $object) {
if ($object && $object->getId()) {
$results[$object->getId()] = [
'id' => $object->getId(),
'properties' => $object->getProperties() ?: [],
];
}
}
}
return $results;
}
private function logBatchResults(string $objectType, array $crmIds, array $results): void
{
$this->log->info("[HubSpot] Batch fetched {$objectType}", [
'requested_count' => count($crmIds),
'returned_count' => count($results),
'crm_ids' => $crmIds,
]);
}
private function handleBatchError(\Throwable $e, string $objectType, array $crmIds): void
{
$errorMessage = $e->getMessage() ?: 'Unknown error';
$errorTrace = $e->getTraceAsString() ?: 'No trace available';
$this->log->error("[HubSpot] Failed to batch fetch {$objectType}", [
'crm_ids' => $crmIds,
'error' => $errorMessage,
'trace' => $errorTrace,
]);
throw new CrmException("Failed to batch fetch {$objectType}: " . $errorMessage);
}
/**
* Batch read multiple opportunities by their CRM IDs
*
* @param array<string> $crmIds Array of HubSpot deal IDs (max 100)
* @param array<string> $fields Array of property names to fetch
*
* @return array<string, array> Array keyed by CRM ID with opportunity data
*/
public function getOpportunitiesByIds(array $crmIds, array $fields): array
{
return $this->batchReadObjects('deals', $crmIds, $fields);
}
/**
* Batch read multiple companies by their CRM IDs
*
* @param array<string> $crmIds Array of HubSpot company IDs (max 100)
* @param array<string> $fields Array of property names to fetch
*
* @return array<string, array> Array keyed by CRM ID with company data
*/
public function getCompaniesByIds(array $crmIds, array $fields): array
{
return $this->batchReadObjects('companies', $crmIds, $fields);
}
/**
* Batch read multiple contacts by their CRM IDs
*
* @param array<string> $crmIds Array of HubSpot contact IDs (max 100)
* @param array<string> $fields Array of property names to fetch
*
* @return array<string, array> Array keyed by CRM ID with contact data
*/
public function getContactsByIds(array $crmIds, array $fields): array
{
return $this->batchReadObjects('contacts', $crmIds, $fields);
}
/**
* @throws CompanyApiException
* @throws CrmException
*/
public function getAccountById(string $crmId, array $fields): array
{
try {
$company = $this->getNewInstance()->crm()->companies()->basicApi()->getById(
$crmId,
implode(',', $fields),
);
} catch (CompanyApiException $e) {
$this->log->info('[Hubspot] Failed to fetch account', [
'crm_id' => $crmId,
'reason' => $e->getMessage(),
]);
throw $e;
}
if (! $company instanceof CompaniesWithAssociations) {
throw new CrmException('Account not found');
}
return [
'id' => $company->getId(),
'properties' => $company->getProperties(),
];
}
/**
* @throws ContactApiException
* @throws CrmException
*/
public function getContactById(string $crmId, array $fields): array
{
try {
$contact = $this->getNewInstance()->crm()->contacts()->basicApi()->getById(
$crmId,
implode(',', $fields)
);
} catch (ContactApiException $e) {
$this->log->info('[Hubspot] Failed to fetch contact', [
'crm_id' => $crmId,
'reason' => $e->getMessage(),
]);
throw $e;
}
if (! $contact instanceof ContactsWithAssociations) {
throw new CrmException('Contact not found');
}
return [
'id' => $contact->getId(),
'properties' => $contact->getProperties(),
];
}
/**
* This is email search request that Hubspot offers as GET (more generous quota)
*/
public function getContactByEmail(string $email, array $fields = []): array
{
try {
$contact = $this->getNewInstance()->crm()->contacts()->basicApi()->getById(
$email,
implode(',', $fields),
null,
false,
'email'
);
return [
'id' => $contact->getId(),
'properties' => $contact->getProperties(),
];
} catch (ContactApiException $e) {
$this->log->info('[Hubspot] Failed to fetch contact', [
'email' => $email,
'reason' => $e->getMessage(),
]);
return [];
}
}
/**
* @throws CrmException
*/
public function fetchProperty(string $objectType, string $propertyId): Property
{
$result = $this->getNewInstance()->crm()->properties()->coreApi()->getByName($objectType, $propertyId);
if (! $result instanceof Property) {
$this->log->error('[Hubspot] Failed to fetch property', [
'object_type' => $objectType,
'property_id' => $propertyId,
'reason' => $result->getMessage(),
]);
throw new CrmException('Failed to fetch property');
}
return $result;
}
/**
* @return array<CrmFieldOption>
*/
public function fetchPropertyOptions(string $objectType, string $propertyId): array
{
/** @var array<CrmFieldOption> */
return $this->fetchProperty($objectType, $propertyId)->getOptions();
}
/**
* @return array<array{id:string, label:string, deleted:bool}>
*/
public function fetchCallDispositions(): array
{
/** @var Response $response */
$response = $this->getInstance()->engagements()->getCallDispositions();
/**
* @var array<array{
* id:string,
* label:string,
* deleted: bool
* }>
*/
return $response->toArray();
}
/**
* @return array<CrmFieldOption>
*/
public function fetchOpportunityPipelineStages(): array
{
$stages = [];
$apiResponse = $this->getNewInstance()->crm()->pipelines()->pipelinesApi()->getAll('deals');
if ($apiResponse instanceof Error) {
$this->log->error('[Hubspot] Failed to fetch opportunity pipelines', [
'reason' => $apiResponse->getMessage(),
]);
return [];
}
foreach ($apiResponse->getResults() as $pipeline) {
$pipelineStages = array_map(
static function (PipelineStage $stage) {
return [
'id' => $stage->getId(),
'label' => $stage->getLabel(),
];
},
$pipeline->getStages()
);
$stages = array_merge($stages, $pipelineStages);
}
return $stages;
}
public function fetchOpportunityPipelines(): array
{
$pipelines = [];
try {
$apiResponse = $this->makeRequest('/crm/v3/pipelines/deals');
} catch (\Exception $e) {
$this->log->info('[Hubspot] Failed to fetch opportunity pipelines', [
'reason' => $e->getMessage(),
]);
return [];
}
$response = $apiResponse->toArray();
foreach ($response['results'] as $pipeline) {
$pipelines[] = [
'id' => $pipeline['id'],
'label' => $pipeline['label'],
];
}
return $pipelines;
}
/**
* @return array<CrmFieldOption>
*/
public function fetchMeetingOutcomeFieldOptions(Field $field): array
{
return $field->getCrmProviderId() === 'meetingOutcome'
? $this->fetchMeetingOutcomeTypes()
: $this->fetchCallActivityTypes();
}
public function fetchMeetingOutcomeTypes(): array
{
return $this->extractMeetingTypeOptions(
'[URL_WITH_CREDENTIALS] Response $response */
$response = $this->getInstance()
->getClient()
->request('GET', $endpoint);
/**
* @var array<array{
* value: string,
* label: string,
* displayOrder: int
* }> $optionData
*/
$optionData = $response->toArray()['options'] ?? [];
$options = [];
foreach ($optionData as $item) {
$options[] = [
'id' => $item['value'],
'value' => $item['value'],
'label' => $item['label'],
'display_order' => $item['displayOrder'],
];
}
return $options;
}
/**
* @return array<CrmFieldOption>
*/
public function fetchDispositionFieldOptions(): array
{
$options = [];
$dispositions = $this->fetchCallDispositions();
foreach ($dispositions as $disposition) {
if ($disposition['deleted'] !== false) {
continue;
}
$option['value'] = $disposition['id'];
$option['id'] = $disposition['id'];
$option['label'] = $disposition['label'];
$options[] = $option;
}
return $options;
}
/**
* @return array<CrmFieldOption>
*/
public function fetchOpportunityFieldOptions(Field $field): array
{
if ($field->isStageField()) {
return $this->fetchOpportunityPipelineStages();
}
if ($field->isPipelineField()) {
return $this->fetchOpportunityPipelines();
}
return $this->fetchPropertyOptions('deals', $field->getCrmProviderId());
}
/**
* @throws BadRequest
* @throws HubspotException
*/
public function makeRequest(string $endpoint, $method = 'GET', $payload = [], ?string $queryString = null)
{
$endpoint = self::BASE_URL . $endpoint;
if ($method === 'GET') {
$response = $this->getInstance()->getClient()?->request(
method: $method,
endpoint: $endpoint,
query_string: $queryString
);
} else {
$response = $this->getInstance()->getClient()->request($method, $endpoint, [
'json' => ($payload),
]);
}
$max = $response->getHeaderLine('X-HubSpot-RateLimit-Max'); // "110"
$remaining = $response->getHeaderLine('X-HubSpot-RateLimit-Remaining'); // "109"
$interval = $response->getHeaderLine('X-HubSpot-RateLimit-Interval-Milliseconds'); // "10000"
$body = json_decode((string) $response->getBody(), true);
\Illuminate\Support\Facades\Log::channel('custom_channel')->info('$max ' . PHP_EOL . print_r($max, true));
\Illuminate\Support\Facades\Log::channel('custom_channel')->info('$remaining ' . PHP_EOL . print_r($remaining, true));
\Illuminate\Support\Facades\Log::channel('custom_channel')->info('$interval ' . PHP_EOL . print_r($interval, true));
\Illuminate\Support\Facades\Log::channel('custom_channel')->info('$body ' . PHP_EOL . print_r($body, true));
return $response;
}
/**
* @throws BadRequest
* @throws HubspotException
*/
public function createMeeting(array $payload): Response
{
$endpoint = '/crm/v3/objects/meetings';
return $this->makeRequest($endpoint, 'POST', $payload);
}
/**
* @throws BadRequest
* @throws HubspotException
*/
public function updateMeeting(string $meetingId, array $payload): Response
{
$endpoint = '/crm/v3/objects/meetings/' . $meetingId;
return $this->makeRequest($endpoint, 'PATCH', $payload);
}
/**
* @throws \Exception
*/
public function createNote(
string $body,
string $ownerId,
int $timestamp,
string $objectId,
NoteObject $noteObject
): ?string {
try {
$noteInput = new SimplePublicObjectInput([
'properties' => [
'hs_note_body' => $body,
'hubspot_owner_id' => $ownerId,
'hs_timestamp' => $timestamp,
],
]);
// Create note
$note = $this->getNewInstance()->crm()->objects()->basicApi()->create('note', $noteInput);
$this->getNewInstance()->crm()->objects()->associationsApi()->create(
'note',
$note->getId(),
$this->getNoteObject($noteObject),
$objectId,
$this->getNoteAssociationType($noteObject),
);
return $note->getId();
} catch (\Exception $e) {
$this->log->error('[Hubspot] Failed to create note', [
'objectId' => $objectId,
'noteObject' => $noteObject->getObjectType(),
'reason' => $e->getMessage(),
]);
\Sentry::captureException($e);
}
return null;
}
public function updateEngagement(string $objectId, array $engagement, array $metadata): void
{
$this->getInstance()->engagements()->update($objectId, $engagement, $metadata);
}
public function getEngagementData(string $engagementId): array
{
$engagement = $this->getInstance()->engagements()->get($engagementId);
return $engagement->toArray();
}
public function createEngagement(array $engagement, array $associations, array $metadata): Response
{
return $this->getInstance()
->engagements()
->create($engagement, $associations, $metadata);
}
public function isUnauthorizedException(\Exception $e): bool
{
// Check for specific HubSpot API exception types first
if ($e instanceof BadRequest) {
// BadRequest can contain 401 status codes
return $e->getCode() === 401;
}
// Check for HTTP client exceptions with status codes
if ($e instanceof \GuzzleHttp\Exception\RequestException && $e->hasResponse()) {
$response = $e->getResponse();
if ($response !== null) {
return $response->getStatusCode() === 401;
}
}
// Check for Guzzle HTTP exceptions
if ($e instanceof \GuzzleHttp\Exception\ClientException) {
return $e->getCode() === 401;
}
// Fallback to string matching as last resort, but be more specific
$message = strtolower($e->getMessage());
return str_contains($message, '401 unauthorized') ||
str_contains($message, 'http 401') ||
str_contains($message, 'status code 401') ||
(preg_match('/\b401\b/', $message) && str_contains($message, 'unauthorized'));
}
/**
* Validates and refreshes the access token if needed before API requests.
* This ensures long-running processes don't fail due to token expiration.
*
* @throws SocialAccountTokenInvalidException
*/
public function ensureValidToken(): void
{
if ($this->oauthAccount === null) {
return;
}
$newToken = $this->tokenManager->ensureValidToken($this->oauthAccount);
if ($newToken !== null) {
$this->accessToken = $newToken;
}
}
public function getConfig()
{
return $this->config;
}
// returns only active (archived=false)
public function getOwners(): array
{
return $this->getNewInstance()->crm()->owners()->getAll();
}
/**
* @param bool $archived
*
* @return array<Owner>|[]
*/
public function getOwnersArchived(bool $archived = true): array
{
$endpoint = '/crm/v3/owners';
$queryParams = [
'archived' => $archived ? 'true' : 'false',
];
$queryString = http_build_query($queryParams);
$owners = [];
try {
$response = $this->makeRequest(endpoint: $endpoint, queryString: $queryString);
$responseData = $response?->toArray();
foreach ($responseData['results'] as $result) {
try {
$owners[] = Owner::create($result);
} catch (Throwable $e) {
$this->log->error('[HubSpot] Failed to process owner data', [
'result' => $result,
'error' => $e->getMessage(),
]);
continue;
}
}
} catch (Throwable $e) {
$this->log->error('HubSpot] Failed to fetch owners', [
'archived' => $archived,
'error' => $e->getMessage(),
]);
return [];
}
return $owners;
}
public function getMeeting(string $engagementId): ObjectWithAssociations
{
return $this->getNewInstance()->crm()->objects()->basicApi()
->getById('meeting', $engagementId, null, 'contact,company,deal');
}
public function deleteEngagement(string $engagementId): void
{
$this->getInstance()->engagements()->delete((int) $engagementId);
}
public function getAssociationsData(array $ids, string $fromObject, string $toObject): array
{
$associationData = [];
$idChunks = array_chunk($ids, self::ASSOCIATIONS_BATCH_SIZE_LIMIT);
foreach ($idChunks as $idChunk) {
try {
$batchInput = new \HubSpot\Client\Crm\Associations\Model\BatchInputPublicObjectId();
$batchInput->setInputs(array_map(function ($id) {
$publicObjectId = new \HubSpot\Client\Crm\Associations\Model\PublicObjectId();
$publicObjectId->setId($id);
return $publicObjectId;
}, $idChunk));
$associatedObjectsData = $this
->getNewInstance()
->crm()
->associations()
->batchApi()
->read($fromObject, $toObject, $batchInput);
if ($associatedObjectsData instanceof \HubSpot\Client\Crm\Associations\Model\BatchResponsePublicAssociationMulti) {
foreach ($associatedObjectsData->getResults() as $association) {
$from = $association->getFrom()->getId();
$toAssociations = $association->getTo();
if (! empty($toAssociations)) {
$associationData[$from] = array_map(function ($item) {
return $item->getId();
}, $toAssociations);
}
}
}
} catch (\Exception $e) {
$this->log->error('[Hubspot] Failed to fetch associations', [
'from_object' => $fromObject,
'to_object' => $toObject,
'reason' => $e->getMessage(),
]);
}
}
return $associationData;
}
/**
* @throws \Exception
*/
private function getNoteAssociationType(NoteObject $noteObject): string
{
return match($noteObject) {
NoteObject::Opportunity => 'note_to_deal',
NoteObject::Lead, NoteObject::Contact => 'note_to_contact', // or 'note_to_lead' if your portal supports it
NoteObject::Account => 'note_to_company',
NoteObject::Call, NoteObject::Event => throw new \Exception('Not supported'),
};
}
/**
* @throws \Exception
*/
private function getNoteObject(NoteObject $noteObject): string
{
return match($noteObject) {
NoteObject::Opportunity => 'deal',
NoteObject::Lead, NoteObject::Contact => 'contact',
NoteObject::Account => 'company',
NoteObject::Call, NoteObject::Event => throw new \Exception('Not supported'),
};
}
public function addAssociations(string $objectType, string $associationType, array $payload): Response
{
$endpoint = "/crm/v4/associations/$objectType/$associationType/batch/create";
return $this->makeRequest($endpoint, 'POST', $payload);
}
public function removeAssociations(string $objectType, string $associationType, array $payload): Response
{
$endpoint = "/crm/v4/associations/$objectType/$associationType/batch/archive";
return $this->makeRequest($endpoint, 'POST', $payload);
}
}
Project
Project
New File or Directory…
Expand Selected
Collapse All
Options
Hide...
|
NULL
|
NULL
|
NULL
|
NULL
|
|
2745
|
112
|
44
|
2026-05-07T11:38:09.615253+00:00
|
/Users/lukas/.screenpipe/data/data/2026-05-07/1778 /Users/lukas/.screenpipe/data/data/2026-05-07/1778153889615_m2.jpg...
|
PhpStorm
|
faVsco.js – Hubspot/Client.php
|
True
|
NULL
|
monitor_2
|
NULL
|
NULL
|
NULL
|
NULL
|
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
Editor for custom.log
Sync Changes
Hide This Notification
Code changed:
Hide
2
60
Previous Highlighted Error
Next Highlighted Error
<?php
declare(strict_types=1);
namespace Jiminny\Services\Crm\Hubspot;
use HubSpot\Client\Crm\Deals\ApiException as DealApiException;
use HubSpot\Client\Crm\Contacts\ApiException as ContactApiException;
use HubSpot\Client\Crm\Companies\ApiException as CompanyApiException;
use HubSpot\Client\Crm\Contacts\Model\SimplePublicObjectWithAssociations as ContactsWithAssociations;
use HubSpot\Client\Crm\Companies\Model\SimplePublicObjectWithAssociations as CompaniesWithAssociations;
use HubSpot\Client\Crm\Deals\Model\SimplePublicObjectWithAssociations as DealWithAssociations;
use HubSpot\Client\Crm\Objects\Model\SimplePublicObjectInput;
use HubSpot\Client\Crm\Objects\Model\SimplePublicObjectWithAssociations as ObjectWithAssociations;
use HubSpot\Client\Crm\Pipelines\Model\Error;
use HubSpot\Client\Crm\Pipelines\Model\PipelineStage;
use HubSpot\Client\Crm\Properties\Model\Property;
use HubSpot\Discovery\Discovery;
use Jiminny\Component\Utility\Service\ProviderRateLimiter;
use Jiminny\Exceptions\CrmException;
use Jiminny\Exceptions\RateLimitException;
use Jiminny\Exceptions\SocialAccountTokenInvalidException;
use Jiminny\Jobs\Crm\NoteObject;
use Jiminny\Models\Crm\Field;
use Jiminny\Services\Crm\BaseClient;
use Jiminny\Services\Crm\Hubspot\DTO\Response\Owner;
use Jiminny\Services\SocialAccountService;
use SevenShores\Hubspot\Exceptions\BadRequest;
use SevenShores\Hubspot\Exceptions\HubspotException;
use SevenShores\Hubspot\Factory;
use SevenShores\Hubspot\Http\Response;
use Jiminny\Services\Crm\Hubspot\Pagination\HubspotPaginationService;
use Throwable;
/**
* @phpstan-type CrmFieldOption array{id:string, label:string, value?:string}
*/
class Client extends BaseClient implements HubspotClientInterface
{
public const string MIN_API_VERSION = '2';
public const string BASE_URL = '[URL_WITH_CREDENTIALS] T
* @param callable(): T $apiCall
* @return T
*
* @throws RateLimitException
*/
private function executeRequest(callable $apiCall)
{
if (! $this->rateLimiter->canMakeRequest($this->config)) {
$retryAfter = $this->rateLimiter->requestAvailableIn($this->config);
$this->log->warning('[Hubspot] Rate limit exceeded, deferring request', [
'team_id' => $this->config->team_id,
'config_id' => $this->config->getId(),
'retry_after' => $retryAfter,
]);
throw new RateLimitException(
'Hubspot rate limit reached for configuration ' . $this->config->getId(),
$retryAfter,
);
}
$this->rateLimiter->incrementRequestCount($this->config);
try {
return $apiCall();
} catch (Throwable $e) {
if ($this->isHubspotRateLimit($e)) {
$retryAfter = $this->parseRetryAfter($e);
$this->log->warning('[Hubspot] Received 429 from API', [
'team_id' => $this->config->team_id,
'config_id' => $this->config->getId(),
'retry_after' => $retryAfter,
'reason' => $e->getMessage(),
]);
throw new RateLimitException('Hubspot returned 429', $retryAfter, $e);
}
throw $e;
}
}
private function isHubspotRateLimit(Throwable $e): bool
{
return method_exists($e, 'getCode') && (int) $e->getCode() === 429;
}
private function parseRetryAfter(Throwable $e): int
{
if (method_exists($e, 'getResponseHeaders')) {
$headers = $e->getResponseHeaders() ?: [];
$value = $headers['Retry-After'] ?? $headers['retry-after'] ?? null;
if (is_array($value)) {
$value = $value[0] ?? null;
}
if (is_numeric($value)) {
return (int) $value;
}
}
return 10;
}
public function getMinimumApiVersion(): string
{
return self::MIN_API_VERSION;
}
public function getInstance(): Factory
{
return new Factory([
'key' => $this->accessToken,
'oauth2' => true,
'base_url' => $this->baseUrl,
]);
}
public function getNewInstance(): Discovery
{
return \HubSpot\Factory::createWithAccessToken($this->accessToken);
}
/**
* Secondly and daily limits for Hubspot API
*
* Product Tier: Free & Starter | Professional & Enterprise | API add-on (any tier)
* Burst: 100/10 seconds | 150/10 seconds | 200/10 seconds
* Daily: 250,000 | 500,000 | 1,000,000
*
* Official documentation states: The search endpoints are rate limited to five requests per second.
* Since with 5 RPS were still hitting secondly rate limits we lowered it to 4
*/
public function getPaginatedData(array $payload, string $type, int $offset = 0): array
{
$total = 0;
$lastId = null;
$rows = [];
foreach ($this->getPaginatedDataGenerator($payload, $type, $offset, $total, $lastId) as $row) {
$rows[] = $row;
}
return ['results' => $rows, 'total' => $total, 'last_record' => $lastId];
}
/**
* @throws HubspotException
* @throws SocialAccountTokenInvalidException
* @throws BadRequest
*/
public function getPaginatedDataGenerator(
array $payload,
string $type,
int $offset = 0,
int &$total = 0,
?string &$lastRecordId = null
): \Generator {
return $this->paginationService->getPaginatedDataGenerator(
$this,
$payload,
$type,
$offset,
$total,
$lastRecordId
);
}
/**
* @throws DealApiException
* @throws CrmException
*/
public function getOpportunityById(string $crmId, array $fields): array
{
try {
$deal = $this->executeRequest(fn () => $this->getNewInstance()->crm()->deals()->basicApi()->getById(
$crmId,
implode(',', $fields),
'companies,contacts'
));
} catch (DealApiException $e) {
$this->log->info('[Hubspot] Failed to fetch opportunity', [
'crm_id' => $crmId,
'reason' => $e->getMessage(),
]);
throw $e;
}
if (! $deal instanceof DealWithAssociations) {
throw new CrmException('Deal not found');
}
return [
'id' => $deal->getId(),
'properties' => $deal->getProperties(),
'associations' => $deal->getAssociations(),
];
}
/**
* Generic batch read method for HubSpot objects
*
* @param string $objectType The object type ('deals', 'companies', 'contacts')
* @param array<string> $crmIds Array of HubSpot object IDs (max 100)
* @param array<string> $fields Array of property names to fetch
*
* @return array<string, array> Array keyed by CRM ID with object data
*/
private function batchReadObjects(string $objectType, array $crmIds, array $fields): array
{
if (empty($crmIds)) {
return [];
}
$this->validateBatchSize($objectType, $crmIds);
$this->ensureValidToken();
try {
$batchConfig = $this->createBatchConfiguration($objectType);
$batchReadRequest = $this->prepareBatchRequest($batchConfig, $crmIds, $fields);
$response = $batchConfig['api']->read($batchReadRequest);
$this->validateApiResponse($response, $objectType);
$results = $this->processApiResults($response);
$this->logBatchResults($objectType, $crmIds, $results);
return $results;
} catch (\Throwable $e) {
$this->handleBatchError($e, $objectType, $crmIds);
}
}
private function validateBatchSize(string $objectType, array $crmIds): void
{
if (count($crmIds) > 100) {
throw new \InvalidArgumentException("Batch size cannot exceed 100 {$objectType}");
}
}
private function createBatchConfiguration(string $objectType): array
{
$configurations = [
'deals' => [
'batchReadRequest' => new \HubSpot\Client\Crm\Deals\Model\BatchReadInputSimplePublicObjectId(),
'inputClass' => \HubSpot\Client\Crm\Deals\Model\SimplePublicObjectId::class,
'api' => $this->getNewInstance()->crm()->deals()->batchApi(),
],
'companies' => [
'batchReadRequest' => new \HubSpot\Client\Crm\Companies\Model\BatchReadInputSimplePublicObjectId(),
'inputClass' => \HubSpot\Client\Crm\Companies\Model\SimplePublicObjectId::class,
'api' => $this->getNewInstance()->crm()->companies()->batchApi(),
],
'contacts' => [
'batchReadRequest' => new \HubSpot\Client\Crm\Contacts\Model\BatchReadInputSimplePublicObjectId(),
'inputClass' => \HubSpot\Client\Crm\Contacts\Model\SimplePublicObjectId::class,
'api' => $this->getNewInstance()->crm()->contacts()->batchApi(),
],
];
if (! isset($configurations[$objectType])) {
throw new \InvalidArgumentException("Unsupported object type: {$objectType}");
}
return $configurations[$objectType];
}
private function prepareBatchRequest(array $batchConfig, array $crmIds, array $fields): object
{
$batchReadRequest = $batchConfig['batchReadRequest'];
$inputClass = $batchConfig['inputClass'];
$inputs = array_map(function ($crmId) use ($inputClass) {
$input = new $inputClass();
$input->setId($crmId);
return $input;
}, $crmIds);
$batchReadRequest->setInputs($inputs);
$batchReadRequest->setProperties($fields);
return $batchReadRequest;
}
private function validateApiResponse($response, string $objectType): void
{
if (! $response) {
throw new CrmException("HubSpot API returned null response for {$objectType} batch read");
}
}
private function processApiResults($response): array
{
$results = [];
$responseResults = $response->getResults();
if ($responseResults) {
foreach ($responseResults as $object) {
if ($object && $object->getId()) {
$results[$object->getId()] = [
'id' => $object->getId(),
'properties' => $object->getProperties() ?: [],
];
}
}
}
return $results;
}
private function logBatchResults(string $objectType, array $crmIds, array $results): void
{
$this->log->info("[HubSpot] Batch fetched {$objectType}", [
'requested_count' => count($crmIds),
'returned_count' => count($results),
'crm_ids' => $crmIds,
]);
}
private function handleBatchError(\Throwable $e, string $objectType, array $crmIds): void
{
$errorMessage = $e->getMessage() ?: 'Unknown error';
$errorTrace = $e->getTraceAsString() ?: 'No trace available';
$this->log->error("[HubSpot] Failed to batch fetch {$objectType}", [
'crm_ids' => $crmIds,
'error' => $errorMessage,
'trace' => $errorTrace,
]);
throw new CrmException("Failed to batch fetch {$objectType}: " . $errorMessage);
}
/**
* Batch read multiple opportunities by their CRM IDs
*
* @param array<string> $crmIds Array of HubSpot deal IDs (max 100)
* @param array<string> $fields Array of property names to fetch
*
* @return array<string, array> Array keyed by CRM ID with opportunity data
*/
public function getOpportunitiesByIds(array $crmIds, array $fields): array
{
return $this->batchReadObjects('deals', $crmIds, $fields);
}
/**
* Batch read multiple companies by their CRM IDs
*
* @param array<string> $crmIds Array of HubSpot company IDs (max 100)
* @param array<string> $fields Array of property names to fetch
*
* @return array<string, array> Array keyed by CRM ID with company data
*/
public function getCompaniesByIds(array $crmIds, array $fields): array
{
return $this->batchReadObjects('companies', $crmIds, $fields);
}
/**
* Batch read multiple contacts by their CRM IDs
*
* @param array<string> $crmIds Array of HubSpot contact IDs (max 100)
* @param array<string> $fields Array of property names to fetch
*
* @return array<string, array> Array keyed by CRM ID with contact data
*/
public function getContactsByIds(array $crmIds, array $fields): array
{
return $this->batchReadObjects('contacts', $crmIds, $fields);
}
/**
* @throws CompanyApiException
* @throws CrmException
*/
public function getAccountById(string $crmId, array $fields): array
{
try {
$company = $this->getNewInstance()->crm()->companies()->basicApi()->getById(
$crmId,
implode(',', $fields),
);
} catch (CompanyApiException $e) {
$this->log->info('[Hubspot] Failed to fetch account', [
'crm_id' => $crmId,
'reason' => $e->getMessage(),
]);
throw $e;
}
if (! $company instanceof CompaniesWithAssociations) {
throw new CrmException('Account not found');
}
return [
'id' => $company->getId(),
'properties' => $company->getProperties(),
];
}
/**
* @throws ContactApiException
* @throws CrmException
*/
public function getContactById(string $crmId, array $fields): array
{
try {
$contact = $this->getNewInstance()->crm()->contacts()->basicApi()->getById(
$crmId,
implode(',', $fields)
);
} catch (ContactApiException $e) {
$this->log->info('[Hubspot] Failed to fetch contact', [
'crm_id' => $crmId,
'reason' => $e->getMessage(),
]);
throw $e;
}
if (! $contact instanceof ContactsWithAssociations) {
throw new CrmException('Contact not found');
}
return [
'id' => $contact->getId(),
'properties' => $contact->getProperties(),
];
}
/**
* This is email search request that Hubspot offers as GET (more generous quota)
*/
public function getContactByEmail(string $email, array $fields = []): array
{
try {
$contact = $this->getNewInstance()->crm()->contacts()->basicApi()->getById(
$email,
implode(',', $fields),
null,
false,
'email'
);
return [
'id' => $contact->getId(),
'properties' => $contact->getProperties(),
];
} catch (ContactApiException $e) {
$this->log->info('[Hubspot] Failed to fetch contact', [
'email' => $email,
'reason' => $e->getMessage(),
]);
return [];
}
}
/**
* @throws CrmException
*/
public function fetchProperty(string $objectType, string $propertyId): Property
{
$result = $this->getNewInstance()->crm()->properties()->coreApi()->getByName($objectType, $propertyId);
if (! $result instanceof Property) {
$this->log->error('[Hubspot] Failed to fetch property', [
'object_type' => $objectType,
'property_id' => $propertyId,
'reason' => $result->getMessage(),
]);
throw new CrmException('Failed to fetch property');
}
return $result;
}
/**
* @return array<CrmFieldOption>
*/
public function fetchPropertyOptions(string $objectType, string $propertyId): array
{
/** @var array<CrmFieldOption> */
return $this->fetchProperty($objectType, $propertyId)->getOptions();
}
/**
* @return array<array{id:string, label:string, deleted:bool}>
*/
public function fetchCallDispositions(): array
{
/** @var Response $response */
$response = $this->getInstance()->engagements()->getCallDispositions();
/**
* @var array<array{
* id:string,
* label:string,
* deleted: bool
* }>
*/
return $response->toArray();
}
/**
* @return array<CrmFieldOption>
*/
public function fetchOpportunityPipelineStages(): array
{
$stages = [];
$apiResponse = $this->getNewInstance()->crm()->pipelines()->pipelinesApi()->getAll('deals');
if ($apiResponse instanceof Error) {
$this->log->error('[Hubspot] Failed to fetch opportunity pipelines', [
'reason' => $apiResponse->getMessage(),
]);
return [];
}
foreach ($apiResponse->getResults() as $pipeline) {
$pipelineStages = array_map(
static function (PipelineStage $stage) {
return [
'id' => $stage->getId(),
'label' => $stage->getLabel(),
];
},
$pipeline->getStages()
);
$stages = array_merge($stages, $pipelineStages);
}
return $stages;
}
public function fetchOpportunityPipelines(): array
{
$pipelines = [];
try {
$apiResponse = $this->makeRequest('/crm/v3/pipelines/deals');
} catch (\Exception $e) {
$this->log->info('[Hubspot] Failed to fetch opportunity pipelines', [
'reason' => $e->getMessage(),
]);
return [];
}
$response = $apiResponse->toArray();
foreach ($response['results'] as $pipeline) {
$pipelines[] = [
'id' => $pipeline['id'],
'label' => $pipeline['label'],
];
}
return $pipelines;
}
/**
* @return array<CrmFieldOption>
*/
public function fetchMeetingOutcomeFieldOptions(Field $field): array
{
return $field->getCrmProviderId() === 'meetingOutcome'
? $this->fetchMeetingOutcomeTypes()
: $this->fetchCallActivityTypes();
}
public function fetchMeetingOutcomeTypes(): array
{
return $this->extractMeetingTypeOptions(
'[URL_WITH_CREDENTIALS] Response $response */
$response = $this->getInstance()
->getClient()
->request('GET', $endpoint);
/**
* @var array<array{
* value: string,
* label: string,
* displayOrder: int
* }> $optionData
*/
$optionData = $response->toArray()['options'] ?? [];
$options = [];
foreach ($optionData as $item) {
$options[] = [
'id' => $item['value'],
'value' => $item['value'],
'label' => $item['label'],
'display_order' => $item['displayOrder'],
];
}
return $options;
}
/**
* @return array<CrmFieldOption>
*/
public function fetchDispositionFieldOptions(): array
{
$options = [];
$dispositions = $this->fetchCallDispositions();
foreach ($dispositions as $disposition) {
if ($disposition['deleted'] !== false) {
continue;
}
$option['value'] = $disposition['id'];
$option['id'] = $disposition['id'];
$option['label'] = $disposition['label'];
$options[] = $option;
}
return $options;
}
/**
* @return array<CrmFieldOption>
*/
public function fetchOpportunityFieldOptions(Field $field): array
{
if ($field->isStageField()) {
return $this->fetchOpportunityPipelineStages();
}
if ($field->isPipelineField()) {
return $this->fetchOpportunityPipelines();
}
return $this->fetchPropertyOptions('deals', $field->getCrmProviderId());
}
/**
* @throws BadRequest
* @throws HubspotException
*/
public function makeRequest(string $endpoint, $method = 'GET', $payload = [], ?string $queryString = null)
{
$endpoint = self::BASE_URL . $endpoint;
if ($method === 'GET') {
$response = $this->getInstance()->getClient()?->request(
method: $method,
endpoint: $endpoint,
query_string: $queryString
);
} else {
$response = $this->getInstance()->getClient()->request($method, $endpoint, [
'json' => ($payload),
]);
}
$max = $response->getHeaderLine('X-HubSpot-RateLimit-Max'); // "110"
$remaining = $response->getHeaderLine('X-HubSpot-RateLimit-Remaining'); // "109"
$interval = $response->getHeaderLine('X-HubSpot-RateLimit-Interval-Milliseconds'); // "10000"
$body = json_decode((string) $response->getBody(), true);
\Illuminate\Support\Facades\Log::channel('custom_channel')->info('$max ' . PHP_EOL . print_r($max, true));
\Illuminate\Support\Facades\Log::channel('custom_channel')->info('$remaining ' . PHP_EOL . print_r($remaining, true));
\Illuminate\Support\Facades\Log::channel('custom_channel')->info('$interval ' . PHP_EOL . print_r($interval, true));
\Illuminate\Support\Facades\Log::channel('custom_channel')->info('$body ' . PHP_EOL . print_r($body, true));
return $response;
}
/**
* @throws BadRequest
* @throws HubspotException
*/
public function createMeeting(array $payload): Response
{
$endpoint = '/crm/v3/objects/meetings';
return $this->makeRequest($endpoint, 'POST', $payload);
}
/**
* @throws BadRequest
* @throws HubspotException
*/
public function updateMeeting(string $meetingId, array $payload): Response
{
$endpoint = '/crm/v3/objects/meetings/' . $meetingId;
return $this->makeRequest($endpoint, 'PATCH', $payload);
}
/**
* @throws \Exception
*/
public function createNote(
string $body,
string $ownerId,
int $timestamp,
string $objectId,
NoteObject $noteObject
): ?string {
try {
$noteInput = new SimplePublicObjectInput([
'properties' => [
'hs_note_body' => $body,
'hubspot_owner_id' => $ownerId,
'hs_timestamp' => $timestamp,
],
]);
// Create note
$note = $this->getNewInstance()->crm()->objects()->basicApi()->create('note', $noteInput);
$this->getNewInstance()->crm()->objects()->associationsApi()->create(
'note',
$note->getId(),
$this->getNoteObject($noteObject),
$objectId,
$this->getNoteAssociationType($noteObject),
);
return $note->getId();
} catch (\Exception $e) {
$this->log->error('[Hubspot] Failed to create note', [
'objectId' => $objectId,
'noteObject' => $noteObject->getObjectType(),
'reason' => $e->getMessage(),
]);
\Sentry::captureException($e);
}
return null;
}
public function updateEngagement(string $objectId, array $engagement, array $metadata): void
{
$this->getInstance()->engagements()->update($objectId, $engagement, $metadata);
}
public function getEngagementData(string $engagementId): array
{
$engagement = $this->getInstance()->engagements()->get($engagementId);
return $engagement->toArray();
}
public function createEngagement(array $engagement, array $associations, array $metadata): Response
{
return $this->getInstance()
->engagements()
->create($engagement, $associations, $metadata);
}
public function isUnauthorizedException(\Exception $e): bool
{
// Check for specific HubSpot API exception types first
if ($e instanceof BadRequest) {
// BadRequest can contain 401 status codes
return $e->getCode() === 401;
}
// Check for HTTP client exceptions with status codes
if ($e instanceof \GuzzleHttp\Exception\RequestException && $e->hasResponse()) {
$response = $e->getResponse();
if ($response !== null) {
return $response->getStatusCode() === 401;
}
}
// Check for Guzzle HTTP exceptions
if ($e instanceof \GuzzleHttp\Exception\ClientException) {
return $e->getCode() === 401;
}
// Fallback to string matching as last resort, but be more specific
$message = strtolower($e->getMessage());
return str_contains($message, '401 unauthorized') ||
str_contains($message, 'http 401') ||
str_contains($message, 'status code 401') ||
(preg_match('/\b401\b/', $message) && str_contains($message, 'unauthorized'));
}
/**
* Validates and refreshes the access token if needed before API requests.
* This ensures long-running processes don't fail due to token expiration.
*
* @throws SocialAccountTokenInvalidException
*/
public function ensureValidToken(): void
{
if ($this->oauthAccount === null) {
return;
}
$newToken = $this->tokenManager->ensureValidToken($this->oauthAccount);
if ($newToken !== null) {
$this->accessToken = $newToken;
}
}
public function getConfig()
{
return $this->config;
}
// returns only active (archived=false)
public function getOwners(): array
{
return $this->getNewInstance()->crm()->owners()->getAll();
}
/**
* @param bool $archived
*
* @return array<Owner>|[]
*/
public function getOwnersArchived(bool $archived = true): array
{
$endpoint = '/crm/v3/owners';
$queryParams = [
'archived' => $archived ? 'true' : 'false',
];
$queryString = http_build_query($queryParams);
$owners = [];
try {
$response = $this->makeRequest(endpoint: $endpoint, queryString: $queryString);
$responseData = $response?->toArray();
foreach ($responseData['results'] as $result) {
try {
$owners[] = Owner::create($result);
} catch (Throwable $e) {
$this->log->error('[HubSpot] Failed to process owner data', [
'result' => $result,
'error' => $e->getMessage(),
]);
continue;
}
}
} catch (Throwable $e) {
$this->log->error('HubSpot] Failed to fetch owners', [
'archived' => $archived,
'error' => $e->getMessage(),
]);
return [];
}
return $owners;
}
public function getMeeting(string $engagementId): ObjectWithAssociations
{
return $this->getNewInstance()->crm()->objects()->basicApi()
->getById('meeting', $engagementId, null, 'contact,company,deal');
}
public function deleteEngagement(string $engagementId): void
{
$this->getInstance()->engagements()->delete((int) $engagementId);
}
public function getAssociationsData(array $ids, string $fromObject, string $toObject): array
{
$associationData = [];
$idChunks = array_chunk($ids, self::ASSOCIATIONS_BATCH_SIZE_LIMIT);
foreach ($idChunks as $idChunk) {
try {
$batchInput = new \HubSpot\Client\Crm\Associations\Model\BatchInputPublicObjectId();
$batchInput->setInputs(array_map(function ($id) {
$publicObjectId = new \HubSpot\Client\Crm\Associations\Model\PublicObjectId();
$publicObjectId->setId($id);
return $publicObjectId;
}, $idChunk));
$associatedObjectsData = $this
->getNewInstance()
->crm()
->associations()
->batchApi()
->read($fromObject, $toObject, $batchInput);
if ($associatedObjectsData instanceof \HubSpot\Client\Crm\Associations\Model\BatchResponsePublicAssociationMulti) {
foreach ($associatedObjectsData->getResults() as $association) {
$from = $association->getFrom()->getId();
$toAssociations = $association->getTo();
if (! empty($toAssociations)) {
$associationData[$from] = array_map(function ($item) {
return $item->getId();
}, $toAssociations);
}
}
}
} catch (\Exception $e) {
$this->log->error('[Hubspot] Failed to fetch associations', [
'from_object' => $fromObject,
'to_object' => $toObject,
'reason' => $e->getMessage(),
]);
}
}
return $associationData;
}
/**
* @throws \Exception
*/
private function getNoteAssociationType(NoteObject $noteObject): string
{
return match($noteObject) {
NoteObject::Opportunity => 'note_to_deal',
NoteObject::Lead, NoteObject::Contact => 'note_to_contact', // or 'note_to_lead' if your portal supports it
NoteObject::Account => 'note_to_company',
NoteObject::Call, NoteObject::Event => throw new \Exception('Not supported'),
};
}
/**
* @throws \Exception
*/
private function getNoteObject(NoteObject $noteObject): string
{
return match($noteObject) {
NoteObject::Opportunity => 'deal',
NoteObject::Lead, NoteObject::Contact => 'contact',
NoteObject::Account => 'company',
NoteObject::Call, NoteObject::Event => throw new \Exception('Not supported'),
};
}
public function addAssociations(string $objectType, string $associationType, array $payload): Response
{
$endpoint = "/crm/v4/associations/$objectType/$associationType/batch/create";
return $this->makeRequest($endpoint, 'POST', $payload);
}
public function removeAssociations(string $objectType, string $associationType, array $payload): Response
{
$endpoint = "/crm/v4/associations/$objectType/$associationType/batch/archive";
return $this->makeRequest($endpoint, 'POST', $payload);
}
}
Project
Project
New File or Directory…
Expand Selected
Collapse All
Options
Hide...
|
[{"role":"AXButton","text" [{"role":"AXButton","text":"Project: faVsco.js, menu","depth":5,"bounds":{"left":0.025930852,"top":0.019952115,"width":0.03856383,"height":0.025538707},"on_screen":true,"help_text":"~/jiminny/app","role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"master, menu","depth":5,"bounds":{"left":0.064494684,"top":0.019952115,"width":0.034242023,"height":0.025538707},"on_screen":true,"help_text":"Git Branch: master","role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Start Listening for PHP Debug Connections","depth":5,"bounds":{"left":0.8081782,"top":0.019952115,"width":0.011303191,"height":0.025538707},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"AskJiminnyReportActivityServiceTest","depth":6,"bounds":{"left":0.8234708,"top":0.019952115,"width":0.09208777,"height":0.025538707},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Run 'AskJiminnyReportActivityServiceTest'","depth":6,"bounds":{"left":0.9155585,"top":0.019952115,"width":0.011303191,"height":0.025538707},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Debug 'AskJiminnyReportActivityServiceTest'","depth":6,"bounds":{"left":0.9268617,"top":0.019952115,"width":0.011303191,"height":0.025538707},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"More Actions","depth":6,"bounds":{"left":0.9381649,"top":0.019952115,"width":0.011303191,"height":0.025538707},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"JetBrains AI","depth":5,"bounds":{"left":0.96609044,"top":0.019952115,"width":0.011303191,"height":0.025538707},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Search Everywhere","depth":5,"bounds":{"left":0.9773936,"top":0.019952115,"width":0.011303191,"height":0.025538707},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"IDE and Project Settings","depth":5,"bounds":{"left":0.9886968,"top":0.019952115,"width":0.011303186,"height":0.025538707},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Sync Changes","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.008643617,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Hide This Notification","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.008643617,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Code changed:","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.042220745,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Hide","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.008643617,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXTextArea","text":"Editor for custom.log","depth":4,"bounds":{"left":0.5475399,"top":0.0726257,"width":0.44082448,"height":0.9066241},"on_screen":true,"role_description":"text entry area","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Sync Changes","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.008643617,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Hide This Notification","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.008643617,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Code changed:","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.042220745,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Hide","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.008643617,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"2","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.007978723,"height":0.0},"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"60","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.010305851,"height":0.0},"on_screen":false,"role_description":"text"},{"role":"AXButton","text":"Previous Highlighted Error","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.00731383,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Next Highlighted Error","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.006981383,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXTextArea","text":"<?php\n\ndeclare(strict_types=1);\n\nnamespace Jiminny\\Services\\Crm\\Hubspot;\n\nuse HubSpot\\Client\\Crm\\Deals\\ApiException as DealApiException;\nuse HubSpot\\Client\\Crm\\Contacts\\ApiException as ContactApiException;\nuse HubSpot\\Client\\Crm\\Companies\\ApiException as CompanyApiException;\nuse HubSpot\\Client\\Crm\\Contacts\\Model\\SimplePublicObjectWithAssociations as ContactsWithAssociations;\nuse HubSpot\\Client\\Crm\\Companies\\Model\\SimplePublicObjectWithAssociations as CompaniesWithAssociations;\nuse HubSpot\\Client\\Crm\\Deals\\Model\\SimplePublicObjectWithAssociations as DealWithAssociations;\nuse HubSpot\\Client\\Crm\\Objects\\Model\\SimplePublicObjectInput;\nuse HubSpot\\Client\\Crm\\Objects\\Model\\SimplePublicObjectWithAssociations as ObjectWithAssociations;\nuse HubSpot\\Client\\Crm\\Pipelines\\Model\\Error;\nuse HubSpot\\Client\\Crm\\Pipelines\\Model\\PipelineStage;\nuse HubSpot\\Client\\Crm\\Properties\\Model\\Property;\nuse HubSpot\\Discovery\\Discovery;\nuse Jiminny\\Component\\Utility\\Service\\ProviderRateLimiter;\nuse Jiminny\\Exceptions\\CrmException;\nuse Jiminny\\Exceptions\\RateLimitException;\nuse Jiminny\\Exceptions\\SocialAccountTokenInvalidException;\nuse Jiminny\\Jobs\\Crm\\NoteObject;\nuse Jiminny\\Models\\Crm\\Field;\nuse Jiminny\\Services\\Crm\\BaseClient;\nuse Jiminny\\Services\\Crm\\Hubspot\\DTO\\Response\\Owner;\nuse Jiminny\\Services\\SocialAccountService;\nuse SevenShores\\Hubspot\\Exceptions\\BadRequest;\nuse SevenShores\\Hubspot\\Exceptions\\HubspotException;\nuse SevenShores\\Hubspot\\Factory;\nuse SevenShores\\Hubspot\\Http\\Response;\nuse Jiminny\\Services\\Crm\\Hubspot\\Pagination\\HubspotPaginationService;\nuse Throwable;\n\n/**\n * @phpstan-type CrmFieldOption array{id:string, label:string, value?:string}\n */\nclass Client extends BaseClient implements HubspotClientInterface\n{\n public const string MIN_API_VERSION = '2';\n\n public const string BASE_URL = 'https://api.hubapi.com';\n\n public const int ASSOCIATIONS_BATCH_SIZE_LIMIT = 1000;\n\n private HubspotPaginationService $paginationService;\n private HubspotTokenManager $tokenManager;\n private ProviderRateLimiter $rateLimiter;\n\n public function __construct(\n SocialAccountService $socialAccountService,\n HubspotPaginationService $paginationService,\n HubspotTokenManager $tokenManager,\n ProviderRateLimiter $rateLimiter,\n ) {\n parent::__construct($socialAccountService);\n $this->paginationService = $paginationService;\n $this->tokenManager = $tokenManager;\n $this->rateLimiter = $rateLimiter;\n\n $this->setBaseUrl(self::BASE_URL);\n $this->setVersion(self::MIN_API_VERSION);\n }\n\n /**\n * Single entry point for every HubSpot API call. Enforces the per-portal\n * rate limit configured in the rate_limits table (morphed to the current\n * Configuration) and reacts to a real 429 from HubSpot by translating it\n * into a RateLimitException carrying Retry-After.\n *\n * Wrap any outbound HubSpot call (SDK or raw HTTP) like:\n *\n * $this->executeRequest(fn () => $this->getNewInstance()->crm()->...);\n *\n * @template T\n * @param callable(): T $apiCall\n * @return T\n *\n * @throws RateLimitException\n */\n private function executeRequest(callable $apiCall)\n {\n if (! $this->rateLimiter->canMakeRequest($this->config)) {\n $retryAfter = $this->rateLimiter->requestAvailableIn($this->config);\n\n $this->log->warning('[Hubspot] Rate limit exceeded, deferring request', [\n 'team_id' => $this->config->team_id,\n 'config_id' => $this->config->getId(),\n 'retry_after' => $retryAfter,\n ]);\n\n throw new RateLimitException(\n 'Hubspot rate limit reached for configuration ' . $this->config->getId(),\n $retryAfter,\n );\n }\n\n $this->rateLimiter->incrementRequestCount($this->config);\n\n try {\n return $apiCall();\n } catch (Throwable $e) {\n if ($this->isHubspotRateLimit($e)) {\n $retryAfter = $this->parseRetryAfter($e);\n\n $this->log->warning('[Hubspot] Received 429 from API', [\n 'team_id' => $this->config->team_id,\n 'config_id' => $this->config->getId(),\n 'retry_after' => $retryAfter,\n 'reason' => $e->getMessage(),\n ]);\n\n throw new RateLimitException('Hubspot returned 429', $retryAfter, $e);\n }\n\n throw $e;\n }\n }\n\n private function isHubspotRateLimit(Throwable $e): bool\n {\n return method_exists($e, 'getCode') && (int) $e->getCode() === 429;\n }\n\n private function parseRetryAfter(Throwable $e): int\n {\n if (method_exists($e, 'getResponseHeaders')) {\n $headers = $e->getResponseHeaders() ?: [];\n $value = $headers['Retry-After'] ?? $headers['retry-after'] ?? null;\n if (is_array($value)) {\n $value = $value[0] ?? null;\n }\n if (is_numeric($value)) {\n return (int) $value;\n }\n }\n\n return 10;\n }\n\n public function getMinimumApiVersion(): string\n {\n return self::MIN_API_VERSION;\n }\n\n public function getInstance(): Factory\n {\n return new Factory([\n 'key' => $this->accessToken,\n 'oauth2' => true,\n 'base_url' => $this->baseUrl,\n ]);\n }\n\n public function getNewInstance(): Discovery\n {\n return \\HubSpot\\Factory::createWithAccessToken($this->accessToken);\n }\n\n /**\n * Secondly and daily limits for Hubspot API\n *\n * Product Tier: Free & Starter | Professional & Enterprise | API add-on (any tier)\n * Burst: 100/10 seconds | 150/10 seconds | 200/10 seconds\n * Daily: 250,000 | 500,000 | 1,000,000\n *\n * Official documentation states: The search endpoints are rate limited to five requests per second.\n * Since with 5 RPS were still hitting secondly rate limits we lowered it to 4\n */\n public function getPaginatedData(array $payload, string $type, int $offset = 0): array\n {\n $total = 0;\n $lastId = null;\n $rows = [];\n foreach ($this->getPaginatedDataGenerator($payload, $type, $offset, $total, $lastId) as $row) {\n $rows[] = $row;\n }\n\n return ['results' => $rows, 'total' => $total, 'last_record' => $lastId];\n }\n\n /**\n * @throws HubspotException\n * @throws SocialAccountTokenInvalidException\n * @throws BadRequest\n */\n public function getPaginatedDataGenerator(\n array $payload,\n string $type,\n int $offset = 0,\n int &$total = 0,\n ?string &$lastRecordId = null\n ): \\Generator {\n return $this->paginationService->getPaginatedDataGenerator(\n $this,\n $payload,\n $type,\n $offset,\n $total,\n $lastRecordId\n );\n }\n\n /**\n * @throws DealApiException\n * @throws CrmException\n */\n public function getOpportunityById(string $crmId, array $fields): array\n {\n try {\n $deal = $this->executeRequest(fn () => $this->getNewInstance()->crm()->deals()->basicApi()->getById(\n $crmId,\n implode(',', $fields),\n 'companies,contacts'\n ));\n } catch (DealApiException $e) {\n $this->log->info('[Hubspot] Failed to fetch opportunity', [\n 'crm_id' => $crmId,\n 'reason' => $e->getMessage(),\n ]);\n\n throw $e;\n }\n\n if (! $deal instanceof DealWithAssociations) {\n throw new CrmException('Deal not found');\n }\n\n return [\n 'id' => $deal->getId(),\n 'properties' => $deal->getProperties(),\n 'associations' => $deal->getAssociations(),\n ];\n }\n\n /**\n * Generic batch read method for HubSpot objects\n *\n * @param string $objectType The object type ('deals', 'companies', 'contacts')\n * @param array<string> $crmIds Array of HubSpot object IDs (max 100)\n * @param array<string> $fields Array of property names to fetch\n *\n * @return array<string, array> Array keyed by CRM ID with object data\n */\n private function batchReadObjects(string $objectType, array $crmIds, array $fields): array\n {\n if (empty($crmIds)) {\n return [];\n }\n\n $this->validateBatchSize($objectType, $crmIds);\n $this->ensureValidToken();\n\n try {\n $batchConfig = $this->createBatchConfiguration($objectType);\n $batchReadRequest = $this->prepareBatchRequest($batchConfig, $crmIds, $fields);\n $response = $batchConfig['api']->read($batchReadRequest);\n\n $this->validateApiResponse($response, $objectType);\n\n $results = $this->processApiResults($response);\n $this->logBatchResults($objectType, $crmIds, $results);\n\n return $results;\n } catch (\\Throwable $e) {\n $this->handleBatchError($e, $objectType, $crmIds);\n }\n }\n\n private function validateBatchSize(string $objectType, array $crmIds): void\n {\n if (count($crmIds) > 100) {\n throw new \\InvalidArgumentException(\"Batch size cannot exceed 100 {$objectType}\");\n }\n }\n\n private function createBatchConfiguration(string $objectType): array\n {\n $configurations = [\n 'deals' => [\n 'batchReadRequest' => new \\HubSpot\\Client\\Crm\\Deals\\Model\\BatchReadInputSimplePublicObjectId(),\n 'inputClass' => \\HubSpot\\Client\\Crm\\Deals\\Model\\SimplePublicObjectId::class,\n 'api' => $this->getNewInstance()->crm()->deals()->batchApi(),\n ],\n 'companies' => [\n 'batchReadRequest' => new \\HubSpot\\Client\\Crm\\Companies\\Model\\BatchReadInputSimplePublicObjectId(),\n 'inputClass' => \\HubSpot\\Client\\Crm\\Companies\\Model\\SimplePublicObjectId::class,\n 'api' => $this->getNewInstance()->crm()->companies()->batchApi(),\n ],\n 'contacts' => [\n 'batchReadRequest' => new \\HubSpot\\Client\\Crm\\Contacts\\Model\\BatchReadInputSimplePublicObjectId(),\n 'inputClass' => \\HubSpot\\Client\\Crm\\Contacts\\Model\\SimplePublicObjectId::class,\n 'api' => $this->getNewInstance()->crm()->contacts()->batchApi(),\n ],\n ];\n\n if (! isset($configurations[$objectType])) {\n throw new \\InvalidArgumentException(\"Unsupported object type: {$objectType}\");\n }\n\n return $configurations[$objectType];\n }\n\n private function prepareBatchRequest(array $batchConfig, array $crmIds, array $fields): object\n {\n $batchReadRequest = $batchConfig['batchReadRequest'];\n $inputClass = $batchConfig['inputClass'];\n\n $inputs = array_map(function ($crmId) use ($inputClass) {\n $input = new $inputClass();\n $input->setId($crmId);\n\n return $input;\n }, $crmIds);\n\n $batchReadRequest->setInputs($inputs);\n $batchReadRequest->setProperties($fields);\n\n return $batchReadRequest;\n }\n\n private function validateApiResponse($response, string $objectType): void\n {\n if (! $response) {\n throw new CrmException(\"HubSpot API returned null response for {$objectType} batch read\");\n }\n }\n\n private function processApiResults($response): array\n {\n $results = [];\n $responseResults = $response->getResults();\n\n if ($responseResults) {\n foreach ($responseResults as $object) {\n if ($object && $object->getId()) {\n $results[$object->getId()] = [\n 'id' => $object->getId(),\n 'properties' => $object->getProperties() ?: [],\n ];\n }\n }\n }\n\n return $results;\n }\n\n private function logBatchResults(string $objectType, array $crmIds, array $results): void\n {\n $this->log->info(\"[HubSpot] Batch fetched {$objectType}\", [\n 'requested_count' => count($crmIds),\n 'returned_count' => count($results),\n 'crm_ids' => $crmIds,\n ]);\n }\n\n private function handleBatchError(\\Throwable $e, string $objectType, array $crmIds): void\n {\n $errorMessage = $e->getMessage() ?: 'Unknown error';\n $errorTrace = $e->getTraceAsString() ?: 'No trace available';\n\n $this->log->error(\"[HubSpot] Failed to batch fetch {$objectType}\", [\n 'crm_ids' => $crmIds,\n 'error' => $errorMessage,\n 'trace' => $errorTrace,\n ]);\n\n throw new CrmException(\"Failed to batch fetch {$objectType}: \" . $errorMessage);\n }\n\n /**\n * Batch read multiple opportunities by their CRM IDs\n *\n * @param array<string> $crmIds Array of HubSpot deal IDs (max 100)\n * @param array<string> $fields Array of property names to fetch\n *\n * @return array<string, array> Array keyed by CRM ID with opportunity data\n */\n public function getOpportunitiesByIds(array $crmIds, array $fields): array\n {\n return $this->batchReadObjects('deals', $crmIds, $fields);\n }\n\n /**\n * Batch read multiple companies by their CRM IDs\n *\n * @param array<string> $crmIds Array of HubSpot company IDs (max 100)\n * @param array<string> $fields Array of property names to fetch\n *\n * @return array<string, array> Array keyed by CRM ID with company data\n */\n public function getCompaniesByIds(array $crmIds, array $fields): array\n {\n return $this->batchReadObjects('companies', $crmIds, $fields);\n }\n\n /**\n * Batch read multiple contacts by their CRM IDs\n *\n * @param array<string> $crmIds Array of HubSpot contact IDs (max 100)\n * @param array<string> $fields Array of property names to fetch\n *\n * @return array<string, array> Array keyed by CRM ID with contact data\n */\n public function getContactsByIds(array $crmIds, array $fields): array\n {\n return $this->batchReadObjects('contacts', $crmIds, $fields);\n }\n\n /**\n * @throws CompanyApiException\n * @throws CrmException\n */\n public function getAccountById(string $crmId, array $fields): array\n {\n try {\n $company = $this->getNewInstance()->crm()->companies()->basicApi()->getById(\n $crmId,\n implode(',', $fields),\n );\n } catch (CompanyApiException $e) {\n $this->log->info('[Hubspot] Failed to fetch account', [\n 'crm_id' => $crmId,\n 'reason' => $e->getMessage(),\n ]);\n\n throw $e;\n }\n\n if (! $company instanceof CompaniesWithAssociations) {\n throw new CrmException('Account not found');\n }\n\n return [\n 'id' => $company->getId(),\n 'properties' => $company->getProperties(),\n ];\n }\n\n /**\n * @throws ContactApiException\n * @throws CrmException\n */\n public function getContactById(string $crmId, array $fields): array\n {\n try {\n $contact = $this->getNewInstance()->crm()->contacts()->basicApi()->getById(\n $crmId,\n implode(',', $fields)\n );\n } catch (ContactApiException $e) {\n $this->log->info('[Hubspot] Failed to fetch contact', [\n 'crm_id' => $crmId,\n 'reason' => $e->getMessage(),\n ]);\n\n throw $e;\n }\n\n if (! $contact instanceof ContactsWithAssociations) {\n throw new CrmException('Contact not found');\n }\n\n return [\n 'id' => $contact->getId(),\n 'properties' => $contact->getProperties(),\n ];\n }\n\n /**\n * This is email search request that Hubspot offers as GET (more generous quota)\n */\n public function getContactByEmail(string $email, array $fields = []): array\n {\n try {\n $contact = $this->getNewInstance()->crm()->contacts()->basicApi()->getById(\n $email,\n implode(',', $fields),\n null,\n false,\n 'email'\n );\n\n return [\n 'id' => $contact->getId(),\n 'properties' => $contact->getProperties(),\n ];\n } catch (ContactApiException $e) {\n $this->log->info('[Hubspot] Failed to fetch contact', [\n 'email' => $email,\n 'reason' => $e->getMessage(),\n ]);\n\n return [];\n }\n }\n\n /**\n * @throws CrmException\n */\n public function fetchProperty(string $objectType, string $propertyId): Property\n {\n $result = $this->getNewInstance()->crm()->properties()->coreApi()->getByName($objectType, $propertyId);\n\n if (! $result instanceof Property) {\n $this->log->error('[Hubspot] Failed to fetch property', [\n 'object_type' => $objectType,\n 'property_id' => $propertyId,\n 'reason' => $result->getMessage(),\n ]);\n\n throw new CrmException('Failed to fetch property');\n }\n\n return $result;\n }\n\n /**\n * @return array<CrmFieldOption>\n */\n public function fetchPropertyOptions(string $objectType, string $propertyId): array\n {\n /** @var array<CrmFieldOption> */\n return $this->fetchProperty($objectType, $propertyId)->getOptions();\n }\n\n /**\n * @return array<array{id:string, label:string, deleted:bool}>\n */\n public function fetchCallDispositions(): array\n {\n /** @var Response $response */\n $response = $this->getInstance()->engagements()->getCallDispositions();\n\n /**\n * @var array<array{\n * id:string,\n * label:string,\n * deleted: bool\n * }>\n */\n return $response->toArray();\n }\n\n /**\n * @return array<CrmFieldOption>\n */\n public function fetchOpportunityPipelineStages(): array\n {\n $stages = [];\n $apiResponse = $this->getNewInstance()->crm()->pipelines()->pipelinesApi()->getAll('deals');\n\n if ($apiResponse instanceof Error) {\n $this->log->error('[Hubspot] Failed to fetch opportunity pipelines', [\n 'reason' => $apiResponse->getMessage(),\n ]);\n\n return [];\n }\n\n foreach ($apiResponse->getResults() as $pipeline) {\n $pipelineStages = array_map(\n static function (PipelineStage $stage) {\n return [\n 'id' => $stage->getId(),\n 'label' => $stage->getLabel(),\n ];\n },\n $pipeline->getStages()\n );\n\n $stages = array_merge($stages, $pipelineStages);\n }\n\n return $stages;\n }\n\n public function fetchOpportunityPipelines(): array\n {\n $pipelines = [];\n\n try {\n $apiResponse = $this->makeRequest('/crm/v3/pipelines/deals');\n } catch (\\Exception $e) {\n $this->log->info('[Hubspot] Failed to fetch opportunity pipelines', [\n 'reason' => $e->getMessage(),\n ]);\n\n return [];\n }\n\n $response = $apiResponse->toArray();\n\n foreach ($response['results'] as $pipeline) {\n $pipelines[] = [\n 'id' => $pipeline['id'],\n 'label' => $pipeline['label'],\n ];\n }\n\n return $pipelines;\n }\n\n /**\n * @return array<CrmFieldOption>\n */\n public function fetchMeetingOutcomeFieldOptions(Field $field): array\n {\n return $field->getCrmProviderId() === 'meetingOutcome'\n ? $this->fetchMeetingOutcomeTypes()\n : $this->fetchCallActivityTypes();\n }\n\n public function fetchMeetingOutcomeTypes(): array\n {\n return $this->extractMeetingTypeOptions(\n 'https://api.hubapi.com/crm/v3/properties/meeting/hs_meeting_outcome'\n );\n }\n\n public function fetchCallActivityTypes(): array\n {\n return $this->extractMeetingTypeOptions(\n 'https://api.hubapi.com/crm/v3/properties/call/hs_activity_type'\n );\n }\n\n private function extractMeetingTypeOptions(string $endpoint): array\n {\n /** @var Response $response */\n $response = $this->getInstance()\n ->getClient()\n ->request('GET', $endpoint);\n\n /**\n * @var array<array{\n * value: string,\n * label: string,\n * displayOrder: int\n * }> $optionData\n */\n $optionData = $response->toArray()['options'] ?? [];\n\n $options = [];\n foreach ($optionData as $item) {\n $options[] = [\n 'id' => $item['value'],\n 'value' => $item['value'],\n 'label' => $item['label'],\n 'display_order' => $item['displayOrder'],\n ];\n }\n\n return $options;\n }\n\n /**\n * @return array<CrmFieldOption>\n */\n public function fetchDispositionFieldOptions(): array\n {\n $options = [];\n\n $dispositions = $this->fetchCallDispositions();\n\n foreach ($dispositions as $disposition) {\n if ($disposition['deleted'] !== false) {\n continue;\n }\n\n $option['value'] = $disposition['id'];\n $option['id'] = $disposition['id'];\n $option['label'] = $disposition['label'];\n\n $options[] = $option;\n }\n\n return $options;\n }\n\n /**\n * @return array<CrmFieldOption>\n */\n public function fetchOpportunityFieldOptions(Field $field): array\n {\n if ($field->isStageField()) {\n return $this->fetchOpportunityPipelineStages();\n }\n\n if ($field->isPipelineField()) {\n return $this->fetchOpportunityPipelines();\n }\n\n return $this->fetchPropertyOptions('deals', $field->getCrmProviderId());\n }\n\n /**\n * @throws BadRequest\n * @throws HubspotException\n */\n public function makeRequest(string $endpoint, $method = 'GET', $payload = [], ?string $queryString = null)\n {\n $endpoint = self::BASE_URL . $endpoint;\n\n if ($method === 'GET') {\n $response = $this->getInstance()->getClient()?->request(\n method: $method,\n endpoint: $endpoint,\n query_string: $queryString\n );\n } else {\n $response = $this->getInstance()->getClient()->request($method, $endpoint, [\n 'json' => ($payload),\n ]);\n }\n\n $max = $response->getHeaderLine('X-HubSpot-RateLimit-Max'); // \"110\"\n $remaining = $response->getHeaderLine('X-HubSpot-RateLimit-Remaining'); // \"109\"\n $interval = $response->getHeaderLine('X-HubSpot-RateLimit-Interval-Milliseconds'); // \"10000\"\n $body = json_decode((string) $response->getBody(), true);\n\n \\Illuminate\\Support\\Facades\\Log::channel('custom_channel')->info('$max ' . PHP_EOL . print_r($max, true));\n \\Illuminate\\Support\\Facades\\Log::channel('custom_channel')->info('$remaining ' . PHP_EOL . print_r($remaining, true));\n \\Illuminate\\Support\\Facades\\Log::channel('custom_channel')->info('$interval ' . PHP_EOL . print_r($interval, true));\n \\Illuminate\\Support\\Facades\\Log::channel('custom_channel')->info('$body ' . PHP_EOL . print_r($body, true));\n\n return $response;\n }\n\n /**\n * @throws BadRequest\n * @throws HubspotException\n */\n public function createMeeting(array $payload): Response\n {\n $endpoint = '/crm/v3/objects/meetings';\n\n return $this->makeRequest($endpoint, 'POST', $payload);\n }\n\n /**\n * @throws BadRequest\n * @throws HubspotException\n */\n public function updateMeeting(string $meetingId, array $payload): Response\n {\n $endpoint = '/crm/v3/objects/meetings/' . $meetingId;\n\n return $this->makeRequest($endpoint, 'PATCH', $payload);\n }\n\n /**\n * @throws \\Exception\n */\n public function createNote(\n string $body,\n string $ownerId,\n int $timestamp,\n string $objectId,\n NoteObject $noteObject\n ): ?string {\n try {\n $noteInput = new SimplePublicObjectInput([\n 'properties' => [\n 'hs_note_body' => $body,\n 'hubspot_owner_id' => $ownerId,\n 'hs_timestamp' => $timestamp,\n ],\n ]);\n\n // Create note\n $note = $this->getNewInstance()->crm()->objects()->basicApi()->create('note', $noteInput);\n\n $this->getNewInstance()->crm()->objects()->associationsApi()->create(\n 'note',\n $note->getId(),\n $this->getNoteObject($noteObject),\n $objectId,\n $this->getNoteAssociationType($noteObject),\n );\n\n return $note->getId();\n } catch (\\Exception $e) {\n $this->log->error('[Hubspot] Failed to create note', [\n 'objectId' => $objectId,\n 'noteObject' => $noteObject->getObjectType(),\n 'reason' => $e->getMessage(),\n ]);\n\n \\Sentry::captureException($e);\n }\n\n return null;\n }\n\n public function updateEngagement(string $objectId, array $engagement, array $metadata): void\n {\n $this->getInstance()->engagements()->update($objectId, $engagement, $metadata);\n }\n\n public function getEngagementData(string $engagementId): array\n {\n $engagement = $this->getInstance()->engagements()->get($engagementId);\n\n return $engagement->toArray();\n }\n\n public function createEngagement(array $engagement, array $associations, array $metadata): Response\n {\n return $this->getInstance()\n ->engagements()\n ->create($engagement, $associations, $metadata);\n }\n\n public function isUnauthorizedException(\\Exception $e): bool\n {\n // Check for specific HubSpot API exception types first\n if ($e instanceof BadRequest) {\n // BadRequest can contain 401 status codes\n return $e->getCode() === 401;\n }\n\n // Check for HTTP client exceptions with status codes\n if ($e instanceof \\GuzzleHttp\\Exception\\RequestException && $e->hasResponse()) {\n $response = $e->getResponse();\n if ($response !== null) {\n return $response->getStatusCode() === 401;\n }\n }\n\n // Check for Guzzle HTTP exceptions\n if ($e instanceof \\GuzzleHttp\\Exception\\ClientException) {\n return $e->getCode() === 401;\n }\n\n // Fallback to string matching as last resort, but be more specific\n $message = strtolower($e->getMessage());\n\n return str_contains($message, '401 unauthorized') ||\n str_contains($message, 'http 401') ||\n str_contains($message, 'status code 401') ||\n (preg_match('/\\b401\\b/', $message) && str_contains($message, 'unauthorized'));\n }\n\n /**\n * Validates and refreshes the access token if needed before API requests.\n * This ensures long-running processes don't fail due to token expiration.\n *\n * @throws SocialAccountTokenInvalidException\n */\n public function ensureValidToken(): void\n {\n if ($this->oauthAccount === null) {\n return;\n }\n\n $newToken = $this->tokenManager->ensureValidToken($this->oauthAccount);\n if ($newToken !== null) {\n $this->accessToken = $newToken;\n }\n }\n\n public function getConfig()\n {\n return $this->config;\n }\n\n // returns only active (archived=false)\n public function getOwners(): array\n {\n return $this->getNewInstance()->crm()->owners()->getAll();\n }\n\n /**\n * @param bool $archived\n *\n * @return array<Owner>|[]\n */\n public function getOwnersArchived(bool $archived = true): array\n {\n $endpoint = '/crm/v3/owners';\n $queryParams = [\n 'archived' => $archived ? 'true' : 'false',\n ];\n $queryString = http_build_query($queryParams);\n\n $owners = [];\n\n try {\n $response = $this->makeRequest(endpoint: $endpoint, queryString: $queryString);\n $responseData = $response?->toArray();\n\n foreach ($responseData['results'] as $result) {\n try {\n $owners[] = Owner::create($result);\n } catch (Throwable $e) {\n $this->log->error('[HubSpot] Failed to process owner data', [\n 'result' => $result,\n 'error' => $e->getMessage(),\n ]);\n\n continue;\n }\n }\n } catch (Throwable $e) {\n $this->log->error('HubSpot] Failed to fetch owners', [\n 'archived' => $archived,\n 'error' => $e->getMessage(),\n ]);\n\n return [];\n }\n\n return $owners;\n }\n\n public function getMeeting(string $engagementId): ObjectWithAssociations\n {\n return $this->getNewInstance()->crm()->objects()->basicApi()\n ->getById('meeting', $engagementId, null, 'contact,company,deal');\n }\n\n public function deleteEngagement(string $engagementId): void\n {\n $this->getInstance()->engagements()->delete((int) $engagementId);\n }\n\n public function getAssociationsData(array $ids, string $fromObject, string $toObject): array\n {\n $associationData = [];\n $idChunks = array_chunk($ids, self::ASSOCIATIONS_BATCH_SIZE_LIMIT);\n\n foreach ($idChunks as $idChunk) {\n try {\n $batchInput = new \\HubSpot\\Client\\Crm\\Associations\\Model\\BatchInputPublicObjectId();\n $batchInput->setInputs(array_map(function ($id) {\n $publicObjectId = new \\HubSpot\\Client\\Crm\\Associations\\Model\\PublicObjectId();\n $publicObjectId->setId($id);\n\n return $publicObjectId;\n }, $idChunk));\n\n $associatedObjectsData = $this\n ->getNewInstance()\n ->crm()\n ->associations()\n ->batchApi()\n ->read($fromObject, $toObject, $batchInput);\n\n if ($associatedObjectsData instanceof \\HubSpot\\Client\\Crm\\Associations\\Model\\BatchResponsePublicAssociationMulti) {\n foreach ($associatedObjectsData->getResults() as $association) {\n $from = $association->getFrom()->getId();\n $toAssociations = $association->getTo();\n\n if (! empty($toAssociations)) {\n $associationData[$from] = array_map(function ($item) {\n return $item->getId();\n }, $toAssociations);\n }\n }\n }\n } catch (\\Exception $e) {\n $this->log->error('[Hubspot] Failed to fetch associations', [\n 'from_object' => $fromObject,\n 'to_object' => $toObject,\n 'reason' => $e->getMessage(),\n ]);\n }\n }\n\n return $associationData;\n }\n\n /**\n * @throws \\Exception\n */\n private function getNoteAssociationType(NoteObject $noteObject): string\n {\n return match($noteObject) {\n NoteObject::Opportunity => 'note_to_deal',\n NoteObject::Lead, NoteObject::Contact => 'note_to_contact', // or 'note_to_lead' if your portal supports it\n NoteObject::Account => 'note_to_company',\n NoteObject::Call, NoteObject::Event => throw new \\Exception('Not supported'),\n };\n }\n\n /**\n * @throws \\Exception\n */\n private function getNoteObject(NoteObject $noteObject): string\n {\n return match($noteObject) {\n NoteObject::Opportunity => 'deal',\n NoteObject::Lead, NoteObject::Contact => 'contact',\n NoteObject::Account => 'company',\n NoteObject::Call, NoteObject::Event => throw new \\Exception('Not supported'),\n };\n }\n\n public function addAssociations(string $objectType, string $associationType, array $payload): Response\n {\n $endpoint = \"/crm/v4/associations/$objectType/$associationType/batch/create\";\n\n return $this->makeRequest($endpoint, 'POST', $payload);\n }\n\n public function removeAssociations(string $objectType, string $associationType, array $payload): Response\n {\n $endpoint = \"/crm/v4/associations/$objectType/$associationType/batch/archive\";\n\n return $this->makeRequest($endpoint, 'POST', $payload);\n }\n}","depth":4,"on_screen":true,"value":"<?php\n\ndeclare(strict_types=1);\n\nnamespace Jiminny\\Services\\Crm\\Hubspot;\n\nuse HubSpot\\Client\\Crm\\Deals\\ApiException as DealApiException;\nuse HubSpot\\Client\\Crm\\Contacts\\ApiException as ContactApiException;\nuse HubSpot\\Client\\Crm\\Companies\\ApiException as CompanyApiException;\nuse HubSpot\\Client\\Crm\\Contacts\\Model\\SimplePublicObjectWithAssociations as ContactsWithAssociations;\nuse HubSpot\\Client\\Crm\\Companies\\Model\\SimplePublicObjectWithAssociations as CompaniesWithAssociations;\nuse HubSpot\\Client\\Crm\\Deals\\Model\\SimplePublicObjectWithAssociations as DealWithAssociations;\nuse HubSpot\\Client\\Crm\\Objects\\Model\\SimplePublicObjectInput;\nuse HubSpot\\Client\\Crm\\Objects\\Model\\SimplePublicObjectWithAssociations as ObjectWithAssociations;\nuse HubSpot\\Client\\Crm\\Pipelines\\Model\\Error;\nuse HubSpot\\Client\\Crm\\Pipelines\\Model\\PipelineStage;\nuse HubSpot\\Client\\Crm\\Properties\\Model\\Property;\nuse HubSpot\\Discovery\\Discovery;\nuse Jiminny\\Component\\Utility\\Service\\ProviderRateLimiter;\nuse Jiminny\\Exceptions\\CrmException;\nuse Jiminny\\Exceptions\\RateLimitException;\nuse Jiminny\\Exceptions\\SocialAccountTokenInvalidException;\nuse Jiminny\\Jobs\\Crm\\NoteObject;\nuse Jiminny\\Models\\Crm\\Field;\nuse Jiminny\\Services\\Crm\\BaseClient;\nuse Jiminny\\Services\\Crm\\Hubspot\\DTO\\Response\\Owner;\nuse Jiminny\\Services\\SocialAccountService;\nuse SevenShores\\Hubspot\\Exceptions\\BadRequest;\nuse SevenShores\\Hubspot\\Exceptions\\HubspotException;\nuse SevenShores\\Hubspot\\Factory;\nuse SevenShores\\Hubspot\\Http\\Response;\nuse Jiminny\\Services\\Crm\\Hubspot\\Pagination\\HubspotPaginationService;\nuse Throwable;\n\n/**\n * @phpstan-type CrmFieldOption array{id:string, label:string, value?:string}\n */\nclass Client extends BaseClient implements HubspotClientInterface\n{\n public const string MIN_API_VERSION = '2';\n\n public const string BASE_URL = 'https://api.hubapi.com';\n\n public const int ASSOCIATIONS_BATCH_SIZE_LIMIT = 1000;\n\n private HubspotPaginationService $paginationService;\n private HubspotTokenManager $tokenManager;\n private ProviderRateLimiter $rateLimiter;\n\n public function __construct(\n SocialAccountService $socialAccountService,\n HubspotPaginationService $paginationService,\n HubspotTokenManager $tokenManager,\n ProviderRateLimiter $rateLimiter,\n ) {\n parent::__construct($socialAccountService);\n $this->paginationService = $paginationService;\n $this->tokenManager = $tokenManager;\n $this->rateLimiter = $rateLimiter;\n\n $this->setBaseUrl(self::BASE_URL);\n $this->setVersion(self::MIN_API_VERSION);\n }\n\n /**\n * Single entry point for every HubSpot API call. Enforces the per-portal\n * rate limit configured in the rate_limits table (morphed to the current\n * Configuration) and reacts to a real 429 from HubSpot by translating it\n * into a RateLimitException carrying Retry-After.\n *\n * Wrap any outbound HubSpot call (SDK or raw HTTP) like:\n *\n * $this->executeRequest(fn () => $this->getNewInstance()->crm()->...);\n *\n * @template T\n * @param callable(): T $apiCall\n * @return T\n *\n * @throws RateLimitException\n */\n private function executeRequest(callable $apiCall)\n {\n if (! $this->rateLimiter->canMakeRequest($this->config)) {\n $retryAfter = $this->rateLimiter->requestAvailableIn($this->config);\n\n $this->log->warning('[Hubspot] Rate limit exceeded, deferring request', [\n 'team_id' => $this->config->team_id,\n 'config_id' => $this->config->getId(),\n 'retry_after' => $retryAfter,\n ]);\n\n throw new RateLimitException(\n 'Hubspot rate limit reached for configuration ' . $this->config->getId(),\n $retryAfter,\n );\n }\n\n $this->rateLimiter->incrementRequestCount($this->config);\n\n try {\n return $apiCall();\n } catch (Throwable $e) {\n if ($this->isHubspotRateLimit($e)) {\n $retryAfter = $this->parseRetryAfter($e);\n\n $this->log->warning('[Hubspot] Received 429 from API', [\n 'team_id' => $this->config->team_id,\n 'config_id' => $this->config->getId(),\n 'retry_after' => $retryAfter,\n 'reason' => $e->getMessage(),\n ]);\n\n throw new RateLimitException('Hubspot returned 429', $retryAfter, $e);\n }\n\n throw $e;\n }\n }\n\n private function isHubspotRateLimit(Throwable $e): bool\n {\n return method_exists($e, 'getCode') && (int) $e->getCode() === 429;\n }\n\n private function parseRetryAfter(Throwable $e): int\n {\n if (method_exists($e, 'getResponseHeaders')) {\n $headers = $e->getResponseHeaders() ?: [];\n $value = $headers['Retry-After'] ?? $headers['retry-after'] ?? null;\n if (is_array($value)) {\n $value = $value[0] ?? null;\n }\n if (is_numeric($value)) {\n return (int) $value;\n }\n }\n\n return 10;\n }\n\n public function getMinimumApiVersion(): string\n {\n return self::MIN_API_VERSION;\n }\n\n public function getInstance(): Factory\n {\n return new Factory([\n 'key' => $this->accessToken,\n 'oauth2' => true,\n 'base_url' => $this->baseUrl,\n ]);\n }\n\n public function getNewInstance(): Discovery\n {\n return \\HubSpot\\Factory::createWithAccessToken($this->accessToken);\n }\n\n /**\n * Secondly and daily limits for Hubspot API\n *\n * Product Tier: Free & Starter | Professional & Enterprise | API add-on (any tier)\n * Burst: 100/10 seconds | 150/10 seconds | 200/10 seconds\n * Daily: 250,000 | 500,000 | 1,000,000\n *\n * Official documentation states: The search endpoints are rate limited to five requests per second.\n * Since with 5 RPS were still hitting secondly rate limits we lowered it to 4\n */\n public function getPaginatedData(array $payload, string $type, int $offset = 0): array\n {\n $total = 0;\n $lastId = null;\n $rows = [];\n foreach ($this->getPaginatedDataGenerator($payload, $type, $offset, $total, $lastId) as $row) {\n $rows[] = $row;\n }\n\n return ['results' => $rows, 'total' => $total, 'last_record' => $lastId];\n }\n\n /**\n * @throws HubspotException\n * @throws SocialAccountTokenInvalidException\n * @throws BadRequest\n */\n public function getPaginatedDataGenerator(\n array $payload,\n string $type,\n int $offset = 0,\n int &$total = 0,\n ?string &$lastRecordId = null\n ): \\Generator {\n return $this->paginationService->getPaginatedDataGenerator(\n $this,\n $payload,\n $type,\n $offset,\n $total,\n $lastRecordId\n );\n }\n\n /**\n * @throws DealApiException\n * @throws CrmException\n */\n public function getOpportunityById(string $crmId, array $fields): array\n {\n try {\n $deal = $this->executeRequest(fn () => $this->getNewInstance()->crm()->deals()->basicApi()->getById(\n $crmId,\n implode(',', $fields),\n 'companies,contacts'\n ));\n } catch (DealApiException $e) {\n $this->log->info('[Hubspot] Failed to fetch opportunity', [\n 'crm_id' => $crmId,\n 'reason' => $e->getMessage(),\n ]);\n\n throw $e;\n }\n\n if (! $deal instanceof DealWithAssociations) {\n throw new CrmException('Deal not found');\n }\n\n return [\n 'id' => $deal->getId(),\n 'properties' => $deal->getProperties(),\n 'associations' => $deal->getAssociations(),\n ];\n }\n\n /**\n * Generic batch read method for HubSpot objects\n *\n * @param string $objectType The object type ('deals', 'companies', 'contacts')\n * @param array<string> $crmIds Array of HubSpot object IDs (max 100)\n * @param array<string> $fields Array of property names to fetch\n *\n * @return array<string, array> Array keyed by CRM ID with object data\n */\n private function batchReadObjects(string $objectType, array $crmIds, array $fields): array\n {\n if (empty($crmIds)) {\n return [];\n }\n\n $this->validateBatchSize($objectType, $crmIds);\n $this->ensureValidToken();\n\n try {\n $batchConfig = $this->createBatchConfiguration($objectType);\n $batchReadRequest = $this->prepareBatchRequest($batchConfig, $crmIds, $fields);\n $response = $batchConfig['api']->read($batchReadRequest);\n\n $this->validateApiResponse($response, $objectType);\n\n $results = $this->processApiResults($response);\n $this->logBatchResults($objectType, $crmIds, $results);\n\n return $results;\n } catch (\\Throwable $e) {\n $this->handleBatchError($e, $objectType, $crmIds);\n }\n }\n\n private function validateBatchSize(string $objectType, array $crmIds): void\n {\n if (count($crmIds) > 100) {\n throw new \\InvalidArgumentException(\"Batch size cannot exceed 100 {$objectType}\");\n }\n }\n\n private function createBatchConfiguration(string $objectType): array\n {\n $configurations = [\n 'deals' => [\n 'batchReadRequest' => new \\HubSpot\\Client\\Crm\\Deals\\Model\\BatchReadInputSimplePublicObjectId(),\n 'inputClass' => \\HubSpot\\Client\\Crm\\Deals\\Model\\SimplePublicObjectId::class,\n 'api' => $this->getNewInstance()->crm()->deals()->batchApi(),\n ],\n 'companies' => [\n 'batchReadRequest' => new \\HubSpot\\Client\\Crm\\Companies\\Model\\BatchReadInputSimplePublicObjectId(),\n 'inputClass' => \\HubSpot\\Client\\Crm\\Companies\\Model\\SimplePublicObjectId::class,\n 'api' => $this->getNewInstance()->crm()->companies()->batchApi(),\n ],\n 'contacts' => [\n 'batchReadRequest' => new \\HubSpot\\Client\\Crm\\Contacts\\Model\\BatchReadInputSimplePublicObjectId(),\n 'inputClass' => \\HubSpot\\Client\\Crm\\Contacts\\Model\\SimplePublicObjectId::class,\n 'api' => $this->getNewInstance()->crm()->contacts()->batchApi(),\n ],\n ];\n\n if (! isset($configurations[$objectType])) {\n throw new \\InvalidArgumentException(\"Unsupported object type: {$objectType}\");\n }\n\n return $configurations[$objectType];\n }\n\n private function prepareBatchRequest(array $batchConfig, array $crmIds, array $fields): object\n {\n $batchReadRequest = $batchConfig['batchReadRequest'];\n $inputClass = $batchConfig['inputClass'];\n\n $inputs = array_map(function ($crmId) use ($inputClass) {\n $input = new $inputClass();\n $input->setId($crmId);\n\n return $input;\n }, $crmIds);\n\n $batchReadRequest->setInputs($inputs);\n $batchReadRequest->setProperties($fields);\n\n return $batchReadRequest;\n }\n\n private function validateApiResponse($response, string $objectType): void\n {\n if (! $response) {\n throw new CrmException(\"HubSpot API returned null response for {$objectType} batch read\");\n }\n }\n\n private function processApiResults($response): array\n {\n $results = [];\n $responseResults = $response->getResults();\n\n if ($responseResults) {\n foreach ($responseResults as $object) {\n if ($object && $object->getId()) {\n $results[$object->getId()] = [\n 'id' => $object->getId(),\n 'properties' => $object->getProperties() ?: [],\n ];\n }\n }\n }\n\n return $results;\n }\n\n private function logBatchResults(string $objectType, array $crmIds, array $results): void\n {\n $this->log->info(\"[HubSpot] Batch fetched {$objectType}\", [\n 'requested_count' => count($crmIds),\n 'returned_count' => count($results),\n 'crm_ids' => $crmIds,\n ]);\n }\n\n private function handleBatchError(\\Throwable $e, string $objectType, array $crmIds): void\n {\n $errorMessage = $e->getMessage() ?: 'Unknown error';\n $errorTrace = $e->getTraceAsString() ?: 'No trace available';\n\n $this->log->error(\"[HubSpot] Failed to batch fetch {$objectType}\", [\n 'crm_ids' => $crmIds,\n 'error' => $errorMessage,\n 'trace' => $errorTrace,\n ]);\n\n throw new CrmException(\"Failed to batch fetch {$objectType}: \" . $errorMessage);\n }\n\n /**\n * Batch read multiple opportunities by their CRM IDs\n *\n * @param array<string> $crmIds Array of HubSpot deal IDs (max 100)\n * @param array<string> $fields Array of property names to fetch\n *\n * @return array<string, array> Array keyed by CRM ID with opportunity data\n */\n public function getOpportunitiesByIds(array $crmIds, array $fields): array\n {\n return $this->batchReadObjects('deals', $crmIds, $fields);\n }\n\n /**\n * Batch read multiple companies by their CRM IDs\n *\n * @param array<string> $crmIds Array of HubSpot company IDs (max 100)\n * @param array<string> $fields Array of property names to fetch\n *\n * @return array<string, array> Array keyed by CRM ID with company data\n */\n public function getCompaniesByIds(array $crmIds, array $fields): array\n {\n return $this->batchReadObjects('companies', $crmIds, $fields);\n }\n\n /**\n * Batch read multiple contacts by their CRM IDs\n *\n * @param array<string> $crmIds Array of HubSpot contact IDs (max 100)\n * @param array<string> $fields Array of property names to fetch\n *\n * @return array<string, array> Array keyed by CRM ID with contact data\n */\n public function getContactsByIds(array $crmIds, array $fields): array\n {\n return $this->batchReadObjects('contacts', $crmIds, $fields);\n }\n\n /**\n * @throws CompanyApiException\n * @throws CrmException\n */\n public function getAccountById(string $crmId, array $fields): array\n {\n try {\n $company = $this->getNewInstance()->crm()->companies()->basicApi()->getById(\n $crmId,\n implode(',', $fields),\n );\n } catch (CompanyApiException $e) {\n $this->log->info('[Hubspot] Failed to fetch account', [\n 'crm_id' => $crmId,\n 'reason' => $e->getMessage(),\n ]);\n\n throw $e;\n }\n\n if (! $company instanceof CompaniesWithAssociations) {\n throw new CrmException('Account not found');\n }\n\n return [\n 'id' => $company->getId(),\n 'properties' => $company->getProperties(),\n ];\n }\n\n /**\n * @throws ContactApiException\n * @throws CrmException\n */\n public function getContactById(string $crmId, array $fields): array\n {\n try {\n $contact = $this->getNewInstance()->crm()->contacts()->basicApi()->getById(\n $crmId,\n implode(',', $fields)\n );\n } catch (ContactApiException $e) {\n $this->log->info('[Hubspot] Failed to fetch contact', [\n 'crm_id' => $crmId,\n 'reason' => $e->getMessage(),\n ]);\n\n throw $e;\n }\n\n if (! $contact instanceof ContactsWithAssociations) {\n throw new CrmException('Contact not found');\n }\n\n return [\n 'id' => $contact->getId(),\n 'properties' => $contact->getProperties(),\n ];\n }\n\n /**\n * This is email search request that Hubspot offers as GET (more generous quota)\n */\n public function getContactByEmail(string $email, array $fields = []): array\n {\n try {\n $contact = $this->getNewInstance()->crm()->contacts()->basicApi()->getById(\n $email,\n implode(',', $fields),\n null,\n false,\n 'email'\n );\n\n return [\n 'id' => $contact->getId(),\n 'properties' => $contact->getProperties(),\n ];\n } catch (ContactApiException $e) {\n $this->log->info('[Hubspot] Failed to fetch contact', [\n 'email' => $email,\n 'reason' => $e->getMessage(),\n ]);\n\n return [];\n }\n }\n\n /**\n * @throws CrmException\n */\n public function fetchProperty(string $objectType, string $propertyId): Property\n {\n $result = $this->getNewInstance()->crm()->properties()->coreApi()->getByName($objectType, $propertyId);\n\n if (! $result instanceof Property) {\n $this->log->error('[Hubspot] Failed to fetch property', [\n 'object_type' => $objectType,\n 'property_id' => $propertyId,\n 'reason' => $result->getMessage(),\n ]);\n\n throw new CrmException('Failed to fetch property');\n }\n\n return $result;\n }\n\n /**\n * @return array<CrmFieldOption>\n */\n public function fetchPropertyOptions(string $objectType, string $propertyId): array\n {\n /** @var array<CrmFieldOption> */\n return $this->fetchProperty($objectType, $propertyId)->getOptions();\n }\n\n /**\n * @return array<array{id:string, label:string, deleted:bool}>\n */\n public function fetchCallDispositions(): array\n {\n /** @var Response $response */\n $response = $this->getInstance()->engagements()->getCallDispositions();\n\n /**\n * @var array<array{\n * id:string,\n * label:string,\n * deleted: bool\n * }>\n */\n return $response->toArray();\n }\n\n /**\n * @return array<CrmFieldOption>\n */\n public function fetchOpportunityPipelineStages(): array\n {\n $stages = [];\n $apiResponse = $this->getNewInstance()->crm()->pipelines()->pipelinesApi()->getAll('deals');\n\n if ($apiResponse instanceof Error) {\n $this->log->error('[Hubspot] Failed to fetch opportunity pipelines', [\n 'reason' => $apiResponse->getMessage(),\n ]);\n\n return [];\n }\n\n foreach ($apiResponse->getResults() as $pipeline) {\n $pipelineStages = array_map(\n static function (PipelineStage $stage) {\n return [\n 'id' => $stage->getId(),\n 'label' => $stage->getLabel(),\n ];\n },\n $pipeline->getStages()\n );\n\n $stages = array_merge($stages, $pipelineStages);\n }\n\n return $stages;\n }\n\n public function fetchOpportunityPipelines(): array\n {\n $pipelines = [];\n\n try {\n $apiResponse = $this->makeRequest('/crm/v3/pipelines/deals');\n } catch (\\Exception $e) {\n $this->log->info('[Hubspot] Failed to fetch opportunity pipelines', [\n 'reason' => $e->getMessage(),\n ]);\n\n return [];\n }\n\n $response = $apiResponse->toArray();\n\n foreach ($response['results'] as $pipeline) {\n $pipelines[] = [\n 'id' => $pipeline['id'],\n 'label' => $pipeline['label'],\n ];\n }\n\n return $pipelines;\n }\n\n /**\n * @return array<CrmFieldOption>\n */\n public function fetchMeetingOutcomeFieldOptions(Field $field): array\n {\n return $field->getCrmProviderId() === 'meetingOutcome'\n ? $this->fetchMeetingOutcomeTypes()\n : $this->fetchCallActivityTypes();\n }\n\n public function fetchMeetingOutcomeTypes(): array\n {\n return $this->extractMeetingTypeOptions(\n 'https://api.hubapi.com/crm/v3/properties/meeting/hs_meeting_outcome'\n );\n }\n\n public function fetchCallActivityTypes(): array\n {\n return $this->extractMeetingTypeOptions(\n 'https://api.hubapi.com/crm/v3/properties/call/hs_activity_type'\n );\n }\n\n private function extractMeetingTypeOptions(string $endpoint): array\n {\n /** @var Response $response */\n $response = $this->getInstance()\n ->getClient()\n ->request('GET', $endpoint);\n\n /**\n * @var array<array{\n * value: string,\n * label: string,\n * displayOrder: int\n * }> $optionData\n */\n $optionData = $response->toArray()['options'] ?? [];\n\n $options = [];\n foreach ($optionData as $item) {\n $options[] = [\n 'id' => $item['value'],\n 'value' => $item['value'],\n 'label' => $item['label'],\n 'display_order' => $item['displayOrder'],\n ];\n }\n\n return $options;\n }\n\n /**\n * @return array<CrmFieldOption>\n */\n public function fetchDispositionFieldOptions(): array\n {\n $options = [];\n\n $dispositions = $this->fetchCallDispositions();\n\n foreach ($dispositions as $disposition) {\n if ($disposition['deleted'] !== false) {\n continue;\n }\n\n $option['value'] = $disposition['id'];\n $option['id'] = $disposition['id'];\n $option['label'] = $disposition['label'];\n\n $options[] = $option;\n }\n\n return $options;\n }\n\n /**\n * @return array<CrmFieldOption>\n */\n public function fetchOpportunityFieldOptions(Field $field): array\n {\n if ($field->isStageField()) {\n return $this->fetchOpportunityPipelineStages();\n }\n\n if ($field->isPipelineField()) {\n return $this->fetchOpportunityPipelines();\n }\n\n return $this->fetchPropertyOptions('deals', $field->getCrmProviderId());\n }\n\n /**\n * @throws BadRequest\n * @throws HubspotException\n */\n public function makeRequest(string $endpoint, $method = 'GET', $payload = [], ?string $queryString = null)\n {\n $endpoint = self::BASE_URL . $endpoint;\n\n if ($method === 'GET') {\n $response = $this->getInstance()->getClient()?->request(\n method: $method,\n endpoint: $endpoint,\n query_string: $queryString\n );\n } else {\n $response = $this->getInstance()->getClient()->request($method, $endpoint, [\n 'json' => ($payload),\n ]);\n }\n\n $max = $response->getHeaderLine('X-HubSpot-RateLimit-Max'); // \"110\"\n $remaining = $response->getHeaderLine('X-HubSpot-RateLimit-Remaining'); // \"109\"\n $interval = $response->getHeaderLine('X-HubSpot-RateLimit-Interval-Milliseconds'); // \"10000\"\n $body = json_decode((string) $response->getBody(), true);\n\n \\Illuminate\\Support\\Facades\\Log::channel('custom_channel')->info('$max ' . PHP_EOL . print_r($max, true));\n \\Illuminate\\Support\\Facades\\Log::channel('custom_channel')->info('$remaining ' . PHP_EOL . print_r($remaining, true));\n \\Illuminate\\Support\\Facades\\Log::channel('custom_channel')->info('$interval ' . PHP_EOL . print_r($interval, true));\n \\Illuminate\\Support\\Facades\\Log::channel('custom_channel')->info('$body ' . PHP_EOL . print_r($body, true));\n\n return $response;\n }\n\n /**\n * @throws BadRequest\n * @throws HubspotException\n */\n public function createMeeting(array $payload): Response\n {\n $endpoint = '/crm/v3/objects/meetings';\n\n return $this->makeRequest($endpoint, 'POST', $payload);\n }\n\n /**\n * @throws BadRequest\n * @throws HubspotException\n */\n public function updateMeeting(string $meetingId, array $payload): Response\n {\n $endpoint = '/crm/v3/objects/meetings/' . $meetingId;\n\n return $this->makeRequest($endpoint, 'PATCH', $payload);\n }\n\n /**\n * @throws \\Exception\n */\n public function createNote(\n string $body,\n string $ownerId,\n int $timestamp,\n string $objectId,\n NoteObject $noteObject\n ): ?string {\n try {\n $noteInput = new SimplePublicObjectInput([\n 'properties' => [\n 'hs_note_body' => $body,\n 'hubspot_owner_id' => $ownerId,\n 'hs_timestamp' => $timestamp,\n ],\n ]);\n\n // Create note\n $note = $this->getNewInstance()->crm()->objects()->basicApi()->create('note', $noteInput);\n\n $this->getNewInstance()->crm()->objects()->associationsApi()->create(\n 'note',\n $note->getId(),\n $this->getNoteObject($noteObject),\n $objectId,\n $this->getNoteAssociationType($noteObject),\n );\n\n return $note->getId();\n } catch (\\Exception $e) {\n $this->log->error('[Hubspot] Failed to create note', [\n 'objectId' => $objectId,\n 'noteObject' => $noteObject->getObjectType(),\n 'reason' => $e->getMessage(),\n ]);\n\n \\Sentry::captureException($e);\n }\n\n return null;\n }\n\n public function updateEngagement(string $objectId, array $engagement, array $metadata): void\n {\n $this->getInstance()->engagements()->update($objectId, $engagement, $metadata);\n }\n\n public function getEngagementData(string $engagementId): array\n {\n $engagement = $this->getInstance()->engagements()->get($engagementId);\n\n return $engagement->toArray();\n }\n\n public function createEngagement(array $engagement, array $associations, array $metadata): Response\n {\n return $this->getInstance()\n ->engagements()\n ->create($engagement, $associations, $metadata);\n }\n\n public function isUnauthorizedException(\\Exception $e): bool\n {\n // Check for specific HubSpot API exception types first\n if ($e instanceof BadRequest) {\n // BadRequest can contain 401 status codes\n return $e->getCode() === 401;\n }\n\n // Check for HTTP client exceptions with status codes\n if ($e instanceof \\GuzzleHttp\\Exception\\RequestException && $e->hasResponse()) {\n $response = $e->getResponse();\n if ($response !== null) {\n return $response->getStatusCode() === 401;\n }\n }\n\n // Check for Guzzle HTTP exceptions\n if ($e instanceof \\GuzzleHttp\\Exception\\ClientException) {\n return $e->getCode() === 401;\n }\n\n // Fallback to string matching as last resort, but be more specific\n $message = strtolower($e->getMessage());\n\n return str_contains($message, '401 unauthorized') ||\n str_contains($message, 'http 401') ||\n str_contains($message, 'status code 401') ||\n (preg_match('/\\b401\\b/', $message) && str_contains($message, 'unauthorized'));\n }\n\n /**\n * Validates and refreshes the access token if needed before API requests.\n * This ensures long-running processes don't fail due to token expiration.\n *\n * @throws SocialAccountTokenInvalidException\n */\n public function ensureValidToken(): void\n {\n if ($this->oauthAccount === null) {\n return;\n }\n\n $newToken = $this->tokenManager->ensureValidToken($this->oauthAccount);\n if ($newToken !== null) {\n $this->accessToken = $newToken;\n }\n }\n\n public function getConfig()\n {\n return $this->config;\n }\n\n // returns only active (archived=false)\n public function getOwners(): array\n {\n return $this->getNewInstance()->crm()->owners()->getAll();\n }\n\n /**\n * @param bool $archived\n *\n * @return array<Owner>|[]\n */\n public function getOwnersArchived(bool $archived = true): array\n {\n $endpoint = '/crm/v3/owners';\n $queryParams = [\n 'archived' => $archived ? 'true' : 'false',\n ];\n $queryString = http_build_query($queryParams);\n\n $owners = [];\n\n try {\n $response = $this->makeRequest(endpoint: $endpoint, queryString: $queryString);\n $responseData = $response?->toArray();\n\n foreach ($responseData['results'] as $result) {\n try {\n $owners[] = Owner::create($result);\n } catch (Throwable $e) {\n $this->log->error('[HubSpot] Failed to process owner data', [\n 'result' => $result,\n 'error' => $e->getMessage(),\n ]);\n\n continue;\n }\n }\n } catch (Throwable $e) {\n $this->log->error('HubSpot] Failed to fetch owners', [\n 'archived' => $archived,\n 'error' => $e->getMessage(),\n ]);\n\n return [];\n }\n\n return $owners;\n }\n\n public function getMeeting(string $engagementId): ObjectWithAssociations\n {\n return $this->getNewInstance()->crm()->objects()->basicApi()\n ->getById('meeting', $engagementId, null, 'contact,company,deal');\n }\n\n public function deleteEngagement(string $engagementId): void\n {\n $this->getInstance()->engagements()->delete((int) $engagementId);\n }\n\n public function getAssociationsData(array $ids, string $fromObject, string $toObject): array\n {\n $associationData = [];\n $idChunks = array_chunk($ids, self::ASSOCIATIONS_BATCH_SIZE_LIMIT);\n\n foreach ($idChunks as $idChunk) {\n try {\n $batchInput = new \\HubSpot\\Client\\Crm\\Associations\\Model\\BatchInputPublicObjectId();\n $batchInput->setInputs(array_map(function ($id) {\n $publicObjectId = new \\HubSpot\\Client\\Crm\\Associations\\Model\\PublicObjectId();\n $publicObjectId->setId($id);\n\n return $publicObjectId;\n }, $idChunk));\n\n $associatedObjectsData = $this\n ->getNewInstance()\n ->crm()\n ->associations()\n ->batchApi()\n ->read($fromObject, $toObject, $batchInput);\n\n if ($associatedObjectsData instanceof \\HubSpot\\Client\\Crm\\Associations\\Model\\BatchResponsePublicAssociationMulti) {\n foreach ($associatedObjectsData->getResults() as $association) {\n $from = $association->getFrom()->getId();\n $toAssociations = $association->getTo();\n\n if (! empty($toAssociations)) {\n $associationData[$from] = array_map(function ($item) {\n return $item->getId();\n }, $toAssociations);\n }\n }\n }\n } catch (\\Exception $e) {\n $this->log->error('[Hubspot] Failed to fetch associations', [\n 'from_object' => $fromObject,\n 'to_object' => $toObject,\n 'reason' => $e->getMessage(),\n ]);\n }\n }\n\n return $associationData;\n }\n\n /**\n * @throws \\Exception\n */\n private function getNoteAssociationType(NoteObject $noteObject): string\n {\n return match($noteObject) {\n NoteObject::Opportunity => 'note_to_deal',\n NoteObject::Lead, NoteObject::Contact => 'note_to_contact', // or 'note_to_lead' if your portal supports it\n NoteObject::Account => 'note_to_company',\n NoteObject::Call, NoteObject::Event => throw new \\Exception('Not supported'),\n };\n }\n\n /**\n * @throws \\Exception\n */\n private function getNoteObject(NoteObject $noteObject): string\n {\n return match($noteObject) {\n NoteObject::Opportunity => 'deal',\n NoteObject::Lead, NoteObject::Contact => 'contact',\n NoteObject::Account => 'company',\n NoteObject::Call, NoteObject::Event => throw new \\Exception('Not supported'),\n };\n }\n\n public function addAssociations(string $objectType, string $associationType, array $payload): Response\n {\n $endpoint = \"/crm/v4/associations/$objectType/$associationType/batch/create\";\n\n return $this->makeRequest($endpoint, 'POST', $payload);\n }\n\n public function removeAssociations(string $objectType, string $associationType, array $payload): Response\n {\n $endpoint = \"/crm/v4/associations/$objectType/$associationType/batch/archive\";\n\n return $this->makeRequest($endpoint, 'POST', $payload);\n }\n}","role_description":"text entry area","is_enabled":true,"is_focused":true,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"Project","depth":3,"on_screen":false,"role_description":"text"},{"role":"AXButton","text":"Project","depth":3,"bounds":{"left":0.011968086,"top":0.047885075,"width":0.024268618,"height":0.024740623},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"New File or Directory…","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.008643617,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Expand Selected","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.008643617,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Collapse All","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.008643617,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Options","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.008643617,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Hide","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.008643617,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false}]...
|
-188086479456983350
|
6378757184196315236
|
click
|
accessibility
|
NULL
|
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
Editor for custom.log
Sync Changes
Hide This Notification
Code changed:
Hide
2
60
Previous Highlighted Error
Next Highlighted Error
<?php
declare(strict_types=1);
namespace Jiminny\Services\Crm\Hubspot;
use HubSpot\Client\Crm\Deals\ApiException as DealApiException;
use HubSpot\Client\Crm\Contacts\ApiException as ContactApiException;
use HubSpot\Client\Crm\Companies\ApiException as CompanyApiException;
use HubSpot\Client\Crm\Contacts\Model\SimplePublicObjectWithAssociations as ContactsWithAssociations;
use HubSpot\Client\Crm\Companies\Model\SimplePublicObjectWithAssociations as CompaniesWithAssociations;
use HubSpot\Client\Crm\Deals\Model\SimplePublicObjectWithAssociations as DealWithAssociations;
use HubSpot\Client\Crm\Objects\Model\SimplePublicObjectInput;
use HubSpot\Client\Crm\Objects\Model\SimplePublicObjectWithAssociations as ObjectWithAssociations;
use HubSpot\Client\Crm\Pipelines\Model\Error;
use HubSpot\Client\Crm\Pipelines\Model\PipelineStage;
use HubSpot\Client\Crm\Properties\Model\Property;
use HubSpot\Discovery\Discovery;
use Jiminny\Component\Utility\Service\ProviderRateLimiter;
use Jiminny\Exceptions\CrmException;
use Jiminny\Exceptions\RateLimitException;
use Jiminny\Exceptions\SocialAccountTokenInvalidException;
use Jiminny\Jobs\Crm\NoteObject;
use Jiminny\Models\Crm\Field;
use Jiminny\Services\Crm\BaseClient;
use Jiminny\Services\Crm\Hubspot\DTO\Response\Owner;
use Jiminny\Services\SocialAccountService;
use SevenShores\Hubspot\Exceptions\BadRequest;
use SevenShores\Hubspot\Exceptions\HubspotException;
use SevenShores\Hubspot\Factory;
use SevenShores\Hubspot\Http\Response;
use Jiminny\Services\Crm\Hubspot\Pagination\HubspotPaginationService;
use Throwable;
/**
* @phpstan-type CrmFieldOption array{id:string, label:string, value?:string}
*/
class Client extends BaseClient implements HubspotClientInterface
{
public const string MIN_API_VERSION = '2';
public const string BASE_URL = '[URL_WITH_CREDENTIALS] T
* @param callable(): T $apiCall
* @return T
*
* @throws RateLimitException
*/
private function executeRequest(callable $apiCall)
{
if (! $this->rateLimiter->canMakeRequest($this->config)) {
$retryAfter = $this->rateLimiter->requestAvailableIn($this->config);
$this->log->warning('[Hubspot] Rate limit exceeded, deferring request', [
'team_id' => $this->config->team_id,
'config_id' => $this->config->getId(),
'retry_after' => $retryAfter,
]);
throw new RateLimitException(
'Hubspot rate limit reached for configuration ' . $this->config->getId(),
$retryAfter,
);
}
$this->rateLimiter->incrementRequestCount($this->config);
try {
return $apiCall();
} catch (Throwable $e) {
if ($this->isHubspotRateLimit($e)) {
$retryAfter = $this->parseRetryAfter($e);
$this->log->warning('[Hubspot] Received 429 from API', [
'team_id' => $this->config->team_id,
'config_id' => $this->config->getId(),
'retry_after' => $retryAfter,
'reason' => $e->getMessage(),
]);
throw new RateLimitException('Hubspot returned 429', $retryAfter, $e);
}
throw $e;
}
}
private function isHubspotRateLimit(Throwable $e): bool
{
return method_exists($e, 'getCode') && (int) $e->getCode() === 429;
}
private function parseRetryAfter(Throwable $e): int
{
if (method_exists($e, 'getResponseHeaders')) {
$headers = $e->getResponseHeaders() ?: [];
$value = $headers['Retry-After'] ?? $headers['retry-after'] ?? null;
if (is_array($value)) {
$value = $value[0] ?? null;
}
if (is_numeric($value)) {
return (int) $value;
}
}
return 10;
}
public function getMinimumApiVersion(): string
{
return self::MIN_API_VERSION;
}
public function getInstance(): Factory
{
return new Factory([
'key' => $this->accessToken,
'oauth2' => true,
'base_url' => $this->baseUrl,
]);
}
public function getNewInstance(): Discovery
{
return \HubSpot\Factory::createWithAccessToken($this->accessToken);
}
/**
* Secondly and daily limits for Hubspot API
*
* Product Tier: Free & Starter | Professional & Enterprise | API add-on (any tier)
* Burst: 100/10 seconds | 150/10 seconds | 200/10 seconds
* Daily: 250,000 | 500,000 | 1,000,000
*
* Official documentation states: The search endpoints are rate limited to five requests per second.
* Since with 5 RPS were still hitting secondly rate limits we lowered it to 4
*/
public function getPaginatedData(array $payload, string $type, int $offset = 0): array
{
$total = 0;
$lastId = null;
$rows = [];
foreach ($this->getPaginatedDataGenerator($payload, $type, $offset, $total, $lastId) as $row) {
$rows[] = $row;
}
return ['results' => $rows, 'total' => $total, 'last_record' => $lastId];
}
/**
* @throws HubspotException
* @throws SocialAccountTokenInvalidException
* @throws BadRequest
*/
public function getPaginatedDataGenerator(
array $payload,
string $type,
int $offset = 0,
int &$total = 0,
?string &$lastRecordId = null
): \Generator {
return $this->paginationService->getPaginatedDataGenerator(
$this,
$payload,
$type,
$offset,
$total,
$lastRecordId
);
}
/**
* @throws DealApiException
* @throws CrmException
*/
public function getOpportunityById(string $crmId, array $fields): array
{
try {
$deal = $this->executeRequest(fn () => $this->getNewInstance()->crm()->deals()->basicApi()->getById(
$crmId,
implode(',', $fields),
'companies,contacts'
));
} catch (DealApiException $e) {
$this->log->info('[Hubspot] Failed to fetch opportunity', [
'crm_id' => $crmId,
'reason' => $e->getMessage(),
]);
throw $e;
}
if (! $deal instanceof DealWithAssociations) {
throw new CrmException('Deal not found');
}
return [
'id' => $deal->getId(),
'properties' => $deal->getProperties(),
'associations' => $deal->getAssociations(),
];
}
/**
* Generic batch read method for HubSpot objects
*
* @param string $objectType The object type ('deals', 'companies', 'contacts')
* @param array<string> $crmIds Array of HubSpot object IDs (max 100)
* @param array<string> $fields Array of property names to fetch
*
* @return array<string, array> Array keyed by CRM ID with object data
*/
private function batchReadObjects(string $objectType, array $crmIds, array $fields): array
{
if (empty($crmIds)) {
return [];
}
$this->validateBatchSize($objectType, $crmIds);
$this->ensureValidToken();
try {
$batchConfig = $this->createBatchConfiguration($objectType);
$batchReadRequest = $this->prepareBatchRequest($batchConfig, $crmIds, $fields);
$response = $batchConfig['api']->read($batchReadRequest);
$this->validateApiResponse($response, $objectType);
$results = $this->processApiResults($response);
$this->logBatchResults($objectType, $crmIds, $results);
return $results;
} catch (\Throwable $e) {
$this->handleBatchError($e, $objectType, $crmIds);
}
}
private function validateBatchSize(string $objectType, array $crmIds): void
{
if (count($crmIds) > 100) {
throw new \InvalidArgumentException("Batch size cannot exceed 100 {$objectType}");
}
}
private function createBatchConfiguration(string $objectType): array
{
$configurations = [
'deals' => [
'batchReadRequest' => new \HubSpot\Client\Crm\Deals\Model\BatchReadInputSimplePublicObjectId(),
'inputClass' => \HubSpot\Client\Crm\Deals\Model\SimplePublicObjectId::class,
'api' => $this->getNewInstance()->crm()->deals()->batchApi(),
],
'companies' => [
'batchReadRequest' => new \HubSpot\Client\Crm\Companies\Model\BatchReadInputSimplePublicObjectId(),
'inputClass' => \HubSpot\Client\Crm\Companies\Model\SimplePublicObjectId::class,
'api' => $this->getNewInstance()->crm()->companies()->batchApi(),
],
'contacts' => [
'batchReadRequest' => new \HubSpot\Client\Crm\Contacts\Model\BatchReadInputSimplePublicObjectId(),
'inputClass' => \HubSpot\Client\Crm\Contacts\Model\SimplePublicObjectId::class,
'api' => $this->getNewInstance()->crm()->contacts()->batchApi(),
],
];
if (! isset($configurations[$objectType])) {
throw new \InvalidArgumentException("Unsupported object type: {$objectType}");
}
return $configurations[$objectType];
}
private function prepareBatchRequest(array $batchConfig, array $crmIds, array $fields): object
{
$batchReadRequest = $batchConfig['batchReadRequest'];
$inputClass = $batchConfig['inputClass'];
$inputs = array_map(function ($crmId) use ($inputClass) {
$input = new $inputClass();
$input->setId($crmId);
return $input;
}, $crmIds);
$batchReadRequest->setInputs($inputs);
$batchReadRequest->setProperties($fields);
return $batchReadRequest;
}
private function validateApiResponse($response, string $objectType): void
{
if (! $response) {
throw new CrmException("HubSpot API returned null response for {$objectType} batch read");
}
}
private function processApiResults($response): array
{
$results = [];
$responseResults = $response->getResults();
if ($responseResults) {
foreach ($responseResults as $object) {
if ($object && $object->getId()) {
$results[$object->getId()] = [
'id' => $object->getId(),
'properties' => $object->getProperties() ?: [],
];
}
}
}
return $results;
}
private function logBatchResults(string $objectType, array $crmIds, array $results): void
{
$this->log->info("[HubSpot] Batch fetched {$objectType}", [
'requested_count' => count($crmIds),
'returned_count' => count($results),
'crm_ids' => $crmIds,
]);
}
private function handleBatchError(\Throwable $e, string $objectType, array $crmIds): void
{
$errorMessage = $e->getMessage() ?: 'Unknown error';
$errorTrace = $e->getTraceAsString() ?: 'No trace available';
$this->log->error("[HubSpot] Failed to batch fetch {$objectType}", [
'crm_ids' => $crmIds,
'error' => $errorMessage,
'trace' => $errorTrace,
]);
throw new CrmException("Failed to batch fetch {$objectType}: " . $errorMessage);
}
/**
* Batch read multiple opportunities by their CRM IDs
*
* @param array<string> $crmIds Array of HubSpot deal IDs (max 100)
* @param array<string> $fields Array of property names to fetch
*
* @return array<string, array> Array keyed by CRM ID with opportunity data
*/
public function getOpportunitiesByIds(array $crmIds, array $fields): array
{
return $this->batchReadObjects('deals', $crmIds, $fields);
}
/**
* Batch read multiple companies by their CRM IDs
*
* @param array<string> $crmIds Array of HubSpot company IDs (max 100)
* @param array<string> $fields Array of property names to fetch
*
* @return array<string, array> Array keyed by CRM ID with company data
*/
public function getCompaniesByIds(array $crmIds, array $fields): array
{
return $this->batchReadObjects('companies', $crmIds, $fields);
}
/**
* Batch read multiple contacts by their CRM IDs
*
* @param array<string> $crmIds Array of HubSpot contact IDs (max 100)
* @param array<string> $fields Array of property names to fetch
*
* @return array<string, array> Array keyed by CRM ID with contact data
*/
public function getContactsByIds(array $crmIds, array $fields): array
{
return $this->batchReadObjects('contacts', $crmIds, $fields);
}
/**
* @throws CompanyApiException
* @throws CrmException
*/
public function getAccountById(string $crmId, array $fields): array
{
try {
$company = $this->getNewInstance()->crm()->companies()->basicApi()->getById(
$crmId,
implode(',', $fields),
);
} catch (CompanyApiException $e) {
$this->log->info('[Hubspot] Failed to fetch account', [
'crm_id' => $crmId,
'reason' => $e->getMessage(),
]);
throw $e;
}
if (! $company instanceof CompaniesWithAssociations) {
throw new CrmException('Account not found');
}
return [
'id' => $company->getId(),
'properties' => $company->getProperties(),
];
}
/**
* @throws ContactApiException
* @throws CrmException
*/
public function getContactById(string $crmId, array $fields): array
{
try {
$contact = $this->getNewInstance()->crm()->contacts()->basicApi()->getById(
$crmId,
implode(',', $fields)
);
} catch (ContactApiException $e) {
$this->log->info('[Hubspot] Failed to fetch contact', [
'crm_id' => $crmId,
'reason' => $e->getMessage(),
]);
throw $e;
}
if (! $contact instanceof ContactsWithAssociations) {
throw new CrmException('Contact not found');
}
return [
'id' => $contact->getId(),
'properties' => $contact->getProperties(),
];
}
/**
* This is email search request that Hubspot offers as GET (more generous quota)
*/
public function getContactByEmail(string $email, array $fields = []): array
{
try {
$contact = $this->getNewInstance()->crm()->contacts()->basicApi()->getById(
$email,
implode(',', $fields),
null,
false,
'email'
);
return [
'id' => $contact->getId(),
'properties' => $contact->getProperties(),
];
} catch (ContactApiException $e) {
$this->log->info('[Hubspot] Failed to fetch contact', [
'email' => $email,
'reason' => $e->getMessage(),
]);
return [];
}
}
/**
* @throws CrmException
*/
public function fetchProperty(string $objectType, string $propertyId): Property
{
$result = $this->getNewInstance()->crm()->properties()->coreApi()->getByName($objectType, $propertyId);
if (! $result instanceof Property) {
$this->log->error('[Hubspot] Failed to fetch property', [
'object_type' => $objectType,
'property_id' => $propertyId,
'reason' => $result->getMessage(),
]);
throw new CrmException('Failed to fetch property');
}
return $result;
}
/**
* @return array<CrmFieldOption>
*/
public function fetchPropertyOptions(string $objectType, string $propertyId): array
{
/** @var array<CrmFieldOption> */
return $this->fetchProperty($objectType, $propertyId)->getOptions();
}
/**
* @return array<array{id:string, label:string, deleted:bool}>
*/
public function fetchCallDispositions(): array
{
/** @var Response $response */
$response = $this->getInstance()->engagements()->getCallDispositions();
/**
* @var array<array{
* id:string,
* label:string,
* deleted: bool
* }>
*/
return $response->toArray();
}
/**
* @return array<CrmFieldOption>
*/
public function fetchOpportunityPipelineStages(): array
{
$stages = [];
$apiResponse = $this->getNewInstance()->crm()->pipelines()->pipelinesApi()->getAll('deals');
if ($apiResponse instanceof Error) {
$this->log->error('[Hubspot] Failed to fetch opportunity pipelines', [
'reason' => $apiResponse->getMessage(),
]);
return [];
}
foreach ($apiResponse->getResults() as $pipeline) {
$pipelineStages = array_map(
static function (PipelineStage $stage) {
return [
'id' => $stage->getId(),
'label' => $stage->getLabel(),
];
},
$pipeline->getStages()
);
$stages = array_merge($stages, $pipelineStages);
}
return $stages;
}
public function fetchOpportunityPipelines(): array
{
$pipelines = [];
try {
$apiResponse = $this->makeRequest('/crm/v3/pipelines/deals');
} catch (\Exception $e) {
$this->log->info('[Hubspot] Failed to fetch opportunity pipelines', [
'reason' => $e->getMessage(),
]);
return [];
}
$response = $apiResponse->toArray();
foreach ($response['results'] as $pipeline) {
$pipelines[] = [
'id' => $pipeline['id'],
'label' => $pipeline['label'],
];
}
return $pipelines;
}
/**
* @return array<CrmFieldOption>
*/
public function fetchMeetingOutcomeFieldOptions(Field $field): array
{
return $field->getCrmProviderId() === 'meetingOutcome'
? $this->fetchMeetingOutcomeTypes()
: $this->fetchCallActivityTypes();
}
public function fetchMeetingOutcomeTypes(): array
{
return $this->extractMeetingTypeOptions(
'[URL_WITH_CREDENTIALS] Response $response */
$response = $this->getInstance()
->getClient()
->request('GET', $endpoint);
/**
* @var array<array{
* value: string,
* label: string,
* displayOrder: int
* }> $optionData
*/
$optionData = $response->toArray()['options'] ?? [];
$options = [];
foreach ($optionData as $item) {
$options[] = [
'id' => $item['value'],
'value' => $item['value'],
'label' => $item['label'],
'display_order' => $item['displayOrder'],
];
}
return $options;
}
/**
* @return array<CrmFieldOption>
*/
public function fetchDispositionFieldOptions(): array
{
$options = [];
$dispositions = $this->fetchCallDispositions();
foreach ($dispositions as $disposition) {
if ($disposition['deleted'] !== false) {
continue;
}
$option['value'] = $disposition['id'];
$option['id'] = $disposition['id'];
$option['label'] = $disposition['label'];
$options[] = $option;
}
return $options;
}
/**
* @return array<CrmFieldOption>
*/
public function fetchOpportunityFieldOptions(Field $field): array
{
if ($field->isStageField()) {
return $this->fetchOpportunityPipelineStages();
}
if ($field->isPipelineField()) {
return $this->fetchOpportunityPipelines();
}
return $this->fetchPropertyOptions('deals', $field->getCrmProviderId());
}
/**
* @throws BadRequest
* @throws HubspotException
*/
public function makeRequest(string $endpoint, $method = 'GET', $payload = [], ?string $queryString = null)
{
$endpoint = self::BASE_URL . $endpoint;
if ($method === 'GET') {
$response = $this->getInstance()->getClient()?->request(
method: $method,
endpoint: $endpoint,
query_string: $queryString
);
} else {
$response = $this->getInstance()->getClient()->request($method, $endpoint, [
'json' => ($payload),
]);
}
$max = $response->getHeaderLine('X-HubSpot-RateLimit-Max'); // "110"
$remaining = $response->getHeaderLine('X-HubSpot-RateLimit-Remaining'); // "109"
$interval = $response->getHeaderLine('X-HubSpot-RateLimit-Interval-Milliseconds'); // "10000"
$body = json_decode((string) $response->getBody(), true);
\Illuminate\Support\Facades\Log::channel('custom_channel')->info('$max ' . PHP_EOL . print_r($max, true));
\Illuminate\Support\Facades\Log::channel('custom_channel')->info('$remaining ' . PHP_EOL . print_r($remaining, true));
\Illuminate\Support\Facades\Log::channel('custom_channel')->info('$interval ' . PHP_EOL . print_r($interval, true));
\Illuminate\Support\Facades\Log::channel('custom_channel')->info('$body ' . PHP_EOL . print_r($body, true));
return $response;
}
/**
* @throws BadRequest
* @throws HubspotException
*/
public function createMeeting(array $payload): Response
{
$endpoint = '/crm/v3/objects/meetings';
return $this->makeRequest($endpoint, 'POST', $payload);
}
/**
* @throws BadRequest
* @throws HubspotException
*/
public function updateMeeting(string $meetingId, array $payload): Response
{
$endpoint = '/crm/v3/objects/meetings/' . $meetingId;
return $this->makeRequest($endpoint, 'PATCH', $payload);
}
/**
* @throws \Exception
*/
public function createNote(
string $body,
string $ownerId,
int $timestamp,
string $objectId,
NoteObject $noteObject
): ?string {
try {
$noteInput = new SimplePublicObjectInput([
'properties' => [
'hs_note_body' => $body,
'hubspot_owner_id' => $ownerId,
'hs_timestamp' => $timestamp,
],
]);
// Create note
$note = $this->getNewInstance()->crm()->objects()->basicApi()->create('note', $noteInput);
$this->getNewInstance()->crm()->objects()->associationsApi()->create(
'note',
$note->getId(),
$this->getNoteObject($noteObject),
$objectId,
$this->getNoteAssociationType($noteObject),
);
return $note->getId();
} catch (\Exception $e) {
$this->log->error('[Hubspot] Failed to create note', [
'objectId' => $objectId,
'noteObject' => $noteObject->getObjectType(),
'reason' => $e->getMessage(),
]);
\Sentry::captureException($e);
}
return null;
}
public function updateEngagement(string $objectId, array $engagement, array $metadata): void
{
$this->getInstance()->engagements()->update($objectId, $engagement, $metadata);
}
public function getEngagementData(string $engagementId): array
{
$engagement = $this->getInstance()->engagements()->get($engagementId);
return $engagement->toArray();
}
public function createEngagement(array $engagement, array $associations, array $metadata): Response
{
return $this->getInstance()
->engagements()
->create($engagement, $associations, $metadata);
}
public function isUnauthorizedException(\Exception $e): bool
{
// Check for specific HubSpot API exception types first
if ($e instanceof BadRequest) {
// BadRequest can contain 401 status codes
return $e->getCode() === 401;
}
// Check for HTTP client exceptions with status codes
if ($e instanceof \GuzzleHttp\Exception\RequestException && $e->hasResponse()) {
$response = $e->getResponse();
if ($response !== null) {
return $response->getStatusCode() === 401;
}
}
// Check for Guzzle HTTP exceptions
if ($e instanceof \GuzzleHttp\Exception\ClientException) {
return $e->getCode() === 401;
}
// Fallback to string matching as last resort, but be more specific
$message = strtolower($e->getMessage());
return str_contains($message, '401 unauthorized') ||
str_contains($message, 'http 401') ||
str_contains($message, 'status code 401') ||
(preg_match('/\b401\b/', $message) && str_contains($message, 'unauthorized'));
}
/**
* Validates and refreshes the access token if needed before API requests.
* This ensures long-running processes don't fail due to token expiration.
*
* @throws SocialAccountTokenInvalidException
*/
public function ensureValidToken(): void
{
if ($this->oauthAccount === null) {
return;
}
$newToken = $this->tokenManager->ensureValidToken($this->oauthAccount);
if ($newToken !== null) {
$this->accessToken = $newToken;
}
}
public function getConfig()
{
return $this->config;
}
// returns only active (archived=false)
public function getOwners(): array
{
return $this->getNewInstance()->crm()->owners()->getAll();
}
/**
* @param bool $archived
*
* @return array<Owner>|[]
*/
public function getOwnersArchived(bool $archived = true): array
{
$endpoint = '/crm/v3/owners';
$queryParams = [
'archived' => $archived ? 'true' : 'false',
];
$queryString = http_build_query($queryParams);
$owners = [];
try {
$response = $this->makeRequest(endpoint: $endpoint, queryString: $queryString);
$responseData = $response?->toArray();
foreach ($responseData['results'] as $result) {
try {
$owners[] = Owner::create($result);
} catch (Throwable $e) {
$this->log->error('[HubSpot] Failed to process owner data', [
'result' => $result,
'error' => $e->getMessage(),
]);
continue;
}
}
} catch (Throwable $e) {
$this->log->error('HubSpot] Failed to fetch owners', [
'archived' => $archived,
'error' => $e->getMessage(),
]);
return [];
}
return $owners;
}
public function getMeeting(string $engagementId): ObjectWithAssociations
{
return $this->getNewInstance()->crm()->objects()->basicApi()
->getById('meeting', $engagementId, null, 'contact,company,deal');
}
public function deleteEngagement(string $engagementId): void
{
$this->getInstance()->engagements()->delete((int) $engagementId);
}
public function getAssociationsData(array $ids, string $fromObject, string $toObject): array
{
$associationData = [];
$idChunks = array_chunk($ids, self::ASSOCIATIONS_BATCH_SIZE_LIMIT);
foreach ($idChunks as $idChunk) {
try {
$batchInput = new \HubSpot\Client\Crm\Associations\Model\BatchInputPublicObjectId();
$batchInput->setInputs(array_map(function ($id) {
$publicObjectId = new \HubSpot\Client\Crm\Associations\Model\PublicObjectId();
$publicObjectId->setId($id);
return $publicObjectId;
}, $idChunk));
$associatedObjectsData = $this
->getNewInstance()
->crm()
->associations()
->batchApi()
->read($fromObject, $toObject, $batchInput);
if ($associatedObjectsData instanceof \HubSpot\Client\Crm\Associations\Model\BatchResponsePublicAssociationMulti) {
foreach ($associatedObjectsData->getResults() as $association) {
$from = $association->getFrom()->getId();
$toAssociations = $association->getTo();
if (! empty($toAssociations)) {
$associationData[$from] = array_map(function ($item) {
return $item->getId();
}, $toAssociations);
}
}
}
} catch (\Exception $e) {
$this->log->error('[Hubspot] Failed to fetch associations', [
'from_object' => $fromObject,
'to_object' => $toObject,
'reason' => $e->getMessage(),
]);
}
}
return $associationData;
}
/**
* @throws \Exception
*/
private function getNoteAssociationType(NoteObject $noteObject): string
{
return match($noteObject) {
NoteObject::Opportunity => 'note_to_deal',
NoteObject::Lead, NoteObject::Contact => 'note_to_contact', // or 'note_to_lead' if your portal supports it
NoteObject::Account => 'note_to_company',
NoteObject::Call, NoteObject::Event => throw new \Exception('Not supported'),
};
}
/**
* @throws \Exception
*/
private function getNoteObject(NoteObject $noteObject): string
{
return match($noteObject) {
NoteObject::Opportunity => 'deal',
NoteObject::Lead, NoteObject::Contact => 'contact',
NoteObject::Account => 'company',
NoteObject::Call, NoteObject::Event => throw new \Exception('Not supported'),
};
}
public function addAssociations(string $objectType, string $associationType, array $payload): Response
{
$endpoint = "/crm/v4/associations/$objectType/$associationType/batch/create";
return $this->makeRequest($endpoint, 'POST', $payload);
}
public function removeAssociations(string $objectType, string $associationType, array $payload): Response
{
$endpoint = "/crm/v4/associations/$objectType/$associationType/batch/archive";
return $this->makeRequest($endpoint, 'POST', $payload);
}
}
Project
Project
New File or Directory…
Expand Selected
Collapse All
Options
Hide...
|
2743
|
NULL
|
NULL
|
NULL
|
|
2746
|
112
|
45
|
2026-05-07T11:38:24.071470+00:00
|
/Users/lukas/.screenpipe/data/data/2026-05-07/1778 /Users/lukas/.screenpipe/data/data/2026-05-07/1778153904071_m2.jpg...
|
PhpStorm
|
|
True
|
NULL
|
monitor_2
|
NULL
|
NULL
|
NULL
|
NULL
|
Project Files
Preview
Filter
Open in Find Tool Win Project Files
Preview
Filter
Open in Find Tool Window
sync
SyncActivity.php .../app/Jobs/Activity/SyncActivity.php, class
SyncActivity.php .../app/Jobs/Crm/SyncActivity.php, class
SyncableCrmObjectInterface.php .../app/Contracts/Crm/SyncableCrmObjectInterface.php, interface
synced-fields-changed.email.mustache resources/views/emails/postmark-templates/email
synced-fields-changed.subject.mustache resources/views/emails/postmark-templates/email
SyncAccount.php .../app/Console/Commands/Crm/SyncAccount.php, class
SyncActivity.php .../app/Console/Commands/SyncActivity.php, class
SyncActivityException.php .../app/Exceptions/SyncActivityException.php, exception class
SyncActivityProviderAction.php .../app/Listeners/Activities/Providers/SyncActivityProviderAction.php, class
SyncAccount.php.html build/coverage/Console/Commands/Crm
SyncActivity.php.html build/coverage/Console/Commands
SyncActivity.php.html build/coverage/Jobs/Activity
SyncActivity.php.html build/coverage/Jobs/Crm
SyncActivityException.php.html build/coverage/Exceptions
SyncActivityProviderAction.php.html build/coverage/Listeners/Activities/Providers
… more
SyncActivity.php .../app/Jobs/Activity/SyncActivity.php, class
SyncActivity.php .../app/Jobs/Crm/SyncActivity.php, class
SyncableCrmObjectInterface.php .../app/Contracts/Crm/SyncableCrmObjectInterface.php, interface
synced-fields-changed.email.mustache resources/views/emails/postmark-templates/email
synced-fields-changed.subject.mustache resources/views/emails/postmark-templates/email
SyncAccount.php .../app/Console/Commands/Crm/SyncAccount.php, class
SyncActivity.php .../app/Console/Commands/SyncActivity.php, class
SyncActivityException.php .../app/Exceptions/SyncActivityException.php, exception class
SyncActivityProviderAction.php .../app/Listeners/Activities/Providers/SyncActivityProviderAction.php, class
SyncAccount.php.html build/coverage/Console/Commands/Crm
SyncActivity.php.html build/coverage/Console/Commands
SyncActivity.php.html build/coverage/Jobs/Activity
SyncActivity.php.html build/coverage/Jobs/Crm
SyncActivityException.php.html build/coverage/Exceptions
SyncActivityProviderAction.php.html build/coverage/Listeners/Activities/Providers
… more
Jobs/Activity/SyncActivity.php
Open In Right Split...
|
[{"role":"AXButton","text" [{"role":"AXButton","text":"Project Files","depth":2,"bounds":{"left":0.6968085,"top":0.24181964,"width":0.03557181,"height":0.01915403},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Preview","depth":2,"bounds":{"left":0.73238033,"top":0.24181964,"width":0.008643617,"height":0.01915403},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Filter","depth":2,"bounds":{"left":0.74102396,"top":0.24181964,"width":0.008643617,"height":0.01915403},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Open in Find Tool Window","depth":2,"bounds":{"left":0.7496675,"top":0.24181964,"width":0.008643617,"height":0.01915403},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXTextField","text":"sync","depth":1,"bounds":{"left":0.50232714,"top":0.27214685,"width":0.25598404,"height":0.023144454},"on_screen":true,"value":"sync","role_description":"text field","is_enabled":true,"is_focused":true,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"SyncActivity.php .../app/Jobs/Activity/SyncActivity.php, class","depth":2,"bounds":{"left":0.4993351,"top":0.30407023,"width":0.26196808,"height":0.017557861},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"SyncActivity.php .../app/Jobs/Crm/SyncActivity.php, class","depth":2,"bounds":{"left":0.4993351,"top":0.3216281,"width":0.26196808,"height":0.017557861},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"SyncableCrmObjectInterface.php .../app/Contracts/Crm/SyncableCrmObjectInterface.php, interface","depth":2,"bounds":{"left":0.4993351,"top":0.33918595,"width":0.26196808,"height":0.017557861},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"synced-fields-changed.email.mustache resources/views/emails/postmark-templates/email","depth":2,"bounds":{"left":0.4993351,"top":0.3567438,"width":0.26196808,"height":0.017557861},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"synced-fields-changed.subject.mustache resources/views/emails/postmark-templates/email","depth":2,"bounds":{"left":0.4993351,"top":0.37430167,"width":0.26196808,"height":0.017557861},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"SyncAccount.php .../app/Console/Commands/Crm/SyncAccount.php, class","depth":2,"bounds":{"left":0.4993351,"top":0.39185953,"width":0.26196808,"height":0.017557861},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"SyncActivity.php .../app/Console/Commands/SyncActivity.php, class","depth":2,"bounds":{"left":0.4993351,"top":0.4094174,"width":0.26196808,"height":0.017557861},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"SyncActivityException.php .../app/Exceptions/SyncActivityException.php, exception class","depth":2,"bounds":{"left":0.4993351,"top":0.42697525,"width":0.26196808,"height":0.017557861},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"SyncActivityProviderAction.php .../app/Listeners/Activities/Providers/SyncActivityProviderAction.php, class","depth":2,"bounds":{"left":0.4993351,"top":0.4445331,"width":0.26196808,"height":0.017557861},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"SyncAccount.php.html build/coverage/Console/Commands/Crm","depth":2,"bounds":{"left":0.4993351,"top":0.46209097,"width":0.26196808,"height":0.017557861},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"SyncActivity.php.html build/coverage/Console/Commands","depth":2,"bounds":{"left":0.4993351,"top":0.47964883,"width":0.26196808,"height":0.017557861},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"SyncActivity.php.html build/coverage/Jobs/Activity","depth":2,"bounds":{"left":0.4993351,"top":0.49720672,"width":0.26196808,"height":0.017557861},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"SyncActivity.php.html build/coverage/Jobs/Crm","depth":2,"bounds":{"left":0.4993351,"top":0.51476455,"width":0.26196808,"height":0.017557861},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"SyncActivityException.php.html build/coverage/Exceptions","depth":2,"bounds":{"left":0.4993351,"top":0.5323224,"width":0.26196808,"height":0.017557861},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"SyncActivityProviderAction.php.html build/coverage/Listeners/Activities/Providers","depth":2,"bounds":{"left":0.4993351,"top":0.54988027,"width":0.26196808,"height":0.017557861},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"… more","depth":2,"bounds":{"left":0.4993351,"top":0.5674381,"width":0.26196808,"height":0.017557861},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"SyncActivity.php .../app/Jobs/Activity/SyncActivity.php, class","depth":4,"bounds":{"left":0.4993351,"top":0.30407023,"width":0.26196808,"height":0.017557861},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"SyncActivity.php .../app/Jobs/Crm/SyncActivity.php, class","depth":4,"bounds":{"left":0.4993351,"top":0.3216281,"width":0.26196808,"height":0.017557861},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"SyncableCrmObjectInterface.php .../app/Contracts/Crm/SyncableCrmObjectInterface.php, interface","depth":4,"bounds":{"left":0.4993351,"top":0.33918595,"width":0.26196808,"height":0.017557861},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"synced-fields-changed.email.mustache resources/views/emails/postmark-templates/email","depth":4,"bounds":{"left":0.4993351,"top":0.3567438,"width":0.26196808,"height":0.017557861},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"synced-fields-changed.subject.mustache resources/views/emails/postmark-templates/email","depth":4,"bounds":{"left":0.4993351,"top":0.37430167,"width":0.26196808,"height":0.017557861},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"SyncAccount.php .../app/Console/Commands/Crm/SyncAccount.php, class","depth":4,"bounds":{"left":0.4993351,"top":0.39185953,"width":0.26196808,"height":0.017557861},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"SyncActivity.php .../app/Console/Commands/SyncActivity.php, class","depth":4,"bounds":{"left":0.4993351,"top":0.4094174,"width":0.26196808,"height":0.017557861},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"SyncActivityException.php .../app/Exceptions/SyncActivityException.php, exception class","depth":4,"bounds":{"left":0.4993351,"top":0.42697525,"width":0.26196808,"height":0.017557861},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"SyncActivityProviderAction.php .../app/Listeners/Activities/Providers/SyncActivityProviderAction.php, class","depth":4,"bounds":{"left":0.4993351,"top":0.4445331,"width":0.26196808,"height":0.017557861},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"SyncAccount.php.html build/coverage/Console/Commands/Crm","depth":4,"bounds":{"left":0.4993351,"top":0.46209097,"width":0.26196808,"height":0.017557861},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"SyncActivity.php.html build/coverage/Console/Commands","depth":4,"bounds":{"left":0.4993351,"top":0.47964883,"width":0.26196808,"height":0.017557861},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"SyncActivity.php.html build/coverage/Jobs/Activity","depth":4,"bounds":{"left":0.4993351,"top":0.49720672,"width":0.26196808,"height":0.017557861},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"SyncActivity.php.html build/coverage/Jobs/Crm","depth":4,"bounds":{"left":0.4993351,"top":0.51476455,"width":0.26196808,"height":0.017557861},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"SyncActivityException.php.html build/coverage/Exceptions","depth":4,"bounds":{"left":0.4993351,"top":0.5323224,"width":0.26196808,"height":0.017557861},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"SyncActivityProviderAction.php.html build/coverage/Listeners/Activities/Providers","depth":4,"bounds":{"left":0.4993351,"top":0.54988027,"width":0.26196808,"height":0.017557861},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"… more","depth":4,"bounds":{"left":0.4993351,"top":0.5674381,"width":0.26196808,"height":0.017557861},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"Jobs/Activity/SyncActivity.php","depth":1,"bounds":{"left":0.50598407,"top":0.75259376,"width":0.057513297,"height":0.013567438},"on_screen":true,"help_text":"Jobs/Activity/SyncActivity.php","role_description":"text"},{"role":"AXLink","text":"Open In Right Split","depth":1,"bounds":{"left":0.71476066,"top":0.75259376,"width":0.03856383,"height":0.013567438},"on_screen":true,"role_description":"link","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false}]...
|
3429100465900535222
|
-3822405153891725461
|
visual_change
|
accessibility
|
NULL
|
Project Files
Preview
Filter
Open in Find Tool Win Project Files
Preview
Filter
Open in Find Tool Window
sync
SyncActivity.php .../app/Jobs/Activity/SyncActivity.php, class
SyncActivity.php .../app/Jobs/Crm/SyncActivity.php, class
SyncableCrmObjectInterface.php .../app/Contracts/Crm/SyncableCrmObjectInterface.php, interface
synced-fields-changed.email.mustache resources/views/emails/postmark-templates/email
synced-fields-changed.subject.mustache resources/views/emails/postmark-templates/email
SyncAccount.php .../app/Console/Commands/Crm/SyncAccount.php, class
SyncActivity.php .../app/Console/Commands/SyncActivity.php, class
SyncActivityException.php .../app/Exceptions/SyncActivityException.php, exception class
SyncActivityProviderAction.php .../app/Listeners/Activities/Providers/SyncActivityProviderAction.php, class
SyncAccount.php.html build/coverage/Console/Commands/Crm
SyncActivity.php.html build/coverage/Console/Commands
SyncActivity.php.html build/coverage/Jobs/Activity
SyncActivity.php.html build/coverage/Jobs/Crm
SyncActivityException.php.html build/coverage/Exceptions
SyncActivityProviderAction.php.html build/coverage/Listeners/Activities/Providers
… more
SyncActivity.php .../app/Jobs/Activity/SyncActivity.php, class
SyncActivity.php .../app/Jobs/Crm/SyncActivity.php, class
SyncableCrmObjectInterface.php .../app/Contracts/Crm/SyncableCrmObjectInterface.php, interface
synced-fields-changed.email.mustache resources/views/emails/postmark-templates/email
synced-fields-changed.subject.mustache resources/views/emails/postmark-templates/email
SyncAccount.php .../app/Console/Commands/Crm/SyncAccount.php, class
SyncActivity.php .../app/Console/Commands/SyncActivity.php, class
SyncActivityException.php .../app/Exceptions/SyncActivityException.php, exception class
SyncActivityProviderAction.php .../app/Listeners/Activities/Providers/SyncActivityProviderAction.php, class
SyncAccount.php.html build/coverage/Console/Commands/Crm
SyncActivity.php.html build/coverage/Console/Commands
SyncActivity.php.html build/coverage/Jobs/Activity
SyncActivity.php.html build/coverage/Jobs/Crm
SyncActivityException.php.html build/coverage/Exceptions
SyncActivityProviderAction.php.html build/coverage/Listeners/Activities/Providers
… more
Jobs/Activity/SyncActivity.php
Open In Right Split...
|
NULL
|
NULL
|
NULL
|
NULL
|
|
2747
|
112
|
46
|
2026-05-07T11:38:27.148248+00:00
|
/Users/lukas/.screenpipe/data/data/2026-05-07/1778 /Users/lukas/.screenpipe/data/data/2026-05-07/1778153907148_m2.jpg...
|
PhpStorm
|
faVsco.js – SyncOpportunity.php
|
True
|
NULL
|
monitor_2
|
NULL
|
NULL
|
NULL
|
NULL
|
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
Editor for custom.log
Sync Changes
Hide This Notification
Code changed:
Hide
Analyzing…
Previous Highlighted Error
Next Highlighted Error
<?php
declare(strict_types=1);
namespace Jiminny\Jobs\Crm;
use Illuminate\Queue\SerializesModels;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Contracts\Queue\ShouldQueue;
use Jiminny\Component\Queue\Constants;
use Jiminny\Exceptions\SocialAccountTokenInvalidException;
use Jiminny\Jobs\Job;
use Jiminny\Models\Opportunity;
use Jiminny\Repositories\Crm\OpportunityRepository;
use Jiminny\Services\ResolveTeamCrmConnection;
use Psr\Log\LoggerInterface;
class SyncOpportunity extends Job implements ShouldQueue
{
use InteractsWithQueue;
use SerializesModels;
private string $opportunityId;
public function __construct(Opportunity $opportunity)
{
$this->opportunityId = $opportunity->id_string;
$this->onQueue(Constants::QUEUE_CRM_SYNC);
}
/**
* Sync CRM object data to ensure we have an up-to-date copy.
*/
public function handle(
ResolveTeamCrmConnection $resolveTeamCrmConnection,
LoggerInterface $logger,
OpportunityRepository $opportunityRepository
): void {
$opportunity = $opportunityRepository->findByUuid($this->opportunityId);
if (! $opportunity instanceof Opportunity) {
$logger->error(__METHOD__ . ': Opportunity not found', [
'opportunity' => $this->opportunityId,
]);
return;
}
$crmConfig = $opportunity->crm;
try {
$crmService = $resolveTeamCrmConnection->resolveForTeam($crmConfig->getTeam());
$logger->info('[' . $crmService->getDisplayName() . '] Syncing opportunity', [
'opportunityId' => $this->opportunityId,
]);
$crmService->syncOpportunity($opportunity->crm_provider_id);
} catch (SocialAccountTokenInvalidException $exception) {
// Their account is disconnected, and we are unable to perform this job.
$logger->warning('[' . $crmConfig->getProviderName() . '] Failed to sync opportunity', [
'opportunityId' => $this->opportunityId,
'reason' => $exception->getMessage(),
]);
}
}
}...
|
[{"role":"AXButton","text" [{"role":"AXButton","text":"Project: faVsco.js, menu","depth":5,"bounds":{"left":0.025930852,"top":0.019952115,"width":0.03856383,"height":0.025538707},"on_screen":true,"help_text":"~/jiminny/app","role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"master, menu","depth":5,"bounds":{"left":0.064494684,"top":0.019952115,"width":0.034242023,"height":0.025538707},"on_screen":true,"help_text":"Git Branch: master","role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Start Listening for PHP Debug Connections","depth":5,"bounds":{"left":0.8081782,"top":0.019952115,"width":0.011303191,"height":0.025538707},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"AskJiminnyReportActivityServiceTest","depth":6,"bounds":{"left":0.8234708,"top":0.019952115,"width":0.09208777,"height":0.025538707},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Run 'AskJiminnyReportActivityServiceTest'","depth":6,"bounds":{"left":0.9155585,"top":0.019952115,"width":0.011303191,"height":0.025538707},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Debug 'AskJiminnyReportActivityServiceTest'","depth":6,"bounds":{"left":0.9268617,"top":0.019952115,"width":0.011303191,"height":0.025538707},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"More Actions","depth":6,"bounds":{"left":0.9381649,"top":0.019952115,"width":0.011303191,"height":0.025538707},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"JetBrains AI","depth":5,"bounds":{"left":0.96609044,"top":0.019952115,"width":0.011303191,"height":0.025538707},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Search Everywhere","depth":5,"bounds":{"left":0.9773936,"top":0.019952115,"width":0.011303191,"height":0.025538707},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"IDE and Project Settings","depth":5,"bounds":{"left":0.9886968,"top":0.019952115,"width":0.011303186,"height":0.025538707},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Sync Changes","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.008643617,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Hide This Notification","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.008643617,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Code changed:","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.042220745,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Hide","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.008643617,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXTextArea","text":"Editor for custom.log","depth":4,"bounds":{"left":0.5475399,"top":0.0726257,"width":0.44082448,"height":0.9066241},"on_screen":true,"role_description":"text entry area","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Sync Changes","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.008643617,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Hide This Notification","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.008643617,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Code changed:","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.042220745,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Hide","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.008643617,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"Analyzing…","depth":4,"bounds":{"left":0.49235374,"top":0.15003991,"width":0.019946808,"height":0.015163607},"on_screen":true,"role_description":"text"},{"role":"AXButton","text":"Previous Highlighted Error","depth":4,"bounds":{"left":0.51396275,"top":0.14844373,"width":0.00731383,"height":0.018355945},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Next Highlighted Error","depth":4,"bounds":{"left":0.5212766,"top":0.14844373,"width":0.006981383,"height":0.018355945},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXTextArea","text":"<?php\n\ndeclare(strict_types=1);\n\nnamespace Jiminny\\Jobs\\Crm;\n\nuse Illuminate\\Queue\\SerializesModels;\nuse Illuminate\\Queue\\InteractsWithQueue;\nuse Illuminate\\Contracts\\Queue\\ShouldQueue;\nuse Jiminny\\Component\\Queue\\Constants;\nuse Jiminny\\Exceptions\\SocialAccountTokenInvalidException;\nuse Jiminny\\Jobs\\Job;\nuse Jiminny\\Models\\Opportunity;\nuse Jiminny\\Repositories\\Crm\\OpportunityRepository;\nuse Jiminny\\Services\\ResolveTeamCrmConnection;\nuse Psr\\Log\\LoggerInterface;\n\nclass SyncOpportunity extends Job implements ShouldQueue\n{\n use InteractsWithQueue;\n use SerializesModels;\n\n private string $opportunityId;\n\n public function __construct(Opportunity $opportunity)\n {\n $this->opportunityId = $opportunity->id_string;\n\n $this->onQueue(Constants::QUEUE_CRM_SYNC);\n }\n\n /**\n * Sync CRM object data to ensure we have an up-to-date copy.\n */\n public function handle(\n ResolveTeamCrmConnection $resolveTeamCrmConnection,\n LoggerInterface $logger,\n OpportunityRepository $opportunityRepository\n ): void {\n $opportunity = $opportunityRepository->findByUuid($this->opportunityId);\n\n if (! $opportunity instanceof Opportunity) {\n $logger->error(__METHOD__ . ': Opportunity not found', [\n 'opportunity' => $this->opportunityId,\n ]);\n\n return;\n }\n\n $crmConfig = $opportunity->crm;\n\n try {\n $crmService = $resolveTeamCrmConnection->resolveForTeam($crmConfig->getTeam());\n\n $logger->info('[' . $crmService->getDisplayName() . '] Syncing opportunity', [\n 'opportunityId' => $this->opportunityId,\n ]);\n\n $crmService->syncOpportunity($opportunity->crm_provider_id);\n } catch (SocialAccountTokenInvalidException $exception) {\n // Their account is disconnected, and we are unable to perform this job.\n $logger->warning('[' . $crmConfig->getProviderName() . '] Failed to sync opportunity', [\n 'opportunityId' => $this->opportunityId,\n 'reason' => $exception->getMessage(),\n ]);\n }\n }\n}","depth":4,"bounds":{"left":0.11968085,"top":0.14684756,"width":0.4085771,"height":0.8324022},"on_screen":true,"value":"<?php\n\ndeclare(strict_types=1);\n\nnamespace Jiminny\\Jobs\\Crm;\n\nuse Illuminate\\Queue\\SerializesModels;\nuse Illuminate\\Queue\\InteractsWithQueue;\nuse Illuminate\\Contracts\\Queue\\ShouldQueue;\nuse Jiminny\\Component\\Queue\\Constants;\nuse Jiminny\\Exceptions\\SocialAccountTokenInvalidException;\nuse Jiminny\\Jobs\\Job;\nuse Jiminny\\Models\\Opportunity;\nuse Jiminny\\Repositories\\Crm\\OpportunityRepository;\nuse Jiminny\\Services\\ResolveTeamCrmConnection;\nuse Psr\\Log\\LoggerInterface;\n\nclass SyncOpportunity extends Job implements ShouldQueue\n{\n use InteractsWithQueue;\n use SerializesModels;\n\n private string $opportunityId;\n\n public function __construct(Opportunity $opportunity)\n {\n $this->opportunityId = $opportunity->id_string;\n\n $this->onQueue(Constants::QUEUE_CRM_SYNC);\n }\n\n /**\n * Sync CRM object data to ensure we have an up-to-date copy.\n */\n public function handle(\n ResolveTeamCrmConnection $resolveTeamCrmConnection,\n LoggerInterface $logger,\n OpportunityRepository $opportunityRepository\n ): void {\n $opportunity = $opportunityRepository->findByUuid($this->opportunityId);\n\n if (! $opportunity instanceof Opportunity) {\n $logger->error(__METHOD__ . ': Opportunity not found', [\n 'opportunity' => $this->opportunityId,\n ]);\n\n return;\n }\n\n $crmConfig = $opportunity->crm;\n\n try {\n $crmService = $resolveTeamCrmConnection->resolveForTeam($crmConfig->getTeam());\n\n $logger->info('[' . $crmService->getDisplayName() . '] Syncing opportunity', [\n 'opportunityId' => $this->opportunityId,\n ]);\n\n $crmService->syncOpportunity($opportunity->crm_provider_id);\n } catch (SocialAccountTokenInvalidException $exception) {\n // Their account is disconnected, and we are unable to perform this job.\n $logger->warning('[' . $crmConfig->getProviderName() . '] Failed to sync opportunity', [\n 'opportunityId' => $this->opportunityId,\n 'reason' => $exception->getMessage(),\n ]);\n }\n }\n}","role_description":"text entry area","is_enabled":true,"is_focused":true,"is_selected":false,"is_expanded":false}]...
|
623363649462417947
|
-2187022677318767893
|
visual_change
|
accessibility
|
NULL
|
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
Editor for custom.log
Sync Changes
Hide This Notification
Code changed:
Hide
Analyzing…
Previous Highlighted Error
Next Highlighted Error
<?php
declare(strict_types=1);
namespace Jiminny\Jobs\Crm;
use Illuminate\Queue\SerializesModels;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Contracts\Queue\ShouldQueue;
use Jiminny\Component\Queue\Constants;
use Jiminny\Exceptions\SocialAccountTokenInvalidException;
use Jiminny\Jobs\Job;
use Jiminny\Models\Opportunity;
use Jiminny\Repositories\Crm\OpportunityRepository;
use Jiminny\Services\ResolveTeamCrmConnection;
use Psr\Log\LoggerInterface;
class SyncOpportunity extends Job implements ShouldQueue
{
use InteractsWithQueue;
use SerializesModels;
private string $opportunityId;
public function __construct(Opportunity $opportunity)
{
$this->opportunityId = $opportunity->id_string;
$this->onQueue(Constants::QUEUE_CRM_SYNC);
}
/**
* Sync CRM object data to ensure we have an up-to-date copy.
*/
public function handle(
ResolveTeamCrmConnection $resolveTeamCrmConnection,
LoggerInterface $logger,
OpportunityRepository $opportunityRepository
): void {
$opportunity = $opportunityRepository->findByUuid($this->opportunityId);
if (! $opportunity instanceof Opportunity) {
$logger->error(__METHOD__ . ': Opportunity not found', [
'opportunity' => $this->opportunityId,
]);
return;
}
$crmConfig = $opportunity->crm;
try {
$crmService = $resolveTeamCrmConnection->resolveForTeam($crmConfig->getTeam());
$logger->info('[' . $crmService->getDisplayName() . '] Syncing opportunity', [
'opportunityId' => $this->opportunityId,
]);
$crmService->syncOpportunity($opportunity->crm_provider_id);
} catch (SocialAccountTokenInvalidException $exception) {
// Their account is disconnected, and we are unable to perform this job.
$logger->warning('[' . $crmConfig->getProviderName() . '] Failed to sync opportunity', [
'opportunityId' => $this->opportunityId,
'reason' => $exception->getMessage(),
]);
}
}
}...
|
2746
|
NULL
|
NULL
|
NULL
|
|
2748
|
111
|
44
|
2026-05-07T11:38:28.609738+00:00
|
/Users/lukas/.screenpipe/data/data/2026-05-07/1778 /Users/lukas/.screenpipe/data/data/2026-05-07/1778153908609_m1.jpg...
|
PhpStorm
|
faVsco.js – SyncOpportunity.php
|
True
|
NULL
|
monitor_1
|
NULL
|
NULL
|
NULL
|
NULL
|
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
Editor for custom.log
Sync Changes
Hide This Notification
Code changed:
Hide
1
Previous Highlighted Error
Next Highlighted Error
<?php
declare(strict_types=1);
namespace Jiminny\Jobs\Crm;
use Illuminate\Queue\SerializesModels;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Contracts\Queue\ShouldQueue;
use Jiminny\Component\Queue\Constants;
use Jiminny\Exceptions\SocialAccountTokenInvalidException;
use Jiminny\Jobs\Job;
use Jiminny\Models\Opportunity;
use Jiminny\Repositories\Crm\OpportunityRepository;
use Jiminny\Services\ResolveTeamCrmConnection;
use Psr\Log\LoggerInterface;
class SyncOpportunity extends Job implements ShouldQueue
{
use InteractsWithQueue;
use SerializesModels;
private string $opportunityId;
public function __construct(Opportunity $opportunity)
{
$this->opportunityId = $opportunity->id_string;
$this->onQueue(Constants::QUEUE_CRM_SYNC);
}
/**
* Sync CRM object data to ensure we have an up-to-date copy.
*/
public function handle(
ResolveTeamCrmConnection $resolveTeamCrmConnection,
LoggerInterface $logger,
OpportunityRepository $opportunityRepository
): void {
$opportunity = $opportunityRepository->findByUuid($this->opportunityId);
if (! $opportunity instanceof Opportunity) {
$logger->error(__METHOD__ . ': Opportunity not found', [
'opportunity' => $this->opportunityId,
]);
return;
}
$crmConfig = $opportunity->crm;
try {
$crmService = $resolveTeamCrmConnection->resolveForTeam($crmConfig->getTeam());
$logger->info('[' . $crmService->getDisplayName() . '] Syncing opportunity', [
'opportunityId' => $this->opportunityId,
]);
$crmService->syncOpportunity($opportunity->crm_provider_id);
} catch (SocialAccountTokenInvalidException $exception) {
// Their account is disconnected, and we are unable to perform this job.
$logger->warning('[' . $crmConfig->getProviderName() . '] Failed to sync opportunity', [
'opportunityId' => $this->opportunityId,
'reason' => $exception->getMessage(),
]);
}
}
}
Project
Project
New File or Directory…
Expand Selected
Collapse All
Options
Hide...
|
[{"role":"AXButton","text" [{"role":"AXButton","text":"Project: faVsco.js, menu","depth":5,"on_screen":true,"help_text":"~/jiminny/app","role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"master, menu","depth":5,"on_screen":true,"help_text":"Git Branch: master","role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Start Listening for PHP Debug Connections","depth":5,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"AskJiminnyReportActivityServiceTest","depth":6,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Run 'AskJiminnyReportActivityServiceTest'","depth":6,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Debug 'AskJiminnyReportActivityServiceTest'","depth":6,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"More Actions","depth":6,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"JetBrains AI","depth":5,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Search Everywhere","depth":5,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"IDE and Project Settings","depth":5,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Sync Changes","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.018055556,"height":0.026666667},"on_screen":false,"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Hide This Notification","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.018055556,"height":0.026666667},"on_screen":false,"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Code changed:","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.088194445,"height":0.027777778},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Hide","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.018055556,"height":0.026666667},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXTextArea","text":"Editor for custom.log","depth":4,"on_screen":true,"role_description":"text entry area","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Sync Changes","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.018055556,"height":0.026666667},"on_screen":false,"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Hide This Notification","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.018055556,"height":0.026666667},"on_screen":false,"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Code changed:","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.088194445,"height":0.027777778},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Hide","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.018055556,"height":0.026666667},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"1","depth":4,"on_screen":true,"role_description":"text"},{"role":"AXButton","text":"Previous Highlighted Error","depth":4,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Next Highlighted Error","depth":4,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXTextArea","text":"<?php\n\ndeclare(strict_types=1);\n\nnamespace Jiminny\\Jobs\\Crm;\n\nuse Illuminate\\Queue\\SerializesModels;\nuse Illuminate\\Queue\\InteractsWithQueue;\nuse Illuminate\\Contracts\\Queue\\ShouldQueue;\nuse Jiminny\\Component\\Queue\\Constants;\nuse Jiminny\\Exceptions\\SocialAccountTokenInvalidException;\nuse Jiminny\\Jobs\\Job;\nuse Jiminny\\Models\\Opportunity;\nuse Jiminny\\Repositories\\Crm\\OpportunityRepository;\nuse Jiminny\\Services\\ResolveTeamCrmConnection;\nuse Psr\\Log\\LoggerInterface;\n\nclass SyncOpportunity extends Job implements ShouldQueue\n{\n use InteractsWithQueue;\n use SerializesModels;\n\n private string $opportunityId;\n\n public function __construct(Opportunity $opportunity)\n {\n $this->opportunityId = $opportunity->id_string;\n\n $this->onQueue(Constants::QUEUE_CRM_SYNC);\n }\n\n /**\n * Sync CRM object data to ensure we have an up-to-date copy.\n */\n public function handle(\n ResolveTeamCrmConnection $resolveTeamCrmConnection,\n LoggerInterface $logger,\n OpportunityRepository $opportunityRepository\n ): void {\n $opportunity = $opportunityRepository->findByUuid($this->opportunityId);\n\n if (! $opportunity instanceof Opportunity) {\n $logger->error(__METHOD__ . ': Opportunity not found', [\n 'opportunity' => $this->opportunityId,\n ]);\n\n return;\n }\n\n $crmConfig = $opportunity->crm;\n\n try {\n $crmService = $resolveTeamCrmConnection->resolveForTeam($crmConfig->getTeam());\n\n $logger->info('[' . $crmService->getDisplayName() . '] Syncing opportunity', [\n 'opportunityId' => $this->opportunityId,\n ]);\n\n $crmService->syncOpportunity($opportunity->crm_provider_id);\n } catch (SocialAccountTokenInvalidException $exception) {\n // Their account is disconnected, and we are unable to perform this job.\n $logger->warning('[' . $crmConfig->getProviderName() . '] Failed to sync opportunity', [\n 'opportunityId' => $this->opportunityId,\n 'reason' => $exception->getMessage(),\n ]);\n }\n }\n}","depth":4,"on_screen":true,"value":"<?php\n\ndeclare(strict_types=1);\n\nnamespace Jiminny\\Jobs\\Crm;\n\nuse Illuminate\\Queue\\SerializesModels;\nuse Illuminate\\Queue\\InteractsWithQueue;\nuse Illuminate\\Contracts\\Queue\\ShouldQueue;\nuse Jiminny\\Component\\Queue\\Constants;\nuse Jiminny\\Exceptions\\SocialAccountTokenInvalidException;\nuse Jiminny\\Jobs\\Job;\nuse Jiminny\\Models\\Opportunity;\nuse Jiminny\\Repositories\\Crm\\OpportunityRepository;\nuse Jiminny\\Services\\ResolveTeamCrmConnection;\nuse Psr\\Log\\LoggerInterface;\n\nclass SyncOpportunity extends Job implements ShouldQueue\n{\n use InteractsWithQueue;\n use SerializesModels;\n\n private string $opportunityId;\n\n public function __construct(Opportunity $opportunity)\n {\n $this->opportunityId = $opportunity->id_string;\n\n $this->onQueue(Constants::QUEUE_CRM_SYNC);\n }\n\n /**\n * Sync CRM object data to ensure we have an up-to-date copy.\n */\n public function handle(\n ResolveTeamCrmConnection $resolveTeamCrmConnection,\n LoggerInterface $logger,\n OpportunityRepository $opportunityRepository\n ): void {\n $opportunity = $opportunityRepository->findByUuid($this->opportunityId);\n\n if (! $opportunity instanceof Opportunity) {\n $logger->error(__METHOD__ . ': Opportunity not found', [\n 'opportunity' => $this->opportunityId,\n ]);\n\n return;\n }\n\n $crmConfig = $opportunity->crm;\n\n try {\n $crmService = $resolveTeamCrmConnection->resolveForTeam($crmConfig->getTeam());\n\n $logger->info('[' . $crmService->getDisplayName() . '] Syncing opportunity', [\n 'opportunityId' => $this->opportunityId,\n ]);\n\n $crmService->syncOpportunity($opportunity->crm_provider_id);\n } catch (SocialAccountTokenInvalidException $exception) {\n // Their account is disconnected, and we are unable to perform this job.\n $logger->warning('[' . $crmConfig->getProviderName() . '] Failed to sync opportunity', [\n 'opportunityId' => $this->opportunityId,\n 'reason' => $exception->getMessage(),\n ]);\n }\n }\n}","role_description":"text entry area","is_enabled":true,"is_focused":true,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"Project","depth":3,"on_screen":false,"role_description":"text"},{"role":"AXButton","text":"Project","depth":3,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"New File or Directory…","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.018055556,"height":0.026666667},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Expand Selected","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.018055556,"height":0.026666667},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Collapse All","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.018055556,"height":0.026666667},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Options","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.018055556,"height":0.026666667},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Hide","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.018055556,"height":0.026666667},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false}]...
|
908213374935449775
|
-1896522874815976721
|
click
|
accessibility
|
NULL
|
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
Editor for custom.log
Sync Changes
Hide This Notification
Code changed:
Hide
1
Previous Highlighted Error
Next Highlighted Error
<?php
declare(strict_types=1);
namespace Jiminny\Jobs\Crm;
use Illuminate\Queue\SerializesModels;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Contracts\Queue\ShouldQueue;
use Jiminny\Component\Queue\Constants;
use Jiminny\Exceptions\SocialAccountTokenInvalidException;
use Jiminny\Jobs\Job;
use Jiminny\Models\Opportunity;
use Jiminny\Repositories\Crm\OpportunityRepository;
use Jiminny\Services\ResolveTeamCrmConnection;
use Psr\Log\LoggerInterface;
class SyncOpportunity extends Job implements ShouldQueue
{
use InteractsWithQueue;
use SerializesModels;
private string $opportunityId;
public function __construct(Opportunity $opportunity)
{
$this->opportunityId = $opportunity->id_string;
$this->onQueue(Constants::QUEUE_CRM_SYNC);
}
/**
* Sync CRM object data to ensure we have an up-to-date copy.
*/
public function handle(
ResolveTeamCrmConnection $resolveTeamCrmConnection,
LoggerInterface $logger,
OpportunityRepository $opportunityRepository
): void {
$opportunity = $opportunityRepository->findByUuid($this->opportunityId);
if (! $opportunity instanceof Opportunity) {
$logger->error(__METHOD__ . ': Opportunity not found', [
'opportunity' => $this->opportunityId,
]);
return;
}
$crmConfig = $opportunity->crm;
try {
$crmService = $resolveTeamCrmConnection->resolveForTeam($crmConfig->getTeam());
$logger->info('[' . $crmService->getDisplayName() . '] Syncing opportunity', [
'opportunityId' => $this->opportunityId,
]);
$crmService->syncOpportunity($opportunity->crm_provider_id);
} catch (SocialAccountTokenInvalidException $exception) {
// Their account is disconnected, and we are unable to perform this job.
$logger->warning('[' . $crmConfig->getProviderName() . '] Failed to sync opportunity', [
'opportunityId' => $this->opportunityId,
'reason' => $exception->getMessage(),
]);
}
}
}
Project
Project
New File or Directory…
Expand Selected
Collapse All
Options
Hide...
|
2744
|
NULL
|
NULL
|
NULL
|
|
2749
|
112
|
47
|
2026-05-07T11:38:28.524278+00:00
|
/Users/lukas/.screenpipe/data/data/2026-05-07/1778 /Users/lukas/.screenpipe/data/data/2026-05-07/1778153908524_m2.jpg...
|
PhpStorm
|
faVsco.js – SyncOpportunity.php
|
True
|
NULL
|
monitor_2
|
NULL
|
NULL
|
NULL
|
NULL
|
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
Editor for custom.log
Sync Changes
Hide This Notification
Code changed:
Hide
1
Previous Highlighted Error
Next Highlighted Error
<?php
declare(strict_types=1);
namespace Jiminny\Jobs\Crm;
use Illuminate\Queue\SerializesModels;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Contracts\Queue\ShouldQueue;
use Jiminny\Component\Queue\Constants;
use Jiminny\Exceptions\SocialAccountTokenInvalidException;
use Jiminny\Jobs\Job;
use Jiminny\Models\Opportunity;
use Jiminny\Repositories\Crm\OpportunityRepository;
use Jiminny\Services\ResolveTeamCrmConnection;
use Psr\Log\LoggerInterface;
class SyncOpportunity extends Job implements ShouldQueue
{
use InteractsWithQueue;
use SerializesModels;
private string $opportunityId;
public function __construct(Opportunity $opportunity)
{
$this->opportunityId = $opportunity->id_string;
$this->onQueue(Constants::QUEUE_CRM_SYNC);
}
/**
* Sync CRM object data to ensure we have an up-to-date copy.
*/
public function handle(
ResolveTeamCrmConnection $resolveTeamCrmConnection,
LoggerInterface $logger,
OpportunityRepository $opportunityRepository
): void {
$opportunity = $opportunityRepository->findByUuid($this->opportunityId);
if (! $opportunity instanceof Opportunity) {
$logger->error(__METHOD__ . ': Opportunity not found', [
'opportunity' => $this->opportunityId,
]);
return;
}
$crmConfig = $opportunity->crm;
try {
$crmService = $resolveTeamCrmConnection->resolveForTeam($crmConfig->getTeam());
$logger->info('[' . $crmService->getDisplayName() . '] Syncing opportunity', [
'opportunityId' => $this->opportunityId,
]);
$crmService->syncOpportunity($opportunity->crm_provider_id);
} catch (SocialAccountTokenInvalidException $exception) {
// Their account is disconnected, and we are unable to perform this job.
$logger->warning('[' . $crmConfig->getProviderName() . '] Failed to sync opportunity', [
'opportunityId' => $this->opportunityId,
'reason' => $exception->getMessage(),
]);
}
}
}
Project
Project
New File or Directory…
Expand Selected
Collapse All
Options
Hide...
|
[{"role":"AXButton","text" [{"role":"AXButton","text":"Project: faVsco.js, menu","depth":5,"bounds":{"left":0.025930852,"top":0.019952115,"width":0.03856383,"height":0.025538707},"on_screen":true,"help_text":"~/jiminny/app","role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"master, menu","depth":5,"bounds":{"left":0.064494684,"top":0.019952115,"width":0.034242023,"height":0.025538707},"on_screen":true,"help_text":"Git Branch: master","role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Start Listening for PHP Debug Connections","depth":5,"bounds":{"left":0.8081782,"top":0.019952115,"width":0.011303191,"height":0.025538707},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"AskJiminnyReportActivityServiceTest","depth":6,"bounds":{"left":0.8234708,"top":0.019952115,"width":0.09208777,"height":0.025538707},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Run 'AskJiminnyReportActivityServiceTest'","depth":6,"bounds":{"left":0.9155585,"top":0.019952115,"width":0.011303191,"height":0.025538707},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Debug 'AskJiminnyReportActivityServiceTest'","depth":6,"bounds":{"left":0.9268617,"top":0.019952115,"width":0.011303191,"height":0.025538707},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"More Actions","depth":6,"bounds":{"left":0.9381649,"top":0.019952115,"width":0.011303191,"height":0.025538707},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"JetBrains AI","depth":5,"bounds":{"left":0.96609044,"top":0.019952115,"width":0.011303191,"height":0.025538707},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Search Everywhere","depth":5,"bounds":{"left":0.9773936,"top":0.019952115,"width":0.011303191,"height":0.025538707},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"IDE and Project Settings","depth":5,"bounds":{"left":0.9886968,"top":0.019952115,"width":0.011303186,"height":0.025538707},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Sync Changes","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.008643617,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Hide This Notification","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.008643617,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Code changed:","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.042220745,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Hide","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.008643617,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXTextArea","text":"Editor for custom.log","depth":4,"bounds":{"left":0.5475399,"top":0.0726257,"width":0.44082448,"height":0.9066241},"on_screen":true,"role_description":"text entry area","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Sync Changes","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.008643617,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Hide This Notification","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.008643617,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Code changed:","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.042220745,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Hide","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.008643617,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"1","depth":4,"bounds":{"left":0.5049867,"top":0.15003991,"width":0.00731383,"height":0.015163607},"on_screen":true,"role_description":"text"},{"role":"AXButton","text":"Previous Highlighted Error","depth":4,"bounds":{"left":0.51396275,"top":0.14844373,"width":0.00731383,"height":0.018355945},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Next Highlighted Error","depth":4,"bounds":{"left":0.5212766,"top":0.14844373,"width":0.006981383,"height":0.018355945},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXTextArea","text":"<?php\n\ndeclare(strict_types=1);\n\nnamespace Jiminny\\Jobs\\Crm;\n\nuse Illuminate\\Queue\\SerializesModels;\nuse Illuminate\\Queue\\InteractsWithQueue;\nuse Illuminate\\Contracts\\Queue\\ShouldQueue;\nuse Jiminny\\Component\\Queue\\Constants;\nuse Jiminny\\Exceptions\\SocialAccountTokenInvalidException;\nuse Jiminny\\Jobs\\Job;\nuse Jiminny\\Models\\Opportunity;\nuse Jiminny\\Repositories\\Crm\\OpportunityRepository;\nuse Jiminny\\Services\\ResolveTeamCrmConnection;\nuse Psr\\Log\\LoggerInterface;\n\nclass SyncOpportunity extends Job implements ShouldQueue\n{\n use InteractsWithQueue;\n use SerializesModels;\n\n private string $opportunityId;\n\n public function __construct(Opportunity $opportunity)\n {\n $this->opportunityId = $opportunity->id_string;\n\n $this->onQueue(Constants::QUEUE_CRM_SYNC);\n }\n\n /**\n * Sync CRM object data to ensure we have an up-to-date copy.\n */\n public function handle(\n ResolveTeamCrmConnection $resolveTeamCrmConnection,\n LoggerInterface $logger,\n OpportunityRepository $opportunityRepository\n ): void {\n $opportunity = $opportunityRepository->findByUuid($this->opportunityId);\n\n if (! $opportunity instanceof Opportunity) {\n $logger->error(__METHOD__ . ': Opportunity not found', [\n 'opportunity' => $this->opportunityId,\n ]);\n\n return;\n }\n\n $crmConfig = $opportunity->crm;\n\n try {\n $crmService = $resolveTeamCrmConnection->resolveForTeam($crmConfig->getTeam());\n\n $logger->info('[' . $crmService->getDisplayName() . '] Syncing opportunity', [\n 'opportunityId' => $this->opportunityId,\n ]);\n\n $crmService->syncOpportunity($opportunity->crm_provider_id);\n } catch (SocialAccountTokenInvalidException $exception) {\n // Their account is disconnected, and we are unable to perform this job.\n $logger->warning('[' . $crmConfig->getProviderName() . '] Failed to sync opportunity', [\n 'opportunityId' => $this->opportunityId,\n 'reason' => $exception->getMessage(),\n ]);\n }\n }\n}","depth":4,"bounds":{"left":0.11968085,"top":0.14684756,"width":0.4085771,"height":0.85315245},"on_screen":true,"value":"<?php\n\ndeclare(strict_types=1);\n\nnamespace Jiminny\\Jobs\\Crm;\n\nuse Illuminate\\Queue\\SerializesModels;\nuse Illuminate\\Queue\\InteractsWithQueue;\nuse Illuminate\\Contracts\\Queue\\ShouldQueue;\nuse Jiminny\\Component\\Queue\\Constants;\nuse Jiminny\\Exceptions\\SocialAccountTokenInvalidException;\nuse Jiminny\\Jobs\\Job;\nuse Jiminny\\Models\\Opportunity;\nuse Jiminny\\Repositories\\Crm\\OpportunityRepository;\nuse Jiminny\\Services\\ResolveTeamCrmConnection;\nuse Psr\\Log\\LoggerInterface;\n\nclass SyncOpportunity extends Job implements ShouldQueue\n{\n use InteractsWithQueue;\n use SerializesModels;\n\n private string $opportunityId;\n\n public function __construct(Opportunity $opportunity)\n {\n $this->opportunityId = $opportunity->id_string;\n\n $this->onQueue(Constants::QUEUE_CRM_SYNC);\n }\n\n /**\n * Sync CRM object data to ensure we have an up-to-date copy.\n */\n public function handle(\n ResolveTeamCrmConnection $resolveTeamCrmConnection,\n LoggerInterface $logger,\n OpportunityRepository $opportunityRepository\n ): void {\n $opportunity = $opportunityRepository->findByUuid($this->opportunityId);\n\n if (! $opportunity instanceof Opportunity) {\n $logger->error(__METHOD__ . ': Opportunity not found', [\n 'opportunity' => $this->opportunityId,\n ]);\n\n return;\n }\n\n $crmConfig = $opportunity->crm;\n\n try {\n $crmService = $resolveTeamCrmConnection->resolveForTeam($crmConfig->getTeam());\n\n $logger->info('[' . $crmService->getDisplayName() . '] Syncing opportunity', [\n 'opportunityId' => $this->opportunityId,\n ]);\n\n $crmService->syncOpportunity($opportunity->crm_provider_id);\n } catch (SocialAccountTokenInvalidException $exception) {\n // Their account is disconnected, and we are unable to perform this job.\n $logger->warning('[' . $crmConfig->getProviderName() . '] Failed to sync opportunity', [\n 'opportunityId' => $this->opportunityId,\n 'reason' => $exception->getMessage(),\n ]);\n }\n }\n}","role_description":"text entry area","is_enabled":true,"is_focused":true,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"Project","depth":3,"on_screen":false,"role_description":"text"},{"role":"AXButton","text":"Project","depth":3,"bounds":{"left":0.011968086,"top":0.047885075,"width":0.024268618,"height":0.024740623},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"New File or Directory…","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.008643617,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Expand Selected","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.008643617,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Collapse All","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.008643617,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Options","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.008643617,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Hide","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.008643617,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false}]...
|
908213374935449775
|
-1896522874815976721
|
click
|
accessibility
|
NULL
|
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
Editor for custom.log
Sync Changes
Hide This Notification
Code changed:
Hide
1
Previous Highlighted Error
Next Highlighted Error
<?php
declare(strict_types=1);
namespace Jiminny\Jobs\Crm;
use Illuminate\Queue\SerializesModels;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Contracts\Queue\ShouldQueue;
use Jiminny\Component\Queue\Constants;
use Jiminny\Exceptions\SocialAccountTokenInvalidException;
use Jiminny\Jobs\Job;
use Jiminny\Models\Opportunity;
use Jiminny\Repositories\Crm\OpportunityRepository;
use Jiminny\Services\ResolveTeamCrmConnection;
use Psr\Log\LoggerInterface;
class SyncOpportunity extends Job implements ShouldQueue
{
use InteractsWithQueue;
use SerializesModels;
private string $opportunityId;
public function __construct(Opportunity $opportunity)
{
$this->opportunityId = $opportunity->id_string;
$this->onQueue(Constants::QUEUE_CRM_SYNC);
}
/**
* Sync CRM object data to ensure we have an up-to-date copy.
*/
public function handle(
ResolveTeamCrmConnection $resolveTeamCrmConnection,
LoggerInterface $logger,
OpportunityRepository $opportunityRepository
): void {
$opportunity = $opportunityRepository->findByUuid($this->opportunityId);
if (! $opportunity instanceof Opportunity) {
$logger->error(__METHOD__ . ': Opportunity not found', [
'opportunity' => $this->opportunityId,
]);
return;
}
$crmConfig = $opportunity->crm;
try {
$crmService = $resolveTeamCrmConnection->resolveForTeam($crmConfig->getTeam());
$logger->info('[' . $crmService->getDisplayName() . '] Syncing opportunity', [
'opportunityId' => $this->opportunityId,
]);
$crmService->syncOpportunity($opportunity->crm_provider_id);
} catch (SocialAccountTokenInvalidException $exception) {
// Their account is disconnected, and we are unable to perform this job.
$logger->warning('[' . $crmConfig->getProviderName() . '] Failed to sync opportunity', [
'opportunityId' => $this->opportunityId,
'reason' => $exception->getMessage(),
]);
}
}
}
Project
Project
New File or Directory…
Expand Selected
Collapse All
Options
Hide...
|
NULL
|
NULL
|
NULL
|
NULL
|
|
2751
|
111
|
45
|
2026-05-07T11:38:55.028924+00:00
|
/Users/lukas/.screenpipe/data/data/2026-05-07/1778 /Users/lukas/.screenpipe/data/data/2026-05-07/1778153935028_m1.jpg...
|
PhpStorm
|
faVsco.js – SyncOpportunitiesJob.php
|
True
|
NULL
|
monitor_1
|
NULL
|
NULL
|
NULL
|
NULL
|
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
Editor for custom.log
Sync Changes
Hide This Notification
Code changed:
Hide
1
1
Previous Highlighted Error
Next Highlighted Error
<?php
declare(strict_types=1);
namespace Jiminny\Jobs\Crm;
use Illuminate\Contracts\Queue\ShouldQueue;
use Jiminny\Component\Queue\Constants;
use Jiminny\Jobs\Job;
use Illuminate\Queue\SerializesModels;
use Illuminate\Queue\InteractsWithQueue;
use Jiminny\Repositories\Crm\CrmConfigurationRepository;
use Jiminny\Services\ResolveTeamCrmConnection;
use Psr\Log\LoggerInterface;
class SyncOpportunitiesJob extends Job implements ShouldQueue
{
use InteractsWithQueue;
use SerializesModels;
private int $crmConfigurationId;
private array $parameters;
private ?string $strategy;
public function __construct(int $crmConfigurationId, array $parameters, ?string $strategy = null)
{
$this->crmConfigurationId = $crmConfigurationId;
$this->parameters = $parameters;
$this->strategy = $strategy;
$this->onQueue(Constants::QUEUE_CRM_SYNC);
}
public function handle(
ResolveTeamCrmConnection $resolveTeamCrmConnection,
LoggerInterface $logger,
CrmConfigurationRepository $crmConfigurationRepository
): void {
$startTime = microtime(true);
$crmConfig = $crmConfigurationRepository->findById($this->crmConfigurationId);
if ($crmConfig === null) {
$logger->error('[SyncOpportunitiesJob] CRM configuration not found', [
'crmConfigurationId' => $this->crmConfigurationId,
]);
return;
}
$crmService = $resolveTeamCrmConnection->resolveForTeam($crmConfig->getTeam());
$logger->info('[' . $crmService->getDisplayName() . '] Syncing opportunities', [
'team' => $crmConfig->getTeam()->getId(),
'parameters' => $this->parameters,
'strategy' => $this->strategy,
]);
$crmService->syncOpportunities($this->parameters, $this->strategy);
$durationMs = round((microtime(true) - $startTime) * 1000, 2);
$logger->info('[' . $crmService->getDisplayName() . '] Syncing opportunities finished', [
'team' => $crmConfig->getTeam()->getId(),
'strategy' => $this->strategy,
'duration_ms' => $durationMs,
]);
}
}
Project
Project
New File or Directory…
Expand Selected
Collapse All
Options
Hide...
|
[{"role":"AXButton","text" [{"role":"AXButton","text":"Project: faVsco.js, menu","depth":5,"on_screen":true,"help_text":"~/jiminny/app","role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"master, menu","depth":5,"on_screen":true,"help_text":"Git Branch: master","role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Start Listening for PHP Debug Connections","depth":5,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"AskJiminnyReportActivityServiceTest","depth":6,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Run 'AskJiminnyReportActivityServiceTest'","depth":6,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Debug 'AskJiminnyReportActivityServiceTest'","depth":6,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"More Actions","depth":6,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"JetBrains AI","depth":5,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Search Everywhere","depth":5,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"IDE and Project Settings","depth":5,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Sync Changes","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.018055556,"height":0.026666667},"on_screen":false,"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Hide This Notification","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.018055556,"height":0.026666667},"on_screen":false,"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Code changed:","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.088194445,"height":0.027777778},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Hide","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.018055556,"height":0.026666667},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXTextArea","text":"Editor for custom.log","depth":4,"on_screen":true,"role_description":"text entry area","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Sync Changes","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.018055556,"height":0.026666667},"on_screen":false,"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Hide This Notification","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.018055556,"height":0.026666667},"on_screen":false,"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Code changed:","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.088194445,"height":0.027777778},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Hide","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.018055556,"height":0.026666667},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"1","depth":4,"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"1","depth":4,"on_screen":true,"role_description":"text"},{"role":"AXButton","text":"Previous Highlighted Error","depth":4,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Next Highlighted Error","depth":4,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXTextArea","text":"<?php\n\ndeclare(strict_types=1);\n\nnamespace Jiminny\\Jobs\\Crm;\n\nuse Illuminate\\Contracts\\Queue\\ShouldQueue;\nuse Jiminny\\Component\\Queue\\Constants;\nuse Jiminny\\Jobs\\Job;\nuse Illuminate\\Queue\\SerializesModels;\nuse Illuminate\\Queue\\InteractsWithQueue;\nuse Jiminny\\Repositories\\Crm\\CrmConfigurationRepository;\nuse Jiminny\\Services\\ResolveTeamCrmConnection;\nuse Psr\\Log\\LoggerInterface;\n\nclass SyncOpportunitiesJob extends Job implements ShouldQueue\n{\n use InteractsWithQueue;\n use SerializesModels;\n\n private int $crmConfigurationId;\n private array $parameters;\n private ?string $strategy;\n\n public function __construct(int $crmConfigurationId, array $parameters, ?string $strategy = null)\n {\n $this->crmConfigurationId = $crmConfigurationId;\n $this->parameters = $parameters;\n $this->strategy = $strategy;\n\n $this->onQueue(Constants::QUEUE_CRM_SYNC);\n }\n\n public function handle(\n ResolveTeamCrmConnection $resolveTeamCrmConnection,\n LoggerInterface $logger,\n CrmConfigurationRepository $crmConfigurationRepository\n ): void {\n $startTime = microtime(true);\n $crmConfig = $crmConfigurationRepository->findById($this->crmConfigurationId);\n\n if ($crmConfig === null) {\n $logger->error('[SyncOpportunitiesJob] CRM configuration not found', [\n 'crmConfigurationId' => $this->crmConfigurationId,\n ]);\n\n return;\n }\n\n $crmService = $resolveTeamCrmConnection->resolveForTeam($crmConfig->getTeam());\n\n $logger->info('[' . $crmService->getDisplayName() . '] Syncing opportunities', [\n 'team' => $crmConfig->getTeam()->getId(),\n 'parameters' => $this->parameters,\n 'strategy' => $this->strategy,\n ]);\n\n $crmService->syncOpportunities($this->parameters, $this->strategy);\n\n $durationMs = round((microtime(true) - $startTime) * 1000, 2);\n $logger->info('[' . $crmService->getDisplayName() . '] Syncing opportunities finished', [\n 'team' => $crmConfig->getTeam()->getId(),\n 'strategy' => $this->strategy,\n 'duration_ms' => $durationMs,\n ]);\n }\n}","depth":4,"on_screen":true,"value":"<?php\n\ndeclare(strict_types=1);\n\nnamespace Jiminny\\Jobs\\Crm;\n\nuse Illuminate\\Contracts\\Queue\\ShouldQueue;\nuse Jiminny\\Component\\Queue\\Constants;\nuse Jiminny\\Jobs\\Job;\nuse Illuminate\\Queue\\SerializesModels;\nuse Illuminate\\Queue\\InteractsWithQueue;\nuse Jiminny\\Repositories\\Crm\\CrmConfigurationRepository;\nuse Jiminny\\Services\\ResolveTeamCrmConnection;\nuse Psr\\Log\\LoggerInterface;\n\nclass SyncOpportunitiesJob extends Job implements ShouldQueue\n{\n use InteractsWithQueue;\n use SerializesModels;\n\n private int $crmConfigurationId;\n private array $parameters;\n private ?string $strategy;\n\n public function __construct(int $crmConfigurationId, array $parameters, ?string $strategy = null)\n {\n $this->crmConfigurationId = $crmConfigurationId;\n $this->parameters = $parameters;\n $this->strategy = $strategy;\n\n $this->onQueue(Constants::QUEUE_CRM_SYNC);\n }\n\n public function handle(\n ResolveTeamCrmConnection $resolveTeamCrmConnection,\n LoggerInterface $logger,\n CrmConfigurationRepository $crmConfigurationRepository\n ): void {\n $startTime = microtime(true);\n $crmConfig = $crmConfigurationRepository->findById($this->crmConfigurationId);\n\n if ($crmConfig === null) {\n $logger->error('[SyncOpportunitiesJob] CRM configuration not found', [\n 'crmConfigurationId' => $this->crmConfigurationId,\n ]);\n\n return;\n }\n\n $crmService = $resolveTeamCrmConnection->resolveForTeam($crmConfig->getTeam());\n\n $logger->info('[' . $crmService->getDisplayName() . '] Syncing opportunities', [\n 'team' => $crmConfig->getTeam()->getId(),\n 'parameters' => $this->parameters,\n 'strategy' => $this->strategy,\n ]);\n\n $crmService->syncOpportunities($this->parameters, $this->strategy);\n\n $durationMs = round((microtime(true) - $startTime) * 1000, 2);\n $logger->info('[' . $crmService->getDisplayName() . '] Syncing opportunities finished', [\n 'team' => $crmConfig->getTeam()->getId(),\n 'strategy' => $this->strategy,\n 'duration_ms' => $durationMs,\n ]);\n }\n}","role_description":"text entry area","is_enabled":true,"is_focused":true,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"Project","depth":3,"on_screen":false,"role_description":"text"},{"role":"AXButton","text":"Project","depth":3,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"New File or Directory…","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.018055556,"height":0.026666667},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Expand Selected","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.018055556,"height":0.026666667},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Collapse All","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.018055556,"height":0.026666667},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Options","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.018055556,"height":0.026666667},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Hide","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.018055556,"height":0.026666667},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false}]...
|
1407994949500831869
|
-4200101383188084283
|
click
|
accessibility
|
NULL
|
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
Editor for custom.log
Sync Changes
Hide This Notification
Code changed:
Hide
1
1
Previous Highlighted Error
Next Highlighted Error
<?php
declare(strict_types=1);
namespace Jiminny\Jobs\Crm;
use Illuminate\Contracts\Queue\ShouldQueue;
use Jiminny\Component\Queue\Constants;
use Jiminny\Jobs\Job;
use Illuminate\Queue\SerializesModels;
use Illuminate\Queue\InteractsWithQueue;
use Jiminny\Repositories\Crm\CrmConfigurationRepository;
use Jiminny\Services\ResolveTeamCrmConnection;
use Psr\Log\LoggerInterface;
class SyncOpportunitiesJob extends Job implements ShouldQueue
{
use InteractsWithQueue;
use SerializesModels;
private int $crmConfigurationId;
private array $parameters;
private ?string $strategy;
public function __construct(int $crmConfigurationId, array $parameters, ?string $strategy = null)
{
$this->crmConfigurationId = $crmConfigurationId;
$this->parameters = $parameters;
$this->strategy = $strategy;
$this->onQueue(Constants::QUEUE_CRM_SYNC);
}
public function handle(
ResolveTeamCrmConnection $resolveTeamCrmConnection,
LoggerInterface $logger,
CrmConfigurationRepository $crmConfigurationRepository
): void {
$startTime = microtime(true);
$crmConfig = $crmConfigurationRepository->findById($this->crmConfigurationId);
if ($crmConfig === null) {
$logger->error('[SyncOpportunitiesJob] CRM configuration not found', [
'crmConfigurationId' => $this->crmConfigurationId,
]);
return;
}
$crmService = $resolveTeamCrmConnection->resolveForTeam($crmConfig->getTeam());
$logger->info('[' . $crmService->getDisplayName() . '] Syncing opportunities', [
'team' => $crmConfig->getTeam()->getId(),
'parameters' => $this->parameters,
'strategy' => $this->strategy,
]);
$crmService->syncOpportunities($this->parameters, $this->strategy);
$durationMs = round((microtime(true) - $startTime) * 1000, 2);
$logger->info('[' . $crmService->getDisplayName() . '] Syncing opportunities finished', [
'team' => $crmConfig->getTeam()->getId(),
'strategy' => $this->strategy,
'duration_ms' => $durationMs,
]);
}
}
Project
Project
New File or Directory…
Expand Selected
Collapse All
Options
Hide...
|
NULL
|
NULL
|
NULL
|
NULL
|
|
2752
|
112
|
49
|
2026-05-07T11:38:54.933973+00:00
|
/Users/lukas/.screenpipe/data/data/2026-05-07/1778 /Users/lukas/.screenpipe/data/data/2026-05-07/1778153934933_m2.jpg...
|
PhpStorm
|
faVsco.js – SyncOpportunitiesJob.php
|
True
|
NULL
|
monitor_2
|
NULL
|
NULL
|
NULL
|
NULL
|
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
Editor for custom.log
Sync Changes
Hide This Notification
Code changed:
Hide
1
1
Previous Highlighted Error
Next Highlighted Error
<?php
declare(strict_types=1);
namespace Jiminny\Jobs\Crm;
use Illuminate\Contracts\Queue\ShouldQueue;
use Jiminny\Component\Queue\Constants;
use Jiminny\Jobs\Job;
use Illuminate\Queue\SerializesModels;
use Illuminate\Queue\InteractsWithQueue;
use Jiminny\Repositories\Crm\CrmConfigurationRepository;
use Jiminny\Services\ResolveTeamCrmConnection;
use Psr\Log\LoggerInterface;
class SyncOpportunitiesJob extends Job implements ShouldQueue
{
use InteractsWithQueue;
use SerializesModels;
private int $crmConfigurationId;
private array $parameters;
private ?string $strategy;
public function __construct(int $crmConfigurationId, array $parameters, ?string $strategy = null)
{
$this->crmConfigurationId = $crmConfigurationId;
$this->parameters = $parameters;
$this->strategy = $strategy;
$this->onQueue(Constants::QUEUE_CRM_SYNC);
}
public function handle(
ResolveTeamCrmConnection $resolveTeamCrmConnection,
LoggerInterface $logger,
CrmConfigurationRepository $crmConfigurationRepository
): void {
$startTime = microtime(true);
$crmConfig = $crmConfigurationRepository->findById($this->crmConfigurationId);
if ($crmConfig === null) {
$logger->error('[SyncOpportunitiesJob] CRM configuration not found', [
'crmConfigurationId' => $this->crmConfigurationId,
]);
return;
}
$crmService = $resolveTeamCrmConnection->resolveForTeam($crmConfig->getTeam());
$logger->info('[' . $crmService->getDisplayName() . '] Syncing opportunities', [
'team' => $crmConfig->getTeam()->getId(),
'parameters' => $this->parameters,
'strategy' => $this->strategy,
]);
$crmService->syncOpportunities($this->parameters, $this->strategy);
$durationMs = round((microtime(true) - $startTime) * 1000, 2);
$logger->info('[' . $crmService->getDisplayName() . '] Syncing opportunities finished', [
'team' => $crmConfig->getTeam()->getId(),
'strategy' => $this->strategy,
'duration_ms' => $durationMs,
]);
}
}
Project
Project
New File or Directory…
Expand Selected
Collapse All
Options
Hide...
|
[{"role":"AXButton","text" [{"role":"AXButton","text":"Project: faVsco.js, menu","depth":5,"bounds":{"left":0.025930852,"top":0.019952115,"width":0.03856383,"height":0.025538707},"on_screen":true,"help_text":"~/jiminny/app","role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"master, menu","depth":5,"bounds":{"left":0.064494684,"top":0.019952115,"width":0.034242023,"height":0.025538707},"on_screen":true,"help_text":"Git Branch: master","role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Start Listening for PHP Debug Connections","depth":5,"bounds":{"left":0.8081782,"top":0.019952115,"width":0.011303191,"height":0.025538707},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"AskJiminnyReportActivityServiceTest","depth":6,"bounds":{"left":0.8234708,"top":0.019952115,"width":0.09208777,"height":0.025538707},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Run 'AskJiminnyReportActivityServiceTest'","depth":6,"bounds":{"left":0.9155585,"top":0.019952115,"width":0.011303191,"height":0.025538707},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Debug 'AskJiminnyReportActivityServiceTest'","depth":6,"bounds":{"left":0.9268617,"top":0.019952115,"width":0.011303191,"height":0.025538707},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"More Actions","depth":6,"bounds":{"left":0.9381649,"top":0.019952115,"width":0.011303191,"height":0.025538707},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"JetBrains AI","depth":5,"bounds":{"left":0.96609044,"top":0.019952115,"width":0.011303191,"height":0.025538707},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Search Everywhere","depth":5,"bounds":{"left":0.9773936,"top":0.019952115,"width":0.011303191,"height":0.025538707},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"IDE and Project Settings","depth":5,"bounds":{"left":0.9886968,"top":0.019952115,"width":0.011303186,"height":0.025538707},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Sync Changes","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.008643617,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Hide This Notification","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.008643617,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Code changed:","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.042220745,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Hide","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.008643617,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXTextArea","text":"Editor for custom.log","depth":4,"bounds":{"left":0.5475399,"top":0.0726257,"width":0.44082448,"height":0.9066241},"on_screen":true,"role_description":"text entry area","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Sync Changes","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.008643617,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Hide This Notification","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.008643617,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Code changed:","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.042220745,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Hide","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.008643617,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"1","depth":4,"bounds":{"left":0.4956782,"top":0.15003991,"width":0.00731383,"height":0.015163607},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"1","depth":4,"bounds":{"left":0.5049867,"top":0.15003991,"width":0.00731383,"height":0.015163607},"on_screen":true,"role_description":"text"},{"role":"AXButton","text":"Previous Highlighted Error","depth":4,"bounds":{"left":0.51396275,"top":0.14844373,"width":0.00731383,"height":0.018355945},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Next Highlighted Error","depth":4,"bounds":{"left":0.5212766,"top":0.14844373,"width":0.006981383,"height":0.018355945},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXTextArea","text":"<?php\n\ndeclare(strict_types=1);\n\nnamespace Jiminny\\Jobs\\Crm;\n\nuse Illuminate\\Contracts\\Queue\\ShouldQueue;\nuse Jiminny\\Component\\Queue\\Constants;\nuse Jiminny\\Jobs\\Job;\nuse Illuminate\\Queue\\SerializesModels;\nuse Illuminate\\Queue\\InteractsWithQueue;\nuse Jiminny\\Repositories\\Crm\\CrmConfigurationRepository;\nuse Jiminny\\Services\\ResolveTeamCrmConnection;\nuse Psr\\Log\\LoggerInterface;\n\nclass SyncOpportunitiesJob extends Job implements ShouldQueue\n{\n use InteractsWithQueue;\n use SerializesModels;\n\n private int $crmConfigurationId;\n private array $parameters;\n private ?string $strategy;\n\n public function __construct(int $crmConfigurationId, array $parameters, ?string $strategy = null)\n {\n $this->crmConfigurationId = $crmConfigurationId;\n $this->parameters = $parameters;\n $this->strategy = $strategy;\n\n $this->onQueue(Constants::QUEUE_CRM_SYNC);\n }\n\n public function handle(\n ResolveTeamCrmConnection $resolveTeamCrmConnection,\n LoggerInterface $logger,\n CrmConfigurationRepository $crmConfigurationRepository\n ): void {\n $startTime = microtime(true);\n $crmConfig = $crmConfigurationRepository->findById($this->crmConfigurationId);\n\n if ($crmConfig === null) {\n $logger->error('[SyncOpportunitiesJob] CRM configuration not found', [\n 'crmConfigurationId' => $this->crmConfigurationId,\n ]);\n\n return;\n }\n\n $crmService = $resolveTeamCrmConnection->resolveForTeam($crmConfig->getTeam());\n\n $logger->info('[' . $crmService->getDisplayName() . '] Syncing opportunities', [\n 'team' => $crmConfig->getTeam()->getId(),\n 'parameters' => $this->parameters,\n 'strategy' => $this->strategy,\n ]);\n\n $crmService->syncOpportunities($this->parameters, $this->strategy);\n\n $durationMs = round((microtime(true) - $startTime) * 1000, 2);\n $logger->info('[' . $crmService->getDisplayName() . '] Syncing opportunities finished', [\n 'team' => $crmConfig->getTeam()->getId(),\n 'strategy' => $this->strategy,\n 'duration_ms' => $durationMs,\n ]);\n }\n}","depth":4,"bounds":{"left":0.11968085,"top":0.14684756,"width":0.4085771,"height":0.85315245},"on_screen":true,"value":"<?php\n\ndeclare(strict_types=1);\n\nnamespace Jiminny\\Jobs\\Crm;\n\nuse Illuminate\\Contracts\\Queue\\ShouldQueue;\nuse Jiminny\\Component\\Queue\\Constants;\nuse Jiminny\\Jobs\\Job;\nuse Illuminate\\Queue\\SerializesModels;\nuse Illuminate\\Queue\\InteractsWithQueue;\nuse Jiminny\\Repositories\\Crm\\CrmConfigurationRepository;\nuse Jiminny\\Services\\ResolveTeamCrmConnection;\nuse Psr\\Log\\LoggerInterface;\n\nclass SyncOpportunitiesJob extends Job implements ShouldQueue\n{\n use InteractsWithQueue;\n use SerializesModels;\n\n private int $crmConfigurationId;\n private array $parameters;\n private ?string $strategy;\n\n public function __construct(int $crmConfigurationId, array $parameters, ?string $strategy = null)\n {\n $this->crmConfigurationId = $crmConfigurationId;\n $this->parameters = $parameters;\n $this->strategy = $strategy;\n\n $this->onQueue(Constants::QUEUE_CRM_SYNC);\n }\n\n public function handle(\n ResolveTeamCrmConnection $resolveTeamCrmConnection,\n LoggerInterface $logger,\n CrmConfigurationRepository $crmConfigurationRepository\n ): void {\n $startTime = microtime(true);\n $crmConfig = $crmConfigurationRepository->findById($this->crmConfigurationId);\n\n if ($crmConfig === null) {\n $logger->error('[SyncOpportunitiesJob] CRM configuration not found', [\n 'crmConfigurationId' => $this->crmConfigurationId,\n ]);\n\n return;\n }\n\n $crmService = $resolveTeamCrmConnection->resolveForTeam($crmConfig->getTeam());\n\n $logger->info('[' . $crmService->getDisplayName() . '] Syncing opportunities', [\n 'team' => $crmConfig->getTeam()->getId(),\n 'parameters' => $this->parameters,\n 'strategy' => $this->strategy,\n ]);\n\n $crmService->syncOpportunities($this->parameters, $this->strategy);\n\n $durationMs = round((microtime(true) - $startTime) * 1000, 2);\n $logger->info('[' . $crmService->getDisplayName() . '] Syncing opportunities finished', [\n 'team' => $crmConfig->getTeam()->getId(),\n 'strategy' => $this->strategy,\n 'duration_ms' => $durationMs,\n ]);\n }\n}","role_description":"text entry area","is_enabled":true,"is_focused":true,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"Project","depth":3,"on_screen":false,"role_description":"text"},{"role":"AXButton","text":"Project","depth":3,"bounds":{"left":0.011968086,"top":0.047885075,"width":0.024268618,"height":0.024740623},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"New File or Directory…","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.008643617,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Expand Selected","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.008643617,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Collapse All","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.008643617,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Options","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.008643617,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Hide","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.008643617,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false}]...
|
1407994949500831869
|
-4200101383188084283
|
click
|
accessibility
|
NULL
|
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
Editor for custom.log
Sync Changes
Hide This Notification
Code changed:
Hide
1
1
Previous Highlighted Error
Next Highlighted Error
<?php
declare(strict_types=1);
namespace Jiminny\Jobs\Crm;
use Illuminate\Contracts\Queue\ShouldQueue;
use Jiminny\Component\Queue\Constants;
use Jiminny\Jobs\Job;
use Illuminate\Queue\SerializesModels;
use Illuminate\Queue\InteractsWithQueue;
use Jiminny\Repositories\Crm\CrmConfigurationRepository;
use Jiminny\Services\ResolveTeamCrmConnection;
use Psr\Log\LoggerInterface;
class SyncOpportunitiesJob extends Job implements ShouldQueue
{
use InteractsWithQueue;
use SerializesModels;
private int $crmConfigurationId;
private array $parameters;
private ?string $strategy;
public function __construct(int $crmConfigurationId, array $parameters, ?string $strategy = null)
{
$this->crmConfigurationId = $crmConfigurationId;
$this->parameters = $parameters;
$this->strategy = $strategy;
$this->onQueue(Constants::QUEUE_CRM_SYNC);
}
public function handle(
ResolveTeamCrmConnection $resolveTeamCrmConnection,
LoggerInterface $logger,
CrmConfigurationRepository $crmConfigurationRepository
): void {
$startTime = microtime(true);
$crmConfig = $crmConfigurationRepository->findById($this->crmConfigurationId);
if ($crmConfig === null) {
$logger->error('[SyncOpportunitiesJob] CRM configuration not found', [
'crmConfigurationId' => $this->crmConfigurationId,
]);
return;
}
$crmService = $resolveTeamCrmConnection->resolveForTeam($crmConfig->getTeam());
$logger->info('[' . $crmService->getDisplayName() . '] Syncing opportunities', [
'team' => $crmConfig->getTeam()->getId(),
'parameters' => $this->parameters,
'strategy' => $this->strategy,
]);
$crmService->syncOpportunities($this->parameters, $this->strategy);
$durationMs = round((microtime(true) - $startTime) * 1000, 2);
$logger->info('[' . $crmService->getDisplayName() . '] Syncing opportunities finished', [
'team' => $crmConfig->getTeam()->getId(),
'strategy' => $this->strategy,
'duration_ms' => $durationMs,
]);
}
}
Project
Project
New File or Directory…
Expand Selected
Collapse All
Options
Hide...
|
NULL
|
NULL
|
NULL
|
NULL
|
|
2753
|
112
|
50
|
2026-05-07T11:39:00.913489+00:00
|
/Users/lukas/.screenpipe/data/data/2026-05-07/1778 /Users/lukas/.screenpipe/data/data/2026-05-07/1778153940913_m2.jpg...
|
PhpStorm
|
|
True
|
NULL
|
monitor_2
|
NULL
|
NULL
|
NULL
|
NULL
|
Search
syncOpportunities (Service .../app/Services Search
syncOpportunities (Service .../app/Services/Crm/Salesforce), public method
syncOpportunities (Service .../app/Services/Crm/Copper), public method
syncOpportunities (Service .../app/Services/Crm/Pipedrive), public method
syncOpportunities (Service .../app/Services/Crm/Close), public method
syncOpportunities (SyncCrmEntitiesTrait .../app/Services/Crm/IntegrationApp/ServiceTraits), public method
syncOpportunities (SyncCrmEntitiesInterface .../app/Contracts/Services/Crm), public abstract method
syncOpportunities (BullhornService .../app/Services/Crm/Bullhorn), public method
syncOpportunities (OpportunitySyncTrait .../app/Services/Crm/Hubspot/ServiceTraits), public method
syncOpportunities (ServiceInterface .../app/Contracts/Services/Crm), public abstract method
syncOpportunities (Service .../app/Services/Crm/Dummy), public method
Choose Declaration...
|
[{"role":"AXTextField","text [{"role":"AXTextField","text":"Search","depth":1,"on_screen":false,"role_description":"text field","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"syncOpportunities (Service .../app/Services/Crm/Salesforce), public method","depth":4,"bounds":{"left":0.19980054,"top":0.7422187,"width":0.2506649,"height":0.017557861},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"syncOpportunities (Service .../app/Services/Crm/Copper), public method","depth":4,"bounds":{"left":0.19980054,"top":0.75977653,"width":0.2506649,"height":0.017557861},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"syncOpportunities (Service .../app/Services/Crm/Pipedrive), public method","depth":4,"bounds":{"left":0.19980054,"top":0.7773344,"width":0.2506649,"height":0.017557861},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"syncOpportunities (Service .../app/Services/Crm/Close), public method","depth":4,"bounds":{"left":0.19980054,"top":0.79489225,"width":0.2506649,"height":0.017557861},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"syncOpportunities (SyncCrmEntitiesTrait .../app/Services/Crm/IntegrationApp/ServiceTraits), public method","depth":4,"bounds":{"left":0.19980054,"top":0.8124501,"width":0.2506649,"height":0.017557861},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"syncOpportunities (SyncCrmEntitiesInterface .../app/Contracts/Services/Crm), public abstract method","depth":4,"bounds":{"left":0.19980054,"top":0.830008,"width":0.2506649,"height":0.017557861},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"syncOpportunities (BullhornService .../app/Services/Crm/Bullhorn), public method","depth":4,"bounds":{"left":0.19980054,"top":0.8475658,"width":0.2506649,"height":0.017557861},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"syncOpportunities (OpportunitySyncTrait .../app/Services/Crm/Hubspot/ServiceTraits), public method","depth":4,"bounds":{"left":0.19980054,"top":0.8651237,"width":0.2506649,"height":0.017557861},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"syncOpportunities (ServiceInterface .../app/Contracts/Services/Crm), public abstract method","depth":4,"bounds":{"left":0.19980054,"top":0.88268155,"width":0.2506649,"height":0.017557861},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"syncOpportunities (Service .../app/Services/Crm/Dummy), public method","depth":4,"bounds":{"left":0.19980054,"top":0.9002394,"width":0.2506649,"height":0.017557861},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"Choose Declaration","depth":1,"bounds":{"left":0.203125,"top":0.7158819,"width":0.24401596,"height":0.026336791},"on_screen":true,"role_description":"text"}]...
|
-5622708014245704429
|
7043171957963170458
|
visual_change
|
accessibility
|
NULL
|
Search
syncOpportunities (Service .../app/Services Search
syncOpportunities (Service .../app/Services/Crm/Salesforce), public method
syncOpportunities (Service .../app/Services/Crm/Copper), public method
syncOpportunities (Service .../app/Services/Crm/Pipedrive), public method
syncOpportunities (Service .../app/Services/Crm/Close), public method
syncOpportunities (SyncCrmEntitiesTrait .../app/Services/Crm/IntegrationApp/ServiceTraits), public method
syncOpportunities (SyncCrmEntitiesInterface .../app/Contracts/Services/Crm), public abstract method
syncOpportunities (BullhornService .../app/Services/Crm/Bullhorn), public method
syncOpportunities (OpportunitySyncTrait .../app/Services/Crm/Hubspot/ServiceTraits), public method
syncOpportunities (ServiceInterface .../app/Contracts/Services/Crm), public abstract method
syncOpportunities (Service .../app/Services/Crm/Dummy), public method
Choose Declaration...
|
2752
|
NULL
|
NULL
|
NULL
|